[
  {
    "path": ".github/CODEOWNERS",
    "content": "# Every request must be reviewed and accepted by:\n\n*\t@felangel"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [felangel]\npatreon: felangel\nopen_collective: bloc\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: Create a report to help us improve\ntitle: \"fix: \"\nlabels: bug\n---\n\n**Description**\nA clear and concise description of what the bug is.\n\n**Steps To Reproduce**\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected Behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional Context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/build.md",
    "content": "---\nname: Build System\nabout: Changes that affect the build system or external dependencies\ntitle: \"build: \"\nlabels: build\n---\n\n**Description**\n\nDescribe what changes need to be done to the build system and why\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/chore.md",
    "content": "---\nname: Chore\nabout: Other changes that don't modify src or test files\ntitle: \"chore: \"\nlabels: chore\n---\n\n**Description**\n\nClearly describe what change is needed and why. If this changes code then please use another issue type.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/ci.md",
    "content": "---\nname: Continuous Integration\nabout: Changes to the CI configuration files and scripts\ntitle: \"ci: \"\nlabels: ci\n---\n\n**Description**\n\nDescribe what changes need to be done to the ci/cd system and why\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.md",
    "content": "---\nname: Documentation\nabout: Improve the documentation so all collaborators have a common understanding\ntitle: \"docs: \"\nlabels: documentation\n---\n\n**Description**\n\nClearly describe what documentation you are looking to add or improve.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: A new feature to be added to the project\ntitle: \"feat: \"\nlabels: feature\n---\n\n**Description**\n\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Desired Solution**\n\nA clear and concise description of what you want to happen.\n\n**Alternatives Considered**\n\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional Context**\n\nAdd any other context or screenshots about the feature request go here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/performance.md",
    "content": "---\nname: Performance Update\nabout: A code change that improves performance\ntitle: \"perf: \"\nlabels: performance\n---\n\n**Description**\n\nClearly describe what code needs to be changed and what the performance impact is going to be.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/refactor.md",
    "content": "---\nname: Refactor\nabout: A code change that neither fixes a bug nor adds a feature\ntitle: \"refactor: \"\nlabels: refactor\n---\n\n**Description**\n\nClearly describe what needs to be refactored any why. Please provide links to related issues (bugs or upcoming features) in order to help prioritize.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/revert.md",
    "content": "---\nname: Revert Commit\nabout: Reverts a previous commit\ntitle: \"revert: \"\nlabels: revert\n---\n\n**Description**\n\nProvide a link to a PR/Commit that you are looking to revert and why.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/style.md",
    "content": "---\nname: Style Changes\nabout: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\ntitle: \"style: \"\nlabels: style\n---\n\n**Description**\n\nClearly describe what you are looking to change and why.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/test.md",
    "content": "---\nname: Test\nabout: Adding missing tests or correcting existing tests\ntitle: \"test: \"\nlabels: test\n---\n\n**Description**\n\nList out the tests that need to be added or changed. Please also include any information as to why this was not covered in the past.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n  Thanks for contributing!\n\n  Provide a description of your changes below and a general summary in the title\n\n  Please look at the following checklist to ensure that your PR can be accepted quickly:\n-->\n\n## Status\n\n**READY/IN DEVELOPMENT/HOLD**\n\n## Breaking Changes\n\nYES | NO\n\n## Description\n\n<!--- Describe your changes in detail -->\n\n## Type of Change\n\n<!--- Put an `x` in all the boxes that apply: -->\n\n- [ ] ✨ New feature (non-breaking change which adds functionality)\n- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)\n- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)\n- [ ] 🧹 Code refactor\n- [ ] ✅ Build configuration change\n- [ ] 📝 Documentation\n- [ ] 🗑️ Chore\n"
  },
  {
    "path": ".github/actions/angular_dart_package/action.yaml",
    "content": "name: Angular Dart Package Workflow\ndescription: Build and test Angular Dart packages.\n\ninputs:\n  dart_sdk:\n    required: false\n    default: \"stable\"\n    description: \"The dart sdk version to use\"\n  working_directory:\n    required: false\n    default: \".\"\n    description: The working directory for this workflow\n  analyze_directories:\n    required: false\n    default: \"lib test\"\n    description: Directories to analyze\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: 🎯 Setup Dart\n      uses: dart-lang/setup-dart@v1\n      with:\n        sdk: ${{inputs.dart_sdk}}\n\n    - name: 📦 Install Dependencies\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart pub get\n\n    - name: 🛠️ Build\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        dart pub global activate webdev\n        webdev build\n\n    - name: ✨ Format\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart format --set-exit-if-changed .\n\n    - name: 🔍 Analyze\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart analyze --fatal-warnings ${{inputs.analyze_directories}}\n\n    - name: 🧪 Test\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        if [ -d \"test\" ]; then\n          dart run build_runner test --fail-on-severe\n        fi\n"
  },
  {
    "path": ".github/actions/astro_site/action.yaml",
    "content": "name: Astro Site Workflow\ndescription: Build and test Astro sites.\n\ninputs:\n  working_directory:\n    required: false\n    default: \".\"\n    description: The working directory for this workflow\n  node_version:\n    required: false\n    default: \"20\"\n    description: The node version to use.\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: ⚙️ Setup Node\n      uses: actions/setup-node@v4\n      with:\n        node-version: ${{ inputs.node_version }}\n\n    - name: ⬇️ Install Dependencies\n      run: npm install\n      shell: ${{ inputs.shell }}\n      working-directory: ${{ inputs.working_directory }}\n\n    - name: ✨ Check Format\n      run: npm run format:check\n      shell: ${{ inputs.shell }}\n      working-directory: ${{ inputs.working_directory }}\n\n    - name: 📦 Build Site\n      uses: withastro/action@v2\n      with:\n        path: ${{ inputs.working_directory }}\n        node-version: ${{ inputs.node_version }}\n"
  },
  {
    "path": ".github/actions/dart_compile/action.yaml",
    "content": "name: Dart Compile Workflow\ndescription: Compile Dart Executables\n\ninputs:\n  dart_sdk:\n    required: false\n    default: \"stable\"\n    description: \"The dart sdk version to use\"\n  working_directory:\n    required: false\n    default: \".\"\n    description: The working directory for this workflow\n  entrypoint:\n    required: true\n    description: The path to the Dart entrypoint\n  name:\n    required: true\n    description: The name of the executable\n  os:\n    required: true\n    description: The operating system to compile for\n  arch:\n    required: true\n    description: The architecture to compile for\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: 🎯 Setup Dart\n      uses: dart-lang/setup-dart@v1\n      with:\n        sdk: ${{inputs.dart_sdk}}\n\n    - name: 📦 Install Dependencies\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart pub get\n\n    - name: ⚙️ Compile (${{ inputs.os }}, ${{ inputs.arch }})\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        mkdir ${{ runner.temp }}/executable\n        dart compile exe --target-os ${{ inputs.os }} --target-arch ${{ inputs.arch }} ${{ inputs.entrypoint }} -o ${{ runner.temp }}/executable/${{ inputs.name }}\n\n    - name: ⬆️ Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: ${{ inputs.name }}\n        path: ${{ runner.temp }}/executable/${{ inputs.name }}\n"
  },
  {
    "path": ".github/actions/dart_package/action.yaml",
    "content": "name: Dart Package Workflow\ndescription: Build and test Dart packages.\n\ninputs:\n  codecov_token:\n    required: true\n    description: The Codecov token used to upload coverage\n  collect_coverage:\n    required: false\n    default: \"true\"\n    description: Whether to collect code coverage\n  collect_score:\n    required: false\n    default: \"true\"\n    description: Whether to collect the pana score\n  concurrency:\n    required: false\n    default: \"4\"\n    description: The value of the concurrency flag (-j) used when running tests\n  coverage_excludes:\n    required: false\n    default: \"\"\n    description: Globs to exclude from coverage\n  dart_sdk:\n    required: false\n    default: \"stable\"\n    description: \"The dart sdk version to use\"\n  working_directory:\n    required: false\n    default: \".\"\n    description: The working directory for this workflow\n  min_coverage:\n    required: false\n    default: \"100\"\n    description: The minimum coverage percentage value\n  min_score:\n    required: false\n    default: \"120\"\n    description: The minimum pana score value\n  analyze_directories:\n    required: false\n    default: \"lib test\"\n    description: Directories to analyze\n  report_on:\n    required: false\n    default: \"lib\"\n    description: Directories to report on when collecting coverage\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: 🎯 Setup Dart\n      uses: dart-lang/setup-dart@v1\n      with:\n        sdk: ${{inputs.dart_sdk}}\n\n    - name: ⚙️ Setup Bloc Tools\n      run: dart pub global activate --source path ./packages/bloc_tools\n      shell: ${{ inputs.shell }}\n\n    - name: 📦 Install Dependencies\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart pub get\n\n    - name: ✨ Format\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart format --set-exit-if-changed .\n\n    - name: 🔍 Analyze\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart analyze --fatal-warnings ${{inputs.analyze_directories}}\n\n    - name: 🔍 Bloc Lint\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: bloc lint ${{inputs.analyze_directories}}\n\n    - name: 🧪 Test\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        dart pub global activate coverage\n        dart test -j ${{inputs.concurrency}} --coverage=coverage && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=${{inputs.report_on}} --check-ignore\n\n    - name: 📦 Detect Package Name\n      if: inputs.collect_coverage == 'true'\n      env:\n        PACKAGE_PATH: ${{ inputs.working_directory}}\n      id: package\n      shell: ${{ inputs.shell }}\n      run: echo \"name=${PACKAGE_PATH##*/}\" >> $GITHUB_OUTPUT\n\n    - name: ⬆️ Upload Coverage\n      if: inputs.collect_coverage == 'true'\n      uses: codecov/codecov-action@v4\n      env:\n        PACKAGE_PATH: ${{ inputs.working_directory}}\n      with:\n        flags: ${{ steps.package.outputs.name }}\n        token: ${{ inputs.codecov_token }}\n\n    - name: 📊 Verify Coverage\n      if: inputs.collect_coverage == 'true'\n      uses: VeryGoodOpenSource/very_good_coverage@v3\n      with:\n        path: ${{inputs.working_directory}}/coverage/lcov.info\n        exclude: ${{inputs.coverage_excludes}}\n        min_coverage: ${{inputs.min_coverage}}\n\n    - name: 💯 Verify Pub Score\n      if: inputs.collect_score == 'true'\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        dart pub global activate pana\n        sudo apt-get install webp\n        PANA=$(pana . --no-warning); PANA_SCORE=$(echo $PANA | sed -n \"s/.*Points: \\([0-9]*\\)\\/\\([0-9]*\\)./\\1\\/\\2/p\")\n        echo $PANA\n        IFS='/'; read -a SCORE_ARR <<< \"$PANA_SCORE\"; SCORE=SCORE_ARR[0]; TOTAL=SCORE_ARR[1]\n        if [ -z \"$1\" ]; then MINIMUM_SCORE=TOTAL; else MINIMUM_SCORE=$1; fi\n        if (( $SCORE < $MINIMUM_SCORE )); then echo \"minimum score $MINIMUM_SCORE was not met!\"; exit 1; fi\n"
  },
  {
    "path": ".github/actions/flutter_package/action.yaml",
    "content": "name: Flutter Package Workflow\ndescription: Build and test a Flutter package.\n\ninputs:\n  codecov_token:\n    required: true\n    description: The Codecov token used to upload coverage\n  collect_coverage:\n    required: false\n    default: \"true\"\n    description: Whether to collect code coverage\n  collect_score:\n    required: false\n    default: \"true\"\n    description: Whether to collect the pana score\n  concurrency:\n    required: false\n    default: \"4\"\n    description: The value of the concurrency flag (-j) used when running tests\n  coverage_excludes:\n    required: false\n    default: \"\"\n    description: Globs to exclude from coverage\n  flutter_channel:\n    required: false\n    default: \"stable\"\n    description: The Flutter channel to use\n  working_directory:\n    required: false\n    default: \".\"\n    description: The working directory for this workflow\n  min_coverage:\n    required: false\n    default: \"100\"\n    description: The minimum coverage percentage value\n  analyze_directories:\n    required: false\n    default: \"lib test\"\n    description: Directories to analyze\n  report_on:\n    required: false\n    default: \"lib\"\n    description: Directories to report on when collecting coverage\n  platform:\n    required: false\n    default: \"vm\"\n    description: Platform to use when running tests\n\nruns:\n  using: \"composite\"\n  steps:\n    - name: 🐦 Setup Flutter\n      uses: subosito/flutter-action@v2\n      with:\n        channel: ${{ inputs.flutter_channel }}\n\n    - name: 📦 Install Dependencies\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: flutter pub get\n\n    - name: ⚙️ Setup Bloc Tools\n      run: dart pub global activate --source path ./packages/bloc_tools\n      shell: ${{ inputs.shell }}\n\n    - name: ✨ Format\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart format --set-exit-if-changed .\n\n    - name: 🔍 Analyze\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: dart analyze --fatal-warnings ${{inputs.analyze_directories}}\n\n    - name: 🔍 Bloc Lint\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: bloc lint ${{inputs.analyze_directories}}\n\n    - name: 🧪 Test\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        if [ -d \"test\" ]; then\n          flutter test --no-pub --test-randomize-ordering-seed random --coverage\n        fi\n\n    - name: Exclude Generated Code from Coverage\n      if: ${{ inputs.collect_coverage == 'true' && inputs.coverage_excludes != '' }}\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        mv coverage/lcov.info coverage/lcov.info.bak\n        sudo apt-get -y install lcov\n        lcov --remove coverage/lcov.info.bak \"${{inputs.coverage_excludes}}\" -o coverage/lcov.info\n\n    - name: 📦 Detect Package Name\n      if: inputs.collect_coverage == 'true'\n      env:\n        PACKAGE_PATH: ${{ inputs.working_directory}}\n      id: package\n      shell: ${{ inputs.shell }}\n      run: echo \"name=${PACKAGE_PATH##*/}\" >> $GITHUB_OUTPUT\n\n    - name: ⬆️ Upload Coverage\n      if: inputs.collect_coverage == 'true'\n      uses: codecov/codecov-action@v4\n      env:\n        PACKAGE_PATH: ${{ inputs.working_directory}}\n      with:\n        flags: ${{ steps.package.outputs.name }}\n        token: ${{ inputs.codecov_token }}\n\n    - name: 📊 Verify Coverage\n      if: inputs.collect_coverage == 'true'\n      uses: VeryGoodOpenSource/very_good_coverage@v3\n      with:\n        path: ${{inputs.working_directory}}/coverage/lcov.info\n        exclude: ${{inputs.coverage_excludes}}\n        min_coverage: ${{inputs.min_coverage}}\n\n    - name: 💯 Verify Pub Score\n      if: inputs.collect_score == 'true'\n      working-directory: ${{ inputs.working_directory }}\n      shell: ${{ inputs.shell }}\n      run: |\n        dart pub global activate pana\n        sudo apt-get install webp\n        PANA=$(pana . --no-warning); PANA_SCORE=$(echo $PANA | sed -n \"s/.*Points: \\([0-9]*\\)\\/\\([0-9]*\\)./\\1\\/\\2/p\")\n        echo $PANA\n        IFS='/'; read -a SCORE_ARR <<< \"$PANA_SCORE\"; SCORE=SCORE_ARR[0]; TOTAL=SCORE_ARR[1]\n        if [ -z \"$1\" ]; then MINIMUM_SCORE=TOTAL; else MINIMUM_SCORE=$1; fi\n        if (( $SCORE < $MINIMUM_SCORE )); then echo \"minimum score $MINIMUM_SCORE was not met!\"; exit 1; fi\n"
  },
  {
    "path": ".github/codecov.yml",
    "content": "flag_management:\n  default_rules:\n    carryforward: true\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    groups:\n      github_actions:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/main.yaml",
    "content": "name: build\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  pull_request:\n  push:\n    branches: [master]\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  semantic_pull_request:\n    name: ✅ Semantic Pull Request\n    uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1\n\n  changes:\n    runs-on: ubuntu-latest\n\n    if: github.event.pull_request.draft == false\n\n    outputs:\n      needs_angular_dart_example_checks: ${{ steps.needs_angular_dart_example_checks.outputs.changes }}\n      needs_bloc_tools_e2e_checks: ${{ steps.needs_bloc_tools_e2e_checks.outputs.changes }}\n      needs_dart_package_checks: ${{ steps.needs_dart_package_checks.outputs.changes }}\n      needs_bloc_tools_compile_checks: ${{ steps.needs_bloc_tools_compile_checks.outputs.changes }}\n      needs_flutter_package_checks: ${{ steps.needs_flutter_package_checks.outputs.changes }}\n      needs_flutter_example_checks: ${{ steps.needs_flutter_example_checks.outputs.changes }}\n      needs_docs_checks: ${{ steps.needs_docs_checks.outputs.changes }}\n\n    name: 👀 Detect Changes\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - uses: dorny/paths-filter@v4\n        name: Angular Dart Package Detection\n        id: needs_angular_dart_example_checks\n        with:\n          filters: |\n            angular_counter:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/angular_dart_package/action.yaml\n              - examples/angular_counter/**\n            github_search/angular_github_search:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - examples/github_search/angular_github_search/**\n\n      - uses: dorny/paths-filter@v4\n        name: Bloc Tools E2E Detection\n        id: needs_bloc_tools_e2e_checks\n        with:\n          filters: |\n            bloc_tools:\n              - ./.github/workflows/main.yaml\n              - packages/bloc_tools/**\n\n      - uses: dorny/paths-filter@v4\n        name: Dart Package Detection\n        id: needs_dart_package_checks\n        with:\n          filters: |\n            angular_bloc:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/angular_bloc/**\n            bloc_concurrency:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/bloc_concurrency/**\n            bloc_lint:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/bloc_lint/**\n            bloc_test:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/bloc_test/**\n            bloc_tools:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/bloc_tools/**\n            bloc:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/dart_package/action.yaml\n              - packages/bloc/**\n\n      - uses: dorny/paths-filter@v4\n        name: Bloc Tools Compile Detection\n        id: needs_bloc_tools_compile_checks\n        with:\n          filters: |\n            bloc_tools:\n              - ./.github/actions/dart_compile/action.yaml\n              - ./.github/workflows/main.yaml\n              - packages/bloc_tools/**\n\n      - uses: dorny/paths-filter@v4\n        name: Flutter Package Detection\n        id: needs_flutter_package_checks\n        with:\n          filters: |\n            flutter_bloc:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - packages/flutter_bloc/**\n            replay_bloc:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - packages/replay_bloc/**\n            hydrated_bloc:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - packages/hydrated_bloc/**\n\n      - uses: dorny/paths-filter@v4\n        name: Example Detection\n        id: needs_flutter_example_checks\n        with:\n          filters: |\n            bloc_concurrency_visualizer:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/bloc_concurrency_visualizer/**\n            flutter_bloc_with_stream:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_bloc_with_stream/**\n            flutter_complex_list:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_complex_list/**\n            flutter_counter:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_counter/**\n            flutter_dynamic_form:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_dynamic_form/**\n            flutter_firebase_login:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_firebase_login/**\n            flutter_form_validation:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_form_validation/**\n            flutter_infinite_list:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_infinite_list/**\n            flutter_login:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_login/**\n            flutter_shopping_cart:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_shopping_cart/**\n            flutter_timer:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_timer/**\n            flutter_todos:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_todos/**\n            flutter_weather:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_weather/**\n            flutter_wizard:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/flutter_wizard/**\n            github_search/flutter_github_search:\n              - ./.github/codecov.yml\n              - ./.github/workflows/main.yaml\n              - ./.github/actions/flutter_package/action.yaml\n              - examples/github_search/flutter_github_search/**\n\n      - uses: dorny/paths-filter@v4\n        name: Docs Detection\n        id: needs_docs_checks\n        with:\n          filters: |\n            - ./.github/workflows/main.yaml\n            - ./.github/actions/astro_site/action.yaml\n            - examples/**\n            - docs/**\n\n  bloc_tools_e2e_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_bloc_tools_e2e_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"]\n        package: ${{ fromJSON(needs.changes.outputs.needs_bloc_tools_e2e_checks) }}\n\n    runs-on: ${{ matrix.os }}\n\n    name: 🧪 ${{ matrix.package }} (${{ matrix.os }})\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 🐦 Setup Flutter\n        uses: subosito/flutter-action@v2\n        with:\n          cache: true\n\n      - name: ⚙️ Setup Bloc Tools\n        run: dart pub global activate --source path ./packages/bloc_tools\n\n      - name: 📦 Install Dependencies\n        working-directory: packages/${{ matrix.package }}/e2e\n        run: flutter pub get\n\n      - name: 🧪 E2E\n        working-directory: packages/${{ matrix.package }}/e2e\n        run: dart run main.dart\n\n  dart_package_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_dart_package_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        package: ${{ fromJSON(needs.changes.outputs.needs_dart_package_checks) }}\n\n    runs-on: ubuntu-latest\n\n    name: 🎯 ${{ matrix.package }}\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 🎯 Build ${{ matrix.package }}\n        uses: ./.github/actions/dart_package\n        with:\n          codecov_token: ${{ secrets.CODECOV_TOKEN }}\n          working_directory: packages/${{ matrix.package }}\n          min_coverage: 100\n\n  bloc_tools_compile_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_bloc_tools_compile_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n          [\n            { \"runs-on\": \"ubuntu-latest\", \"os\": \"linux\", \"arch\": \"x64\" },\n            { \"runs-on\": \"ubuntu-latest\", \"os\": \"linux\", \"arch\": \"arm64\" },\n            { \"runs-on\": \"macos-15-intel\", \"os\": \"macos\", \"arch\": \"x64\" },\n            { \"runs-on\": \"macos-latest\", \"os\": \"macos\", \"arch\": \"arm64\" },\n            { \"runs-on\": \"windows-latest\", \"os\": \"windows\", \"arch\": \"x64\" },\n          ]\n\n    runs-on: ${{ matrix.config.runs-on }}\n\n    name: ⚙️ Compile Bloc Tools\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: ⚙️ Compile Bloc Tools for ${{ matrix.config.os }} ${{ matrix.config.arch }}\n        uses: ./.github/actions/dart_compile\n        with:\n          entrypoint: bin/bloc.dart\n          working_directory: packages/bloc_tools\n          name: bloc_${{ matrix.config.os }}_${{ matrix.config.arch }}\n          os: ${{ matrix.config.os }}\n          arch: ${{ matrix.config.arch }}\n\n  flutter_package_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_flutter_package_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        package: ${{ fromJSON(needs.changes.outputs.needs_flutter_package_checks) }}\n\n    runs-on: ubuntu-latest\n\n    name: 🐦 ${{ matrix.package }}\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 🎯 Build ${{ matrix.package }}\n        uses: ./.github/actions/flutter_package\n        with:\n          codecov_token: ${{ secrets.CODECOV_TOKEN }}\n          working_directory: packages/${{ matrix.package }}\n          min_coverage: 100\n\n  angular_dart_example_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_angular_dart_example_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        example: ${{ fromJSON(needs.changes.outputs.needs_angular_dart_example_checks) }}\n\n    runs-on: ubuntu-latest\n\n    name: 🛡️ ${{ matrix.example }}\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 🎯 Build ${{ matrix.example }}\n        uses: ./.github/actions/angular_dart_package\n        with:\n          analyze_directories: lib\n          working_directory: examples/${{ matrix.example }}\n\n  flutter_example_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_flutter_example_checks != '[]' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        example: ${{ fromJSON(needs.changes.outputs.needs_flutter_example_checks) }}\n\n    runs-on: ubuntu-latest\n\n    name: 🧑‍🎓 ${{ matrix.example }}\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 🎯 Build ${{ matrix.example }}\n        uses: ./.github/actions/flutter_package\n        with:\n          analyze_directories: lib\n          collect_coverage: false\n          collect_score: false\n          flutter_channel: master\n          working_directory: examples/${{ matrix.example }}\n\n  docs_checks:\n    needs: changes\n    if: ${{ needs.changes.outputs.needs_docs_checks != '[]' }}\n\n    runs-on: ubuntu-latest\n\n    name: 📖 docs\n\n    steps:\n      - name: 📚 Git Checkout\n        uses: actions/checkout@v6\n\n      - name: 📦 Build Docs\n        uses: ./.github/actions/astro_site\n        with:\n          working_directory: ./docs\n\n  deploy:\n    needs: docs_checks\n    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}\n\n    runs-on: ubuntu-latest\n\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    steps:\n      - name: 🚀 Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n\n  build:\n    needs:\n      [\n        angular_dart_example_checks,\n        bloc_tools_compile_checks,\n        bloc_tools_e2e_checks,\n        dart_package_checks,\n        docs_checks,\n        flutter_example_checks,\n        flutter_package_checks,\n        semantic_pull_request,\n      ]\n\n    if: ${{ always() }}\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: ⛔️ exit(1) on failure\n        if: ${{ contains(join(needs.*.result, ','), 'failure') }}\n        run: exit 1\n"
  },
  {
    "path": ".gitignore",
    "content": "# Miscellaneous\n*.class\n*.lock\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# Visual Studio Code related\n.classpath\n.project\n.settings/\n.vscode/*\n\n# Flutter repo-specific\n/bin/cache/\n/bin/mingit/\n/dev/benchmarks/mega_gallery/\n/dev/bots/.recipe_deps\n/dev/bots/android_tools/\n/dev/docs/doc/\n/dev/docs/flutter.docs.zip\n/dev/docs/lib/\n/dev/docs/pubspec.yaml\n/dev/integration_tests/**/xcuserdata\n/dev/integration_tests/**/Pods\n/packages/flutter/coverage/\nversion\n\n# packages file containing multi-root paths\n.packages.generated\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\nbuild/\nlinked_*.ds\nunlinked.ds\nunlinked_spec.ds\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n**/android/key.properties\n*.jks\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Flutter.podspec\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/flutter_export_environment.sh\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Coverage\ncoverage/\ncoverage_badge.svg\n.test_coverage.dart\n*.lcov\nnohup.out\n\n# Mason\nmason-lock.json\n.mason\n\n# Exceptions to above rules.\n!.vscode/launch.json\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n!/dev/ci/**/Gemfile.lock"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at felangelov@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Bloc\n\n👍🎉 First off, thanks for taking the time to contribute! 🎉👍\n\nThe following is a set of guidelines for contributing to Bloc and its packages.\nThese are mostly guidelines, not rules. Use your best judgment,\nand feel free to propose changes to this document in a pull request.\n\n## Proposing a Change\n\nIf you intend to change the public API, or make any non-trivial changes\nto the implementation, we recommend filing an issue.\nThis lets us reach an agreement on your proposal before you put significant\neffort into it.\n\nIf you’re only fixing a bug, it’s fine to submit a pull request right away\nbut we still recommend to file an issue detailing what you’re fixing.\nThis is helpful in case we don’t accept that specific fix but want to keep\ntrack of the issue.\n\n## Creating a Pull Request\n\nBefore creating a pull request please:\n\n1. Fork the repository and create your branch from `master`.\n1. Install all dependencies (`flutter pub get`).\n1. Squash your commits and ensure you have a meaningful commit message.\n1. If you’ve fixed a bug or added code that should be tested, add tests!\n   Pull Requests without 100% test coverage will not be approved.\n1. Ensure the test suite passes.\n1. If you've changed the public API, make sure to update/add documentation.\n1. Format your code (`dart format .`).\n1. Analyze your code (`dart analyze --fatal-infos --fatal-warnings .`).\n1. Create the Pull Request.\n1. Verify that all status checks are passing.\n\nWhile the prerequisites above must be satisfied prior to having your\npull request reviewed, the reviewer(s) may ask you to complete additional\ndesign work, tests, or other changes before your pull request can be ultimately\naccepted.\n\n## Contributing to Documentation\n\nIf you would like to contribute to the [documentation](https://bloclibrary.dev)\nplease follow the same process for \"Creating a Pull Request\" and double check\nthat your changes look good by running the docs locally.\n\n```sh\n# Change directories into docs\ncd ./docs\n\n# Install dependencies\nnpm install\n\n# Start the dev server\nnpm start\n\n# Navigate to http://localhost:4321 in your browser\n```\n\nIf you wish to add translations, ensure the locale is included in the [locales list](https://github.com/felangel/bloc/blob/8a714a6923a6480032319b45f461d1f9ccd025de/docs/astro.config.mjs#L7) and create the translated `mdx` file in the corresponding subdirectory within `docs/src/content/docs/<lang>`.\n\nRefer to [this pull request](https://github.com/felangel/bloc/pull/4084) for an example.\n\n## Adding an example\n\nExamples live in the `examples` folder.\n\nWhen you're adding an example, make sure to add CI checks for it in\n[main.yaml](https://github.com/felangel/bloc/blob/master/.github/workflows/main.yaml):\n\n- For a Flutter example, add it to the `folder` list in the `examples-flutter`\n  step.\n- For a web example, add it to the `folder` list in the `examples-web` step.\n- For a pure Dart example, add it to the `folder` list in the `examples-pure`\n  step.\n\n## Getting in Touch\n\nIf you want to just ask a question or get feedback on an idea you can post it\non [Discord](https://discord.gg/bloc).\n\n## License\n\nBy contributing to Bloc, you agree that your contributions will be licensed\nunder its [MIT license](LICENSE).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc.png\" height=\"100\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"http://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nA predictable state management library that helps implement the BLoC design pattern.\n\n| Package                                                                                    | Pub                                                                                                            |\n| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Overview\n\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams/bloc_architecture.png\" width=\"500\" alt=\"Bloc Architecture\"></img>\n\nThe goal of this library is to make it easy to separate _presentation_ from _business logic_, facilitating testability and reusability.\n\n## Documentation\n\n- [Official Documentation](https://bloclibrary.dev)\n- [Angular Bloc Package](https://github.com/felangel/bloc/tree/master/packages/angular_bloc/README.md)\n- [Bloc Package](https://github.com/felangel/bloc/tree/master/packages/bloc/README.md)\n- [Bloc Concurrency Package](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency/README.md)\n- [Bloc Lint Package](https://github.com/felangel/bloc/tree/master/packages/bloc_lint/README.md)\n- [Bloc Test Package](https://github.com/felangel/bloc/tree/master/packages/bloc_test/README.md)\n- [Bloc Tools Package](https://github.com/felangel/bloc/tree/master/packages/bloc_tools/README.md)\n- [Flutter Bloc Package](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc/README.md)\n- [Hydrated Bloc Package](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc/README.md)\n- [Replay Bloc Package](https://github.com/felangel/bloc/tree/master/packages/replay_bloc/README.md)\n\n## Migration\n\n- [Migration Guide](https://bloclibrary.dev/migration)\n\n## Examples\n\n<div style=\"text-align: center\">\n    <table>\n        <tr>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-counter\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_counter.gif\" width=\"200\"/>\n                </a>\n            </td>            \n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-infinite-list\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_infinite_list.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-login\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_firebase_login.gif\" width=\"200\" />\n                </a>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/github-search\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_github_search.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-weather\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_weather.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-todos\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_todos.gif\" width=\"200\"/>\n                </a>\n            </td>\n        </tr>\n    </table>\n</div>\n\n### Dart\n\n- [Counter](https://github.com/felangel/bloc/tree/master/packages/bloc/example) - an example of how to create a `CounterBloc` (pure dart).\n\n### Flutter\n\n- [Counter](https://bloclibrary.dev/tutorials/flutter-counter) - an example of how to create a `CounterBloc` to implement the classic Flutter Counter app.\n- [Form Validation](https://github.com/felangel/bloc/tree/master/examples/flutter_form_validation) - an example of how to use the `bloc` and `flutter_bloc` packages to implement form validation.\n- [Bloc with Stream](https://github.com/felangel/bloc/tree/master/examples/flutter_bloc_with_stream) - an example of how to hook up a `bloc` to a `Stream` and update the UI in response to data from the `Stream`.\n- [Complex List](https://github.com/felangel/bloc/tree/master/examples/flutter_complex_list) - an example of how to manage a list of items and asynchronously delete items one at a time using `bloc` and `flutter_bloc`.\n- [Infinite List](https://bloclibrary.dev/tutorials/flutter-infinite-list) - an example of how to use the `bloc` and `flutter_bloc` packages to implement an infinite scrolling list.\n- [Login Flow](https://bloclibrary.dev/tutorials/flutter-login) - an example of how to use the `bloc` and `flutter_bloc` packages to implement a Login Flow.\n- [Firebase Login](https://bloclibrary.dev/tutorials/flutter-firebase-login) - an example of how to use the `bloc` and `flutter_bloc` packages to implement login via Firebase.\n- [Github Search](https://bloclibrary.dev/tutorials/github-search) - an example of how to create a Github Search Application using the `bloc` and `flutter_bloc` packages.\n- [Weather](https://bloclibrary.dev/tutorials/flutter-weather) - an example of how to create a Weather Application using the `bloc` and `flutter_bloc` packages. The app uses a `RefreshIndicator` to implement \"pull-to-refresh\" as well as dynamic theming.\n- [Todos](https://bloclibrary.dev/tutorials/flutter-todos) - an example of how to create a Todos Application using the `bloc` and `flutter_bloc` packages.\n- [Timer](https://bloclibrary.dev/tutorials/flutter-timer) - an example of how to create a Timer using the `bloc` and `flutter_bloc` packages.\n- [Shopping Cart](https://github.com/felangel/bloc/tree/master/examples/flutter_shopping_cart) - an example of how to create a Shopping Cart Application using the `bloc` and `flutter_bloc` packages based on [flutter samples](https://github.com/flutter/samples/tree/master/provider_shopper).\n- [Dynamic Form](https://github.com/felangel/bloc/tree/master/examples/flutter_dynamic_form) - an example of how to use the `bloc` and `flutter_bloc` packages to implement a dynamic form which pulls data from a repository.\n- [Wizard](https://github.com/felangel/bloc/tree/master/examples/flutter_wizard) - an example of how to build a multi-step wizard using the `bloc` and `flutter_bloc` packages.\n- [Bloc Concurrency Visualizer](https://github.com/felangel/bloc/tree/master/examples/bloc_concurrency_visualizer) - an example of visualizing the various `bloc_concurrency` transformers.\n- [Fluttersaurus](https://github.com/felangel/fluttersaurus) - an example of how to use the `bloc` and `flutter_bloc` packages to create a thesaurus app -- made for Bytconf Flutter 2020.\n- [I/O Photo Booth](https://github.com/flutter/photobooth) - an example of how to use the `bloc` and `flutter_bloc` packages to create a virtual photo booth web app -- made for Google I/O 2021.\n- [I/O Pinball](https://github.com/flutter/pinball) - an example of how to use the `bloc` and `flutter_bloc` packages to create a pinball web app -- made for Google I/O 2022.\n- [I/O Holobooth](https://github.com/flutter/holobooth) - an example of how to use the `bloc` and `flutter_bloc` packages to create a virtual photobooth experience -- made for Flutter Forward.\n- [I/O Flip](https://github.com/flutter/io_flip) - an example of how to use the `bloc`, `flutter_bloc`, and `flame_bloc` packages to create a card game -- made for Google I/O 2023.\n\n### Web\n\n- [Counter](https://github.com/felangel/Bloc/tree/master/examples/angular_counter) - an example of how to use a `CounterBloc` in an AngularDart app.\n- [Github Search](https://github.com/felangel/Bloc/tree/master/examples/github_search/angular_github_search) - an example of how to create a Github Search Application using the `bloc` and `angular_bloc` packages.\n\n### Flutter + Web\n\n- [Github Search](https://github.com/felangel/Bloc/tree/master/examples/github_search) - an example of how to create a Github Search Application and share code between Flutter and AngularDart.\n\n## Articles\n\n- [Bloc package](https://medium.com/flutter-community/flutter-bloc-package-295b53e95c5c) - An intro to the bloc package with high level architecture and examples.\n- [Login tutorial with flutter_bloc](https://medium.com/flutter-community/flutter-login-tutorial-with-flutter-bloc-ea606ef701ad) - How to create a full login flow using the bloc and flutter_bloc packages.\n- [Unit testing with bloc](https://medium.com/@felangelov/unit-testing-with-bloc-b94de9655d86) - How to unit test the blocs created in the flutter login tutorial.\n- [Infinite list tutorial with flutter_bloc](https://medium.com/flutter-community/flutter-infinite-list-tutorial-with-flutter-bloc-2fc7a272ec67) - How to create an infinite list using the bloc and flutter_bloc packages.\n- [Code sharing with bloc](https://medium.com/flutter-community/code-sharing-with-bloc-b867302c18ef) - How to share code between a mobile application written with Flutter and a web application written with AngularDart.\n- [Weather app tutorial with flutter_bloc](https://medium.com/flutter-community/weather-app-with-flutter-bloc-e24a7253340d) - How to build a weather app which supports dynamic theming, pull-to-refresh, and interacting with a REST API using the bloc and flutter_bloc packages.\n- [Todos app tutorial with flutter_bloc](https://medium.com/flutter-community/flutter-todos-tutorial-with-flutter-bloc-d9dd833f9df3) - How to build a todos app using the bloc and flutter_bloc packages.\n- [Firebase login tutorial with flutter_bloc](https://medium.com/flutter-community/firebase-login-with-flutter-bloc-47455e6047b0) - How to create a fully functional login/sign up flow using the bloc and flutter_bloc packages with Firebase Authentication and Google Sign In.\n- [Flutter timer tutorial with flutter_bloc](https://medium.com/flutter-community/flutter-timer-with-flutter-bloc-a464e8332ceb) - How to create a timer app using the bloc and flutter_bloc packages.\n- [Firestore todos tutorial with flutter_bloc](https://medium.com/flutter-community/firestore-todos-with-flutter-bloc-7b2d5fadcc80) - How to create a todos app using the bloc and flutter_bloc packages that integrates with cloud firestore.\n\n## Books\n\n- [Flutter Complete Reference](https://fluttercompletereference.com/) - A book about the Dart programming language (version 2.10, with null safety support) and the Flutter framework (version 1.20). It covers the bloc package (version 6.0.3) in all flavors: bloc, flutter_bloc hydrated_bloc, replay_bloc, bloc_test and cubit.\n\n## Extensions\n\n- [IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) - extends IntelliJ/Android Studio with support for the Bloc library and provides tools for effectively creating Blocs for both Flutter and AngularDart apps.\n- [VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc#overview) - extends VSCode with support for the Bloc library and provides tools for effectively creating Blocs for both Flutter and AngularDart apps.\n\n## Community\n\nLearn more at the following links, which have been contributed by the community.\n\n### Packages\n\n- [Bloc.js](https://github.com/felangel/bloc.js) - A port of the `bloc` state management library from Dart to JavaScript, by [Felix Angelov](https://github.com/felangel).\n- [Firebase Auth](https://pub.dev/packages/fb_auth) - A Web, Mobile Firebase Auth Plugin, by [Rody Davis](https://github.com/AppleEducate).\n- [Form Bloc](https://pub.dev/packages/form_bloc) - An easy way to create forms with BLoC pattern without writing a lot of boilerplate code, by [Giancarlo](https://github.com/GiancarloCode).\n- [Flame Bloc](https://pub.dev/packages/flame_bloc) - Bloc integration for the Flame game engine, by [Flame Engine](https://github.com/flame-engine).\n\n### Video Tutorials\n\n- [Bloc Library: Basics and Beyond 🚀](https://youtu.be/knMvKPKBzGE) - Talk given at [Flutter Europe](https://fluttereurope.dev) about the basics of the bloc library, by [Felix Angelov](https://github.com/felangel).\n- [Flutter Bloc Library Tutorial](https://www.youtube.com/watch?v=hTExlt1nJZI) - Introduction to the Bloc Library, by [Reso Coder](https://resocoder.com).\n- [Flutter Youtube Search](https://www.youtube.com/watch?v=BJY8nuYUM7M) - How to build a Youtube Search app which interacts with an API using the bloc and flutter_bloc packages, by [Reso Coder](https://resocoder.com).\n- [Flutter Bloc - AUTOMATIC LOOKUP - v0.20 (and Up), Updated Tutorial](https://www.youtube.com/watch?v=_vOpPuVfmiU) - Updated Tutorial on the Flutter Bloc Package, by [Reso Coder](https://resocoder.com).\n- [Dynamic Theming with flutter_bloc](https://www.youtube.com/watch?v=YYbhkg-W8Mg) - Tutorial on how to use the flutter_bloc package to implement dynamic theming, by [Reso Coder](https://resocoder.com).\n- [Persist Bloc State in Flutter](https://www.youtube.com/watch?v=vSOpZd_FFEY) - Tutorial on how to use the hydrated_bloc package to automatically persist app state, by [Reso Coder](https://resocoder.com).\n- [State Management Foundation](https://www.youtube.com/watch?v=S2KmxzgsTwk&t=731s) - Introduction to state management using the flutter_bloc package, by [Techie Blossom](https://techieblossom.com).\n- [Flutter Football Player Search](https://www.youtube.com/watch?v=S2KmxzgsTwk) - How to build a Football Player Search app which interacts with an API using the bloc and flutter_bloc packages, by [Techie Blossom](https://techieblossom.com).\n- [Learning the Flutter Bloc Package](https://www.youtube.com/watch?v=eAiCPl3yk9A&t=1s) - Learning the flutter_bloc package live, by [Robert Brunhage](https://www.youtube.com/channel/UCSLIg5O0JiYO1i2nD4RclaQ)\n- [Bloc Test Tutorial](https://www.youtube.com/watch?v=S6jFBiiP0Mc) - Tutorial on how to unit test blocs using the bloc_test package, by [Reso Coder](https://resocoder.com).\n- [Bloc - from Zero to Hero](https://www.youtube.com/playlist?list=PLptHs0ZDJKt_T-oNj_6Q98v-tBnVf-S_o) - Playlist which includes everything needed to get started with bloc, by [Flutterly](https://www.youtube.com/channel/UC5PYcSe3to4mtm3SPCUmjvw).\n- [Bloc (Full Course, 11+ Hours) - Flutter State Management Course](https://youtu.be/Mn254cnduOY) - 11+ hour video tutorial on Bloc and Flutter Bloc. In this video you will learn how to create fully fledged production-ready apps with Bloc and Firebase as your backend, by [Vandad Nahavandipoor](https://www.youtube.com/channel/UC8NpGP0AOQ0kX9ZRcohiPeQ).\n\n### Written Resources\n\n- [DevonFw Flutter Guide](https://github.com/devonfw-forge/devonfw4flutter) - A guide on building structured & scalable applications with Flutter and BLoC, by [Sebastian Faust](https://github.com/Fasust)\n- [Using Google´s Flutter Framework for the Development of a Large-Scale Reference Application](https://epb.bibl.th-koeln.de/frontdoor/index/index/docId/1498) - Scientific paper describing how to build [a large-scale Flutter application](https://github.com/devonfw-forge/devonfw4flutter-mts-app) with BLoC, by [Sebastian Faust](https://github.com/Fasust)\n\n### Extensions\n\n- [Feature Scaffolding for VSCode](https://marketplace.visualstudio.com/items?itemName=KiritchoukC.flutter-clean-architecture) - A VSCode extension inspired by [Reso Coder's](https://resocoder.com) clean architecture tutorials, which helps quickly scaffold features, by [Kiritchouk Clément](https://github.com/KiritchoukC).\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "analysis_options.yaml",
    "content": "analyzer:\n  exclude:\n    - bricks/**\n\n  language:\n    strict-casts: true\n    strict-inference: true\n    strict-raw-types: true\n\n  errors:\n    close_sinks: ignore\n    missing_required_param: error\n    missing_return: error\n    record_literal_one_positional_no_trailing_comma: error\n\nformatter:\n  page_width: 80\n  trailing_commas: preserve\n\nlinter:\n  rules:\n    - always_declare_return_types\n    - always_put_required_named_parameters_first\n    - always_use_package_imports\n    - annotate_overrides\n    - annotate_redeclares\n    - avoid_bool_literals_in_conditional_expressions\n    - avoid_catching_errors\n    - avoid_double_and_int_checks\n    - avoid_dynamic_calls\n    - avoid_empty_else\n    - avoid_equals_and_hash_code_on_mutable_classes\n    - avoid_escaping_inner_quotes\n    - avoid_field_initializers_in_const_classes\n    - avoid_final_parameters\n    - avoid_function_literals_in_foreach_calls\n    - avoid_init_to_null\n    - avoid_js_rounded_ints\n    - avoid_multiple_declarations_per_line\n    - avoid_null_checks_in_equality_operators\n    - avoid_positional_boolean_parameters\n    - avoid_print\n    - avoid_private_typedef_functions\n    - avoid_redundant_argument_values\n    - avoid_relative_lib_imports\n    - avoid_renaming_method_parameters\n    - avoid_return_types_on_setters\n    - avoid_returning_null_for_void\n    - avoid_returning_this\n    - avoid_setters_without_getters\n    - avoid_shadowing_type_parameters\n    - avoid_single_cascade_in_expression_statements\n    - avoid_slow_async_io\n    - avoid_type_to_string\n    - avoid_types_as_parameter_names\n    - avoid_unnecessary_containers\n    - avoid_unused_constructor_parameters\n    - avoid_void_async\n    - avoid_web_libraries_in_flutter\n    - await_only_futures\n    - camel_case_extensions\n    - camel_case_types\n    - cancel_subscriptions\n    - cascade_invocations\n    - cast_nullable_to_non_nullable\n    - collection_methods_unrelated_type\n    - combinators_ordering\n    - comment_references\n    - conditional_uri_does_not_exist\n    - constant_identifier_names\n    - control_flow_in_finally\n    - curly_braces_in_flow_control_structures\n    - dangling_library_doc_comments\n    - depend_on_referenced_packages\n    - deprecated_consistency\n    - deprecated_member_use_from_same_package\n    - directives_ordering\n    - do_not_use_environment\n    - empty_catches\n    - empty_constructor_bodies\n    - empty_statements\n    - eol_at_end_of_file\n    - exhaustive_cases\n    - file_names\n    - flutter_style_todos\n    - hash_and_equals\n    - implementation_imports\n    - implicit_call_tearoffs\n    - implicit_reopen\n    - invalid_case_patterns\n    - join_return_with_assignment\n    - leading_newlines_in_multiline_strings\n    - library_annotations\n    - library_names\n    - library_prefixes\n    - library_private_types_in_public_api\n    - lines_longer_than_80_chars\n    - literal_only_boolean_expressions\n    - matching_super_parameters\n    - missing_code_block_language_in_doc_comment\n    - missing_whitespace_between_adjacent_strings\n    - no_adjacent_strings_in_list\n    - no_default_cases\n    - no_duplicate_case_values\n    - no_leading_underscores_for_library_prefixes\n    - no_leading_underscores_for_local_identifiers\n    - no_literal_bool_comparisons\n    - no_logic_in_create_state\n    - no_runtimeType_toString\n    - no_self_assignments\n    - no_wildcard_variable_uses\n    - non_constant_identifier_names\n    - noop_primitive_operations\n    - null_check_on_nullable_type_parameter\n    - null_closures\n    - omit_local_variable_types\n    - omit_obvious_local_variable_types\n    - one_member_abstracts\n    - only_throw_errors\n    - overridden_fields\n    - package_api_docs\n    - package_names\n    - package_prefixed_library_names\n    - parameter_assignments\n    - prefer_adjacent_string_concatenation\n    - prefer_asserts_in_initializer_lists\n    - prefer_asserts_with_message\n    - prefer_collection_literals\n    - prefer_conditional_assignment\n    - prefer_const_constructors\n    - prefer_const_constructors_in_immutables\n    - prefer_const_declarations\n    - prefer_const_literals_to_create_immutables\n    - prefer_constructors_over_static_methods\n    - prefer_contains\n    - prefer_final_fields\n    - prefer_final_in_for_each\n    - prefer_final_locals\n    - prefer_for_elements_to_map_fromIterable\n    - prefer_foreach\n    - prefer_function_declarations_over_variables\n    - prefer_generic_function_type_aliases\n    - prefer_if_elements_to_conditional_expressions\n    - prefer_if_null_operators\n    - prefer_initializing_formals\n    - prefer_inlined_adds\n    - prefer_int_literals\n    - prefer_interpolation_to_compose_strings\n    - prefer_is_empty\n    - prefer_is_not_empty\n    - prefer_is_not_operator\n    - prefer_iterable_whereType\n    - prefer_mixin\n    - prefer_null_aware_method_calls\n    - prefer_null_aware_operators\n    - prefer_single_quotes\n    - prefer_spread_collections\n    - prefer_typing_uninitialized_variables\n    - prefer_void_to_null\n    - provide_deprecation_message\n    - public_member_api_docs\n    - recursive_getters\n    - require_trailing_commas\n    - secure_pubspec_urls\n    - sized_box_for_whitespace\n    - sized_box_shrink_expand\n    - slash_for_doc_comments\n    - sort_child_properties_last\n    - sort_constructors_first\n    - sort_pub_dependencies\n    - sort_unnamed_constructors_first\n    - strict_top_level_inference\n    - switch_on_type\n    - test_types_in_equals\n    - throw_in_finally\n    - tighten_type_of_initializing_formals\n    - type_annotate_public_apis\n    - type_init_formals\n    - type_literal_in_constant_pattern\n    - unawaited_futures\n    - unnecessary_await_in_return\n    - unnecessary_brace_in_string_interps\n    - unnecessary_breaks\n    - unnecessary_const\n    - unnecessary_constructor_name\n    - unnecessary_getters_setters\n    - unnecessary_ignore\n    - unnecessary_lambdas\n    - unnecessary_late\n    - unnecessary_library_directive\n    - unnecessary_new\n    - unnecessary_null_aware_assignments\n    - unnecessary_null_aware_operator_on_extension_on_nullable\n    - unnecessary_null_checks\n    - unnecessary_null_in_if_null_operators\n    - unnecessary_nullable_for_final_variable_declarations\n    - unnecessary_overrides\n    - unnecessary_parenthesis\n    - unnecessary_raw_strings\n    - unnecessary_statements\n    - unnecessary_string_escapes\n    - unnecessary_string_interpolations\n    - unnecessary_this\n    - unnecessary_to_list_in_spreads\n    - unnecessary_unawaited\n    - unnecessary_underscores\n    - unreachable_from_main\n    - unrelated_type_equality_checks\n    - unsafe_html\n    - use_build_context_synchronously\n    - use_colored_box\n    - use_decorated_box\n    - use_enums\n    - use_full_hex_values_for_flutter_colors\n    - use_function_type_syntax_for_parameters\n    - use_if_null_to_convert_nulls_to_bools\n    - use_is_even_rather_than_modulo\n    - use_key_in_widget_constructors\n    - use_late_for_private_fields_and_variables\n    - use_named_constants\n    - use_null_aware_elements\n    - use_raw_strings\n    - use_rethrow_when_possible\n    - use_setters_to_change_properties\n    - use_string_buffers\n    - use_string_in_part_of_directives\n    - use_super_parameters\n    - use_test_throws_matchers\n    - use_to_and_as_if_applicable\n    - use_truncating_division\n    - valid_regexps\n    - void_checks\n"
  },
  {
    "path": "bricks/README.md",
    "content": "# Bloc Bricks 🧱\n\n[Mason](https://github.com/felangel/mason) support for the [Bloc Library](https://bloclibrary.dev). A collection of bricks for effectively working with the bloc state management library.\n\n## Installation\n\nEnsure you have the [mason_cli](https://github.com/felangel/mason/tree/master/packages/mason_cli) installed. Bricks can be installed from [brickhub.dev](https://brickhub.dev).\n\n```sh\nmason add <BRICK>\n```\n\n## Bricks\n\n| Brick                  | Description                              |\n| ---------------------- | ---------------------------------------- |\n| `bloc`                 | Generate a new Bloc                      |\n| `cubit`                | Generate a new Cubit                     |\n| `hydrated_bloc`        | Generate a new HydratedBloc              |\n| `hydrated_cubit`       | Generate a new HydratedCubit             |\n| `replay_bloc`          | Generate a new ReplayBloc                |\n| `replay_cubit`         | Generate a new ReplayCubit               |\n| `flutter_bloc_feature` | Generate a new Flutter feature with bloc |\n"
  },
  {
    "path": "bricks/bloc/CHANGELOG.md",
    "content": "# 0.4.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.3.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.3.0\n\n- feat: add support for `sealed` events using Dart 3\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.3+2\n\n- docs: use dark logo variant\n\n# 0.1.3+1\n\n- docs: adjust logo size in README\n\n# 0.1.3\n\n- docs: add badges to README\n\n# 0.1.2\n\n- docs: minor README update\n\n# 0.1.1\n\n- refactor: upgrade to shorthand lambda syntax (`mason >= 0.1.0-dev.15`)\n\n# 0.1.0\n\n- feat: initial release with support for basic bloc generation\n"
  },
  {
    "path": "bricks/bloc/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/bloc/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_dark.png\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new Bloc in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make bloc --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                 | Default                             | Type     |\n| -------- | --------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the bloc class  | `counter`                           | `string` |\n| `style`  | The style of bloc generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_bloc.dart\n├── counter_event.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{name.snakeCase()}}_bloc.dart",
    "content": "{{#use_freezed}}{{> freezed_bloc }}{{/use_freezed}}{{#use_equatable}}{{> equatable_bloc }}{{/use_equatable}}{{#use_basic}}{{> basic_bloc }}{{/use_basic}}"
  },
  {
    "path": "bricks/bloc/__brick__/{{name.snakeCase()}}_event.dart",
    "content": "{{#use_freezed}}{{> freezed_event }}{{/use_freezed}}{{#use_equatable}}{{> equatable_event }}{{/use_equatable}}{{#use_basic}}{{> basic_event }}{{/use_basic}}"
  },
  {
    "path": "bricks/bloc/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ basic_bloc }}",
    "content": "import 'package:bloc/bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends Bloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ basic_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nsealed class {{name.pascalCase()}}Event {\n  const {{name.pascalCase()}}Event();\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ equatable_bloc }}",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends Bloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ equatable_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nsealed class {{name.pascalCase()}}Event extends Equatable {\n  const {{name.pascalCase()}}Event();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ freezed_bloc }}",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_bloc.freezed.dart';\n\nclass {{name.pascalCase()}}Bloc extends Bloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State.initial()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ freezed_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}Event with _${{name.pascalCase()}}Event {\n  const factory {{name.pascalCase()}}Event.started() = _Started;\n}\n"
  },
  {
    "path": "bricks/bloc/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/bloc/brick.yaml",
    "content": "name: bloc\ndescription: Generate a new Bloc in Dart. Built for the bloc state management library.\nversion: 0.4.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/bloc\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the bloc class.\n    default: counter\n    prompt: What is the bloc name?\n  style:\n    type: enum\n    description: The style of bloc generated.\n    default: basic\n    prompt: What is the bloc style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/bloc/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/bloc/hooks/pubspec.yaml",
    "content": "name: bloc_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "bricks/cubit/CHANGELOG.md",
    "content": "# 0.3.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.2.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.3\n\n- docs: add badges to README\n- docs: use dark logo variant\n\n# 0.1.2\n\n- docs: minor README update\n\n# 0.1.1\n\n- refactor: upgrade to shorthand lambda syntax (`mason >= 0.1.0-dev.15`)\n\n# 0.1.0\n\n- feat: initial release with support for basic cubit generation\n"
  },
  {
    "path": "bricks/cubit/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/cubit/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/cubit_dark.png\" alt=\"Cubit\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new Cubit in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make cubit --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                  | Default                             | Type     |\n| -------- | ---------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the cubit class  | `counter`                           | `string` |\n| `style`  | The style of cubit generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_cubit.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{name.snakeCase()}}_cubit.dart",
    "content": "{{#use_freezed}}{{> freezed_cubit }}{{/use_freezed}}{{#use_equatable}}{{> equatable_cubit }}{{/use_equatable}}{{#use_basic}}{{> basic_cubit }}{{/use_basic}}"
  },
  {
    "path": "bricks/cubit/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ basic_cubit }}",
    "content": "import 'package:bloc/bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends Cubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n}\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ equatable_cubit }}",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends Cubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n}\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ freezed_cubit }}",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_cubit.freezed.dart';\n\nclass {{name.pascalCase()}}Cubit extends Cubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State.initial());\n}\n"
  },
  {
    "path": "bricks/cubit/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/cubit/brick.yaml",
    "content": "name: cubit\ndescription: Generate a new Cubit in Dart. Built for the bloc state management library.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/cubit\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the cubit class.\n    default: counter\n    prompt: Please enter the cubit name.\n  style:\n    type: enum\n    description: The style of cubit generated.\n    default: basic\n    prompt: What is the cubit style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/cubit/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/cubit/hooks/pubspec.yaml",
    "content": "name: cubit_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/CHANGELOG.md",
    "content": "# 0.4.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.3.2\n\n- deps: upgrade to mason v0.1.0-dev.57\n\n# 0.3.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.3.0\n\n- feat: upgrade to Dart 3.0\n  - use `sealed` classes for bloc events\n\n# 0.2.1\n\n- fix: upgrade to mason v0.1.0-dev.40\n  - deps: remove dependency on `package:recase`\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.0+1\n\n- feat: initial release 🎉\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_dark.png\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new Flutter feature with bloc. Built for the [bloc state management library][1].\n\n## Usage 🚀\n\n```sh\nmason make flutter_bloc_feature --name counter --type bloc --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                 | Default                             | Type     |\n| -------- | --------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the feature     | `counter`                           | `string` |\n| `type`   | The type of the bloc        | `bloc`                              | `enum`   |\n| `style`  | The style of bloc generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n── counter\n│   ├── bloc\n│   │   ├── counter_bloc.dart\n│   │   ├── counter_event.dart\n│   │   └── counter_state.dart\n│   ├── counter.dart\n│   └── view\n│       ├── counter_page.dart\n│       └── view.dart\n```\n\n[1]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{name.snakeCase()}}/view/view.dart",
    "content": "export './{{name.snakeCase()}}_page.dart';\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{name.snakeCase()}}/view/{{name.snakeCase()}}_page.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '../{{name.snakeCase()}}.dart';\n\nclass {{name.pascalCase()}}Page extends StatelessWidget {\n  const {{name.pascalCase()}}Page({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n{{#is_bloc}}{{> bloc_provider }}{{/is_bloc}}{{^is_bloc}}{{> cubit_bloc_provider }}{{/is_bloc}}\n  }\n}\n\nclass {{name.pascalCase()}}View extends StatelessWidget {\n  const {{name.pascalCase()}}View({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n{{#is_bloc}}{{> bloc_builder }}{{/is_bloc}}{{^is_bloc}}{{> cubit_bloc_builder }}{{/is_bloc}}\n  }\n}\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{name.snakeCase()}}/{{name.snakeCase()}}.dart",
    "content": "export '{{{bloc_export}}}';\nexport './view/view.dart';\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{~ bloc_builder }}",
    "content": "    return BlocBuilder<{{name.pascalCase()}}Bloc, {{name.pascalCase()}}State>(\n      builder: (context, state) {\n        // TODO: return correct widget based on the state.\n        return const SizedBox();\n      },\n    );"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{~ bloc_provider }}",
    "content": "    return BlocProvider(\n      create: (_) => {{name.pascalCase()}}Bloc(),\n      child: const {{name.pascalCase()}}View(),\n    );"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{~ cubit_bloc_builder }}",
    "content": "    return BlocBuilder<{{name.pascalCase()}}Cubit, {{name.pascalCase()}}State>(\n      builder: (context, state) {\n        // TODO: return correct widget based on the state.\n        return const SizedBox();\n      },\n    );"
  },
  {
    "path": "bricks/flutter_bloc_feature/__brick__/{{~ cubit_bloc_provider }}",
    "content": "    return BlocProvider(\n      create: (_) => {{name.pascalCase()}}Cubit(),\n      child: const {{name.pascalCase()}}View(),\n    );"
  },
  {
    "path": "bricks/flutter_bloc_feature/brick.yaml",
    "content": "name: flutter_bloc_feature\ndescription: Generate a new Flutter feature with bloc. Built for the bloc state management library.\nrepository: https://github.com/felangel/bloc/tree/master/bricks/flutter_bloc_feature\nversion: 0.4.0\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the feature\n    default: counter\n    prompt: What is your feature called?\n  type:\n    type: enum\n    description: The type of bloc used.\n    default: bloc\n    prompt: What type of bloc do you want to use?\n    values:\n      - bloc\n      - cubit\n      - hydrated_bloc\n      - hydrated_cubit\n      - replay_bloc\n      - replay_cubit\n  style:\n    type: enum\n    description: The style of bloc generated.\n    default: basic\n    prompt: What is the bloc style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/hooks/.gitignore",
    "content": ".dart_tool\n.packages\npubspec.lock\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/hooks/post_gen.dart",
    "content": "import 'dart:io';\n\nimport 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  await _runDartFormat(context);\n  await _runDartFix(context);\n}\n\nFuture<void> _runDartFormat(HookContext context) async {\n  final formatProgress = context.logger.progress('Running \"dart format .\"');\n  await Process.run('dart', ['format', '.']);\n  formatProgress.complete();\n}\n\nFuture<void> _runDartFix(HookContext context) async {\n  final formatProgress = context.logger.progress('Running \"dart fix --apply\"');\n  await Process.run('dart', ['fix', '--apply']);\n  formatProgress.complete();\n}\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/hooks/pre_gen.dart",
    "content": "// ignore_for_file: constant_identifier_names\n\nimport 'dart:io';\n\nimport 'package:mason/mason.dart';\nimport 'package:path/path.dart' as path;\n\nenum BlocType {\n  bloc,\n  cubit,\n  hydrated_bloc,\n  hydrated_cubit,\n  replay_bloc,\n  replay_cubit,\n}\n\nfinal brickVersions = {\n  BlocType.bloc: '^0.4.0',\n  BlocType.cubit: '^0.3.0',\n  BlocType.hydrated_bloc: '^0.4.0',\n  BlocType.hydrated_cubit: '^0.3.0',\n  BlocType.replay_bloc: '^0.3.0',\n  BlocType.replay_cubit: '^0.3.0',\n};\n\nFuture<void> run(HookContext context) async {\n  final blocType = _blocTypeFromContext(context);\n  final progress = context.logger.progress('Making brick ${blocType.name}');\n  final name = context.vars['name'] as String;\n  final style = context.vars['style'] as String;\n  final brick = Brick.version(\n    name: blocType.name,\n    version: brickVersions[blocType]!,\n  );\n  final generator = await MasonGenerator.fromBrick(brick);\n  final blocDirectoryName = blocType.toDirectoryName();\n  final directory = Directory(\n    path.join(Directory.current.path, name.snakeCase, blocDirectoryName),\n  );\n  final target = DirectoryGeneratorTarget(directory);\n  var vars = <String, dynamic>{'name': name, 'style': style};\n  await generator.hooks.preGen(vars: vars, onVarsChanged: (v) => vars = v);\n  final files = await generator.generate(\n    target,\n    vars: vars,\n    logger: context.logger,\n    fileConflictResolution: FileConflictResolution.overwrite,\n  );\n  await generator.hooks.postGen(vars: vars);\n  final blocExport =\n      './$blocDirectoryName/${name.snakeCase}_$blocDirectoryName.dart';\n  progress.complete('Made brick ${blocType.name}');\n  context.logger.logFilesGenerated(files.length);\n  context.vars = {\n    ...context.vars,\n    'bloc_export': blocExport,\n    'is_bloc': blocDirectoryName == 'bloc',\n  };\n}\n\nBlocType _blocTypeFromContext(HookContext context) {\n  final type = context.vars['type'] as String;\n  switch (type) {\n    case 'cubit':\n      return BlocType.cubit;\n    case 'hydrated_bloc':\n      return BlocType.hydrated_bloc;\n    case 'hydrated_cubit':\n      return BlocType.hydrated_cubit;\n    case 'replay_bloc':\n      return BlocType.replay_bloc;\n    case 'replay_cubit':\n      return BlocType.replay_cubit;\n    case 'bloc':\n    default:\n      return BlocType.bloc;\n  }\n}\n\nextension on BlocType {\n  String toDirectoryName() {\n    switch (this) {\n      case BlocType.bloc:\n      case BlocType.hydrated_bloc:\n      case BlocType.replay_bloc:\n        return 'bloc';\n      case BlocType.cubit:\n      case BlocType.hydrated_cubit:\n      case BlocType.replay_cubit:\n        return 'cubit';\n    }\n  }\n}\n\nextension on Logger {\n  void logFilesGenerated(int fileCount) {\n    if (fileCount == 1) {\n      this\n        ..info(\n          '${lightGreen.wrap('\\u2713')} '\n          'Generated $fileCount file:',\n        )\n        ..flush((message) => info(darkGray.wrap(message)));\n    } else {\n      this\n        ..info(\n          '${lightGreen.wrap('\\u2713')} '\n          'Generated $fileCount file(s):',\n        )\n        ..flush((message) => info(darkGray.wrap(message)));\n    }\n  }\n}\n"
  },
  {
    "path": "bricks/flutter_bloc_feature/hooks/pubspec.yaml",
    "content": "name: flutter_bloc_feature_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n  path: ^1.8.2\n"
  },
  {
    "path": "bricks/hydrated_bloc/CHANGELOG.md",
    "content": "# 0.4.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.3.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.3.0\n\n- feat: add support for `sealed` events using Dart 3\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.2\n\n- docs: add badges to README\n- docs: use dark logo variant\n\n# 0.1.1\n\n- docs: minor README update\n\n# 0.1.0\n\n- feat: initial release with support for basic hydrated bloc generation\n"
  },
  {
    "path": "bricks/hydrated_bloc/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/hydrated_bloc/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/hydrated_bloc_dark.png\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new HydratedBloc in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make hydrated_bloc --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                 | Default                             | Type     |\n| -------- | --------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the bloc class  | `counter`                           | `string` |\n| `style`  | The style of bloc generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_bloc.dart\n├── counter_event.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{name.snakeCase()}}_bloc.dart",
    "content": "{{#use_freezed}}{{> freezed_bloc }}{{/use_freezed}}{{#use_equatable}}{{> equatable_bloc }}{{/use_equatable}}{{#use_basic}}{{> basic_bloc }}{{/use_basic}}"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{name.snakeCase()}}_event.dart",
    "content": "{{#use_freezed}}{{> freezed_event }}{{/use_freezed}}{{#use_equatable}}{{> equatable_event }}{{/use_equatable}}{{#use_basic}}{{> basic_event }}{{/use_basic}}"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ basic_bloc }}",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends HydratedBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ basic_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nsealed class {{name.pascalCase()}}Event {\n  const {{name.pascalCase()}}Event();\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ equatable_bloc }}",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends HydratedBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ equatable_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nsealed class {{name.pascalCase()}}Event extends Equatable {\n  const {{name.pascalCase()}}Event();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ freezed_bloc }}",
    "content": "import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_bloc.freezed.dart';\n\nclass {{name.pascalCase()}}Bloc extends HydratedBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State.initial()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ freezed_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}Event with _${{name.pascalCase()}}Event {\n  const factory {{name.pascalCase()}}Event.started() = _Started;\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/brick.yaml",
    "content": "name: hydrated_bloc\ndescription: Generate a new HydratedBloc in Dart. Built for the bloc state management library.\nversion: 0.4.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/hydrated_bloc\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the bloc class.\n    default: counter\n    prompt: Please enter the bloc name.\n  style:\n    type: enum\n    description: The style of bloc generated.\n    default: basic\n    prompt: What is the bloc style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/hydrated_bloc/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/hydrated_bloc/hooks/pubspec.yaml",
    "content": "name: hydrated_bloc_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "bricks/hydrated_cubit/CHANGELOG.md",
    "content": "# 0.3.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.2.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.3\n\n- fix: part and imports\n\n# 0.1.2\n\n- docs: add badges to README\n- docs: use dark logo variant\n\n# 0.1.1\n\n- docs: minor README update\n\n# 0.1.0\n\n- feat: initial release with support for basic hydrated cubit generation\n"
  },
  {
    "path": "bricks/hydrated_cubit/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/hydrated_cubit/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/hydrated_cubit_dark.png\" alt=\"Hydrated Cubit\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new HydratedCubit in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make hydrated_cubit --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                  | Default                             | Type     |\n| -------- | ---------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the cubit class  | `counter`                           | `string` |\n| `style`  | The style of cubit generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_cubit.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{name.snakeCase()}}_cubit.dart",
    "content": "{{#use_freezed}}{{> freezed_cubit }}{{/use_freezed}}{{#use_equatable}}{{> equatable_cubit }}{{/use_equatable}}{{#use_basic}}{{> basic_cubit }}{{/use_basic}}"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ basic_cubit }}",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends HydratedCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ equatable_cubit }}",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends HydratedCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ freezed_cubit }}",
    "content": "import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_cubit.freezed.dart';\n\nclass {{name.pascalCase()}}Cubit extends HydratedCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State.initial());\n\n  @override\n  Map<String, dynamic> toJson({{name.pascalCase()}}State state) {\n    // TODO: implement toJson\n  }\n\n  @override\n  {{name.pascalCase()}}State fromJson(Map<String, dynamic> json) {\n    // TODO: implement fromJson\n  }\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/brick.yaml",
    "content": "name: hydrated_cubit\ndescription: Generate a new HydratedCubit in Dart. Built for the bloc state management library.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/hydrated_cubit\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the cubit class.\n    default: counter\n    prompt: Please enter the cubit name.\n  style:\n    type: enum\n    description: The style of cubit generated.\n    default: basic\n    prompt: What is the cubit style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/hydrated_cubit/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/hydrated_cubit/hooks/pubspec.yaml",
    "content": "name: hydrated_cubit_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "bricks/mason.yaml",
    "content": "bricks:\n  bloc:\n    path: ./bloc\n  cubit:\n    path: ./cubit\n  hydrated_bloc:\n    path: ./hydrated_bloc\n  hydrated_cubit:\n    path: ./hydrated_cubit\n  replay_bloc:\n    path: ./replay_bloc\n  replay_cubit:\n    path: ./replay_cubit\n  flutter_bloc_feature:\n    path: ./flutter_bloc_feature"
  },
  {
    "path": "bricks/replay_bloc/CHANGELOG.md",
    "content": "# 0.3.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.2.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.3\n\n- fix: add missing `extends ReplayEvent`\n\n# 0.1.2\n\n- docs: add badges to README\n- docs: use dark logo variant\n\n# 0.1.1\n\n- docs: minor README update\n\n# 0.1.0\n\n- feat: initial release with support for basic replay bloc generation\n"
  },
  {
    "path": "bricks/replay_bloc/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/replay_bloc/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/replay_bloc_dark.png\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new ReplayBloc in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make replay_bloc --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                 | Default                             | Type     |\n| -------- | --------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the bloc class  | `counter`                           | `string` |\n| `style`  | The style of bloc generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_bloc.dart\n├── counter_event.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{name.snakeCase()}}_bloc.dart",
    "content": "{{#use_freezed}}{{> freezed_bloc }}{{/use_freezed}}{{#use_equatable}}{{> equatable_bloc }}{{/use_equatable}}{{#use_basic}}{{> basic_bloc }}{{/use_basic}}"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{name.snakeCase()}}_event.dart",
    "content": "{{#use_freezed}}{{> freezed_event }}{{/use_freezed}}{{#use_equatable}}{{> equatable_event }}{{/use_equatable}}{{#use_basic}}{{> basic_event }}{{/use_basic}}"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ basic_bloc }}",
    "content": "import 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends ReplayBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ basic_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nabstract class {{name.pascalCase()}}Event extends ReplayEvent {\n  const {{name.pascalCase()}}Event();\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ equatable_bloc }}",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Bloc extends ReplayBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ equatable_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nabstract class {{name.pascalCase()}}Event extends ReplayEvent with EquatableMixin {\n  const {{name.pascalCase()}}Event();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ freezed_bloc }}",
    "content": "import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_event.dart';\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_bloc.freezed.dart';\n\nclass {{name.pascalCase()}}Bloc extends ReplayBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Bloc() : super(const {{name.pascalCase()}}State.initial()) {\n    on<{{name.pascalCase()}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ freezed_event }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}Event extends ReplayEvent with _${{name.pascalCase()}}Event {\n  const factory {{name.pascalCase()}}Event.started() = _Started;\n}\n"
  },
  {
    "path": "bricks/replay_bloc/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_bloc.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/replay_bloc/brick.yaml",
    "content": "name: replay_bloc\ndescription: Generate a new ReplayBloc in Dart. Built for the bloc state management library.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/replay_bloc\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the bloc class.\n    default: counter\n    prompt: Please enter the bloc name.\n  style:\n    type: enum\n    description: The style of bloc generated.\n    default: basic\n    prompt: What is the bloc style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/replay_bloc/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/replay_bloc/hooks/pubspec.yaml",
    "content": "name: replay_bloc_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "bricks/replay_cubit/CHANGELOG.md",
    "content": "# 0.3.0\n\n- chore(deps): upgrade to `mason ^0.1.0`\n- chore(deps): upgrade hooks to `dart ^3.5.4`\n\n# 0.2.1\n\n- chore: update copyright year\n- chore: update logo image refs\n\n# 0.2.0\n\n- feat: add support for `equatable`\n- feat: add support for `freezed`\n\n# 0.1.3\n\n- fix: part and imports\n\n# 0.1.2\n\n- docs: add badges to README\n- docs: use dark logo variant\n\n# 0.1.1\n\n- docs: minor README update\n\n# 0.1.0\n\n- feat: initial release with support for basic replay cubit generation\n"
  },
  {
    "path": "bricks/replay_cubit/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bricks/replay_cubit/README.md",
    "content": "<p align=\"center\">\n<img style=\"height:100px\" src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/replay_cubit_dark.png\" alt=\"Replay Cubit\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n<a href=\"https://github.com/felangel/mason\"><img src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge\" alt=\"Powered by Mason\"></a>\n</p>\n\nGenerate a new ReplayCubit in [Dart][1]. Built for the [bloc state management library][2].\n\n## Usage 🚀\n\n```sh\nmason make replay_cubit --name counter --style basic\n```\n\n## Variables ✨\n\n| Variable | Description                  | Default                             | Type     |\n| -------- | ---------------------------- | ----------------------------------- | -------- |\n| `name`   | The name of the cubit class  | `counter`                           | `string` |\n| `style`  | The style of cubit generated | `basic (basic, equatable, freezed)` | `enum`   |\n\n## Output 📦\n\n```sh\n├── counter_cubit.dart\n└── counter_state.dart\n```\n\n[1]: https://dart.dev\n[2]: https://github.com/felangel/bloc\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{name.snakeCase()}}_cubit.dart",
    "content": "{{#use_freezed}}{{> freezed_cubit }}{{/use_freezed}}{{#use_equatable}}{{> equatable_cubit }}{{/use_equatable}}{{#use_basic}}{{> basic_cubit }}{{/use_basic}}"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{name.snakeCase()}}_state.dart",
    "content": "{{#use_freezed}}{{> freezed_state }}{{/use_freezed}}{{#use_equatable}}{{> equatable_state }}{{/use_equatable}}{{#use_basic}}{{> basic_state }}{{/use_basic}}"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ basic_cubit }}",
    "content": "import 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends ReplayCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n}\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ basic_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State {\n  const {{name.pascalCase()}}State();\n}\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ equatable_cubit }}",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\n\nclass {{name.pascalCase()}}Cubit extends ReplayCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State());\n}\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ equatable_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\nclass {{name.pascalCase()}}State extends Equatable {\n  const {{name.pascalCase()}}State();\n\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ freezed_cubit }}",
    "content": "import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:replay_bloc/replay_bloc.dart';\n\npart '{{name.snakeCase()}}_state.dart';\npart '{{name.snakeCase()}}_cubit.freezed.dart';\n\nclass {{name.pascalCase()}}Cubit extends ReplayCubit<{{name.pascalCase()}}State> {\n  {{name.pascalCase()}}Cubit() : super(const {{name.pascalCase()}}State.initial());\n}\n"
  },
  {
    "path": "bricks/replay_cubit/__brick__/{{~ freezed_state }}",
    "content": "part of '{{name.snakeCase()}}_cubit.dart';\n\n@freezed\nclass {{name.pascalCase()}}State with _${{name.pascalCase()}}State {\n  const factory {{name.pascalCase()}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "bricks/replay_cubit/brick.yaml",
    "content": "name: replay_cubit\ndescription: Generate a new ReplayCubit in Dart. Built for the bloc state management library.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/bricks/replay_cubit\n\nenvironment:\n  mason: ^0.1.0\n\nvars:\n  name:\n    type: string\n    description: The name of the cubit class.\n    default: counter\n    prompt: Please enter the cubit name.\n  style:\n    type: enum\n    description: The style of cubit generated.\n    default: basic\n    prompt: What is the cubit style?\n    values:\n      - basic\n      - equatable\n      - freezed\n"
  },
  {
    "path": "bricks/replay_cubit/hooks/pre_gen.dart",
    "content": "import 'package:mason/mason.dart';\n\nFuture<void> run(HookContext context) async {\n  final style = context.vars['style'];\n  context.vars = {\n    ...context.vars,\n    'use_basic': style == 'basic',\n    'use_equatable': style == 'equatable',\n    'use_freezed': style == 'freezed',\n  };\n}\n"
  },
  {
    "path": "bricks/replay_cubit/hooks/pubspec.yaml",
    "content": "name: replay_cubit_hooks\n\nenvironment:\n  sdk: ^3.10.0\n\ndependencies:\n  mason: ^0.1.0\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "# build output\ndist/\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# firebase\n.firebase"
  },
  {
    "path": "docs/.prettierignore",
    "content": "src/components/**/*.mdx"
  },
  {
    "path": "docs/.prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\",\n  \"useTabs\": true,\n  \"plugins\": [\"prettier-plugin-astro\"],\n  \"overrides\": [\n    { \"files\": [\".*\", \"*.json\", \"*.md\", \"*.toml\", \"*.yml\"], \"options\": { \"useTabs\": false } },\n    { \"files\": [\"*.md\", \"*.mdx\"], \"options\": { \"printWidth\": 80, \"proseWrap\": \"always\" } }\n  ]\n}\n"
  },
  {
    "path": "docs/astro.config.mjs",
    "content": "import { defineConfig } from 'astro/config';\nimport starlight from '@astrojs/starlight';\nimport starlightLinksValidator from 'starlight-links-validator';\nimport tailwindcss from '@tailwindcss/vite';\n\nconst site = 'https://bloclibrary.dev/';\nconst locales = {\n\troot: { label: 'English', lang: 'en' },\n\taz: { label: 'Azərbaycan', lang: 'az' },\n\tcs: { label: 'Čeština', lang: 'cs' },\n\tde: { label: 'Deutsch', lang: 'de' },\n\tes: { label: 'Español', lang: 'es' },\n\tfil: { label: 'Filipino', lang: 'fil' },\n\tfr: { label: 'Français', lang: 'fr' },\n\tit: { label: 'Italiano', lang: 'it' },\n\tja: { label: '日本語', lang: 'ja' },\n\tko: { label: '한국어', lang: 'ko' },\n\t'pt-br': { label: 'Português', lang: 'pt-BR' },\n\tru: { label: 'Русский', lang: 'ru' },\n\t'zh-cn': { label: '简体中文', lang: 'zh-CN' },\n\tuk: { label: 'Українська', lang: 'uk' },\n\tar: { label: 'العربية', lang: 'ar', dir: 'rtl' },\n\tfa: { label: 'فارسی', lang: 'fa', dir: 'rtl' },\n\tbn: { label: 'বাংলা', lang: 'bn' },\n};\n\n// https://astro.build/config\nexport default defineConfig({\n\tsite,\n\tintegrations: [\n\t\tstarlight({\n\t\t\texpressiveCode: { themes: ['dark-plus', 'github-light'] },\n\t\t\tlogo: {\n\t\t\t\tlight: 'src/assets/light-bloc-logo.svg',\n\t\t\t\tdark: 'src/assets/dark-bloc-logo.svg',\n\t\t\t\treplacesTitle: true,\n\t\t\t},\n\t\t\ttitle: 'Bloc',\n\t\t\teditLink: { baseUrl: 'https://github.com/felangel/bloc/edit/master/docs/' },\n\t\t\ttagline: 'A predictable state management library for Dart.',\n\t\t\tfavicon: 'favicon.ico',\n\t\t\thead: [\n\t\t\t\t{ tag: 'meta', attrs: { property: 'og:image', content: site + 'og.png?v=1' } },\n\t\t\t\t{ tag: 'meta', attrs: { property: 'twitter:image', content: site + 'og.png?v=1' } },\n\t\t\t],\n\t\t\tcustomCss: ['src/tailwind.css', 'src/styles/landing.css', '@fontsource-variable/figtree'],\n\t\t\tsocial: [\n\t\t\t\t{ icon: 'github', label: 'GitHub', href: 'https://github.com/felangel/bloc' },\n\t\t\t\t{ icon: 'discord', label: 'Discord', href: 'https://discord.gg/bloc' },\n\t\t\t],\n\t\t\tdefaultLocale: 'root',\n\t\t\tlocales,\n\t\t\tsidebar: [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Introduction',\n\t\t\t\t\ttranslations: {\n\t\t\t\t\t\tar: 'المقدمة',\n\t\t\t\t\t\t'zh-CN': '介绍',\n\t\t\t\t\t\tfa: 'مقدمه',\n\t\t\t\t\t\tes: 'Introducción',\n\t\t\t\t\t\tja: '紹介',\n\t\t\t\t\t\tru: 'Введение',\n\t\t\t\t\t\tuk: 'Вступ',\n\t\t\t\t\t},\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Getting Started',\n\t\t\t\t\t\t\tlink: '/getting-started/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'ابدأ الآن',\n\t\t\t\t\t\t\t\t'zh-CN': '快速入门',\n\t\t\t\t\t\t\t\tfa: 'شروع شدن',\n\t\t\t\t\t\t\t\tes: 'Empezando',\n\t\t\t\t\t\t\t\tja: 'はじめに',\n\t\t\t\t\t\t\t\tru: 'Начало работы',\n\t\t\t\t\t\t\t\tuk: 'Початок роботи',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Why Bloc?',\n\t\t\t\t\t\t\tlink: '/why-bloc/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'لماذا Bloc؟',\n\t\t\t\t\t\t\t\t'zh-CN': '为什么用 Bloc?',\n\t\t\t\t\t\t\t\tfa: 'چرا Bloc؟',\n\t\t\t\t\t\t\t\tes: '¿Por qué Bloc?',\n\t\t\t\t\t\t\t\tja: 'なぜBloc？',\n\t\t\t\t\t\t\t\tru: 'Почему Bloc?',\n\t\t\t\t\t\t\t\tuk: 'Чому Bloc?',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Bloc Concepts',\n\t\t\t\t\t\t\tlink: '/bloc-concepts/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'مفاهيم Bloc',\n\t\t\t\t\t\t\t\t'zh-CN': 'Bloc 核心概念',\n\t\t\t\t\t\t\t\tfa: 'مفاهیم Bloc',\n\t\t\t\t\t\t\t\tes: 'Conceptos de Bloc',\n\t\t\t\t\t\t\t\tja: 'Blocのコンセプト',\n\t\t\t\t\t\t\t\tru: 'Концепции Bloc',\n\t\t\t\t\t\t\t\tuk: 'Концепції Bloc',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Flutter Bloc Concepts',\n\t\t\t\t\t\t\tlink: '/flutter-bloc-concepts/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'مفاهيم Flutter Bloc',\n\t\t\t\t\t\t\t\t'zh-CN': 'Flutter Bloc 核心概念',\n\t\t\t\t\t\t\t\tfa: 'مفاهیم Bloc فلاتر',\n\t\t\t\t\t\t\t\tes: 'Conceptos de Flutter Bloc',\n\t\t\t\t\t\t\t\tja: 'Flutter Blocのコンセプト',\n\t\t\t\t\t\t\t\tru: 'Концепции Flutter Bloc',\n\t\t\t\t\t\t\t\tuk: 'Концепції Flutter Bloc',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Architecture',\n\t\t\t\t\t\t\tlink: '/architecture/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'البنية المعمارية',\n\t\t\t\t\t\t\t\tfa: 'معماری',\n\t\t\t\t\t\t\t\tes: 'Arquitectura',\n\t\t\t\t\t\t\t\tja: 'アーキテクチャー',\n\t\t\t\t\t\t\t\tru: 'Архитектура',\n\t\t\t\t\t\t\t\tuk: 'Архітектура',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Modeling State',\n\t\t\t\t\t\t\tlink: '/modeling-state/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'نمذجة الحالة',\n\t\t\t\t\t\t\t\tfa: 'حالت (State) مدل سازی',\n\t\t\t\t\t\t\t\tes: 'Modelando el Estado',\n\t\t\t\t\t\t\t\tja: '状態のモデリング',\n\t\t\t\t\t\t\t\tru: 'Моделирование состояния',\n\t\t\t\t\t\t\t\tuk: 'Моделювання стану',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Testing',\n\t\t\t\t\t\t\tlink: '/testing/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'الاختبارات',\n\t\t\t\t\t\t\t\tfa: 'آزمایش کردن',\n\t\t\t\t\t\t\t\tes: 'Pruebas',\n\t\t\t\t\t\t\t\tja: 'テスト',\n\t\t\t\t\t\t\t\tru: 'Тестирование',\n\t\t\t\t\t\t\t\tuk: 'Тестування',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Naming Conventions',\n\t\t\t\t\t\t\tlink: '/naming-conventions/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'اتفاقيات التسمية',\n\t\t\t\t\t\t\t\tfa: 'قراردادهای نامگذاری',\n\t\t\t\t\t\t\t\tes: 'Convenciones de Nomenclatura',\n\t\t\t\t\t\t\t\tja: '命名規則',\n\t\t\t\t\t\t\t\tru: 'Соглашения об именовании',\n\t\t\t\t\t\t\t\tuk: 'Угоди про іменування',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Migration Guide',\n\t\t\t\t\t\t\tlink: '/migration/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'دليل الترقية',\n\t\t\t\t\t\t\t\tfa: 'راهنمای مهاجرت',\n\t\t\t\t\t\t\t\tes: 'Guía de Migración',\n\t\t\t\t\t\t\t\tja: '移行ガイド',\n\t\t\t\t\t\t\t\tru: 'Руководство по миграции',\n\t\t\t\t\t\t\t\tuk: 'Посібник з міграції',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'FAQs',\n\t\t\t\t\t\t\tlink: '/faqs/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'الأسئلة الشائعة',\n\t\t\t\t\t\t\t\tfa: 'سوالات متداول',\n\t\t\t\t\t\t\t\tes: 'Preguntas Frecuentes',\n\t\t\t\t\t\t\t\tja: 'よくある質問',\n\t\t\t\t\t\t\t\tru: 'Часто задаваемые вопросы',\n\t\t\t\t\t\t\t\tuk: 'Часті запитання',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Linter',\n\t\t\t\t\tbadge: { text: 'new' },\n\t\t\t\t\ttranslations: { ar: 'المدقق', uk: 'Лінтер' },\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Overview ',\n\t\t\t\t\t\t\tlink: '/lint/',\n\t\t\t\t\t\t\ttranslations: { ar: 'نظرة عامة', fa: 'بررسی اجمالی', ru: 'Обзор', uk: 'Огляд' },\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Installation ',\n\t\t\t\t\t\t\tlink: '/lint/installation/',\n\t\t\t\t\t\t\ttranslations: { ar: 'التثبيت', fa: 'نصب', ru: 'Установка', uk: 'Встановлення' },\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Configuration ',\n\t\t\t\t\t\t\tlink: '/lint/configuration/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'الإعداد',\n\t\t\t\t\t\t\t\tfa: 'پیکربندی',\n\t\t\t\t\t\t\t\tru: 'Конфигурация',\n\t\t\t\t\t\t\t\tuk: 'Конфігурація',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Customizing Rules ',\n\t\t\t\t\t\t\tlink: '/lint/customizing-rules/',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'تخصيص القواعد',\n\t\t\t\t\t\t\t\tfa: 'سفارشی سازی قوانین',\n\t\t\t\t\t\t\t\tru: 'Настройка правил',\n\t\t\t\t\t\t\t\tuk: 'Налаштування правил',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Rules',\n\t\t\t\t\t\t\tautogenerate: { directory: '/lint-rules' },\n\t\t\t\t\t\t\ttranslations: { ar: 'القواعد', fa: 'قوانین', ru: 'Правила', uk: 'Правила' },\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Tutorials',\n\t\t\t\t\ttranslations: {\n\t\t\t\t\t\tar: 'الدروس التعليمية',\n\t\t\t\t\t\tfa: 'آموزش ها',\n\t\t\t\t\t\tes: 'Tutoriales',\n\t\t\t\t\t\tja: 'チュートリアル',\n\t\t\t\t\t\tru: 'Руководства',\n\t\t\t\t\t\tuk: 'Посібники',\n\t\t\t\t\t},\n\t\t\t\t\tautogenerate: { directory: 'tutorials' },\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Tools',\n\t\t\t\t\ttranslations: {\n\t\t\t\t\t\tar: 'الأدوات',\n\t\t\t\t\t\tfa: 'ابزار',\n\t\t\t\t\t\tes: 'Herramientas',\n\t\t\t\t\t\tja: 'ツール',\n\t\t\t\t\t\tru: 'Инструменты',\n\t\t\t\t\t\tuk: 'Інструменти',\n\t\t\t\t\t},\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'IntelliJ Plugin',\n\t\t\t\t\t\t\tlink: 'https://plugins.jetbrains.com/plugin/12129-bloc',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'إضافة IntelliJ',\n\t\t\t\t\t\t\t\tfa: 'پلاگین IntelliJ',\n\t\t\t\t\t\t\t\tes: 'Plugin de IntelliJ',\n\t\t\t\t\t\t\t\tru: 'Плагин IntelliJ',\n\t\t\t\t\t\t\t\tuk: 'Плагін IntelliJ',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'VSCode Extension',\n\t\t\t\t\t\t\tlink: 'https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc',\n\t\t\t\t\t\t\ttranslations: {\n\t\t\t\t\t\t\t\tar: 'إضافة VS Code',\n\t\t\t\t\t\t\t\tfa: 'پلاگین VSCode',\n\t\t\t\t\t\t\t\tes: 'Extensión de VSCode',\n\t\t\t\t\t\t\t\tru: 'Расширение VSCode',\n\t\t\t\t\t\t\t\tuk: 'Розширення VSCode',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Reference',\n\t\t\t\t\ttranslations: {\n\t\t\t\t\t\tar: 'المرجع',\n\t\t\t\t\t\tfa: 'مرجع',\n\t\t\t\t\t\tes: 'Referencia',\n\t\t\t\t\t\tja: 'APIリファレンス',\n\t\t\t\t\t\tru: 'Справочник',\n\t\t\t\t\t\tuk: 'Довідник',\n\t\t\t\t\t},\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'angular_bloc',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/angular_bloc/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ label: 'bloc', link: 'https://pub.dev/documentation/bloc/latest/index.html' },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'bloc_concurrency',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/bloc_concurrency/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'bloc_lint',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/bloc_lint/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'bloc_test',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/bloc_test/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'bloc_tools',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/bloc_tools/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'flutter_bloc',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/flutter_bloc/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'hydrated_bloc',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/hydrated_bloc/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'replay_bloc',\n\t\t\t\t\t\t\tlink: 'https://pub.dev/documentation/replay_bloc/latest/index.html',\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\tplugins: [\n\t\t\t\tstarlightLinksValidator({ errorOnFallbackPages: false, errorOnInconsistentLocale: true }),\n\t\t\t],\n\t\t}),\n\t],\n\tvite: { plugins: [tailwindcss()] },\n});\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"docs\",\n  \"type\": \"module\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"start\": \"astro dev\",\n    \"build\": \"astro check && astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\",\n    \"format\": \"prettier --write .\",\n    \"format:check\": \"prettier --check .\"\n  },\n  \"dependencies\": {\n    \"@astrojs/check\": \"^0.9.6\",\n    \"@astrojs/starlight\": \"^0.37.6\",\n    \"@astrojs/starlight-tailwind\": \"^4.0.2\",\n    \"@fontsource-variable/figtree\": \"^5.2.6\",\n    \"astro\": \"^5.17.1\",\n    \"sharp\": \"^0.34.1\",\n    \"tailwindcss\": \"^4.1.4\",\n    \"typescript\": \"^5.7.3\"\n  },\n  \"devDependencies\": {\n    \"@shikijs/transformers\": \"^3.3.0\",\n    \"@tailwindcss/vite\": \"^4.1.4\",\n    \"prettier\": \"^3.5.3\",\n    \"prettier-plugin-astro\": \"^0.14.1\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.11\",\n    \"starlight-links-validator\": \"^0.16.0\"\n  }\n}\n"
  },
  {
    "path": "docs/public/CNAME",
    "content": "bloclibrary.dev"
  },
  {
    "path": "docs/src/components/architecture/AppIdeaRankingBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass AppIdeaRankingBloc\n    extends Bloc<AppIdeaRankingEvent, AppIdeaRankingState> {\n  AppIdeaRankingBloc({required AppIdeasRepository appIdeasRepo})\n      : _appIdeasRepo = appIdeasRepo,\n        super(AppIdeaInitialRankingState()) {\n    on<AppIdeaStartRankingEvent>((event, emit) async {\n      // When we are told to start ranking app ideas, we will listen to the\n      // stream of app ideas and emit a state for each one.\n      await emit.forEach(\n        _appIdeasRepo.productIdeas(),\n        onData: (String idea) => AppIdeaRankingIdeaState(idea: idea),\n      );\n    });\n  }\n\n  final AppIdeasRepository _appIdeasRepo;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"app_idea_ranking_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/AppIdeasRepositorySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass AppIdeasRepository {\n  int _currentAppIdea = 0;\n  final List<String> _ideas = [\n    \"Future prediction app that rewards you if you predict the next day's news\",\n    'Dating app for fish that lets your aquarium occupants find true love',\n    'Social media app that pays you when your data is sold',\n    'JavaScript framework gambling app that lets you bet on the next big thing',\n    'Solitaire app that freezes before you can win',\n  ];\n\n  Stream<String> productIdeas() async* {\n    while (true) {\n      yield _ideas[_currentAppIdea++ % _ideas.length];\n      await Future<void>.delayed(const Duration(minutes: 1));\n    }\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"app_ideas_repository.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/BlocLooseCouplingPresentationSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass MyWidget extends StatelessWidget {\n  const MyWidget({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<FirstBloc, FirstState>(\n      listener: (context, state) {\n        // When the first bloc's state changes, this will be called.\n        //\n        // Now we can add an event to the second bloc without it having\n        // to know about the first bloc.\n        context.read<SecondBloc>().add(SecondEvent());\n      },\n      child: TextButton(\n        child: const Text('Hello'),\n        onPressed: () {\n          context.read<FirstBloc>().add(FirstEvent());\n        },\n      ),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_widget.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/BlocTightCouplingSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass TightlyCoupledBloc extends Bloc {\n  final OtherBloc otherBloc;\n  late final StreamSubscription otherBlocSubscription;\n\n  TightlyCoupledBloc(this.otherBloc) {\n    // No matter how much you are tempted to do this, you should not do this!\n    // Keep reading for better alternatives!\n    otherBlocSubscription = otherBloc.stream.listen((state) {\n      add(MyEvent());\n    });\n  }\n\n  @override\n  Future<void> close() {\n    otherBlocSubscription.cancel();\n    return super.close();\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"tightly_coupled_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/BusinessLogicComponentSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass BusinessLogicComponent extends Bloc<MyEvent, MyState> {\n    BusinessLogicComponent(this.repository) {\n        on<AppStarted>((event, emit) async {\n            try {\n                final data = await repository.getAllDataThatMeetsRequirements();\n                emit(Success(data));\n            } catch (error) {\n                emit(Failure(error));\n            }\n        });\n    }\n\n    final Repository repository;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"business_logic_component.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/DataProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass DataProvider {\n    Future<RawData> readData() async {\n        // Read from DB or make network request etc...\n    }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"data_provider.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/PresentationComponentSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass PresentationComponent {\n  PresentationComponent({required this.bloc}) {\n    bloc.add(AppStarted());\n  }\n\n  final Bloc bloc;\n\n  build() {\n    // render UI based on bloc state\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"presentation_component.dart\" />\n"
  },
  {
    "path": "docs/src/components/architecture/RepositorySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass Repository {\n    final DataProviderA dataProviderA;\n    final DataProviderB dataProviderB;\n\n    Future<Data> getAllDataThatMeetsRequirements() async {\n        final RawDataA dataSetA = await dataProviderA.readData();\n        final RawDataB dataSetB = await dataProviderB.readData();\n\n        final Data filteredData = _filterData(dataSetA, dataSetB);\n        return filteredData;\n    }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"repository.dart\" />\n"
  },
  {
    "path": "docs/src/components/code/RemoteCode.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\nimport { z } from 'astro/zod';\n\ninterface Props {\n\ttitle: string;\n\turl: string;\n}\n\nconst propsSchema = z.object({\n\ttitle: z.string(),\n\turl: z.string(),\n});\n\nconst { url, title } = propsSchema.parse(Astro.props);\n\nconst segments = url.split('.');\nconst lang = segments[segments.length - 1];\nconst response = await fetch(url);\nconst code = await response.text();\n---\n\n<Code code={code} lang={lang} title={title} />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/AuthenticationChangeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nChange {\n  currentState: AuthenticationState.authenticated,\n  nextState: AuthenticationState.unauthenticated\n}\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/AuthenticationStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nenum AuthenticationState { unknown, authenticated, unauthenticated }\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"authentication_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/AuthenticationTransitionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nTransition {\n  currentState: AuthenticationState.authenticated,\n  event: LogoutRequested,\n  nextState: AuthenticationState.unauthenticated\n}\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CountStreamSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nStream<int> countStream(int max) async* {\n    for (int i = 0; i < max; i++) {\n        yield i;\n    }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"count_stream.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) {\n      // handle incoming \\`CounterIncrementPressed\\` event\n    });\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocFullSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocIncrementSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) {\n      emit(state + 1);\n    });\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnChangeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  CounterBloc()\n    ..add(CounterIncrementPressed())\n    ..close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nException: increment error!\n#0      new CounterBloc.<anonymous closure> (file:///main.dart:10:58)\n#1      Bloc.on.<anonymous closure>.handleEvent (package:bloc/src/bloc.dart:229:26)\n#2      Bloc.on.<anonymous closure> (package:bloc/src/bloc.dart:238:9)\n#3      _MapStream._handleData (dart:async/stream_pipe.dart:213:31)\n#4      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)\n#5      _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#6      CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11)\n#7      _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#8      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)\n#9      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)\n#10     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)\n#11     _WhereStream._handleData (dart:async/stream_pipe.dart:195:12)\n#12     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)\n#13     _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#14     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)\n#15     _DelayedData.perform (dart:async/stream_impl.dart:515:14)\n#16     _PendingEvents.handleNext (dart:async/stream_impl.dart:620:11)\n#17     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:591:7)\n#18     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)\n#19     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)\n#20     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)\n#21     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:185:5)\n\nCounterBloc Exception: increment error!\n#0      new CounterBloc.<anonymous closure> (file:///main.dart:10:58)\n#1      Bloc.on.<anonymous closure>.handleEvent (package:bloc/src/bloc.dart:229:26)\n#2      Bloc.on.<anonymous closure> (package:bloc/src/bloc.dart:238:9)\n#3      _MapStream._handleData (dart:async/stream_pipe.dart:213:31)\n#4      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)\n#5      _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#6      CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11)\n#7      _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#8      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)\n#9      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)\n#10     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)\n#11     _WhereStream._handleData (dart:async/stream_pipe.dart:195:12)\n#12     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)\n#13     _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)\n#14     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)\n#15     _DelayedData.perform (dart:async/stream_impl.dart:515:14)\n#16     _PendingEvents.handleNext (dart:async/stream_impl.dart:620:11)\n#17     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:591:7)\n#18     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)\n#19     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)\n#20     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)\n#21     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:185:5)\n\nTransition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nCounterBloc Transition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nCounterBloc Change { currentState: 0, nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnErrorSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) {\n      addError(Exception('increment error!'), StackTrace.current);\n      emit(state + 1);\n    });\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    print(transition);\n    super.onTransition(transition);\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    print('$error, $stackTrace');\n    super.onError(error, stackTrace);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnEventSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n\n  @override\n  void onEvent(CounterEvent event) {\n    super.onEvent(event);\n    print(event);\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    print(transition);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nTransition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    print(transition);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nFuture<void> main() async {\n  final bloc = CounterBloc();\n  final subscription = bloc.stream.listen(print); // 1\n  bloc.add(CounterIncrementPressed());\n  await Future.delayed(Duration.zero);\n  await subscription.cancel();\n  await bloc.close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterBlocUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nFuture<void> main() async {\n  final bloc = CounterBloc();\n  print(bloc.state); // 0\n  bloc.add(CounterIncrementPressed());\n  await Future.delayed(Duration.zero);\n  print(bloc.state); // 1\n  await bloc.close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  final cubit = CounterCubit();\n  print(cubit.state); // 0\n  cubit.increment();\n  print(cubit.state); // 1\n  cubit.close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitFullSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitIncrementSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitInitialStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(int initialState) : super(initialState);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitInstantiationSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nfinal cubitA = CounterCubit(0); // state starts at 0\nfinal cubitB = CounterCubit(10); // state starts at 10\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitOnChangeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  CounterCubit()\n    ..increment()\n    ..close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nException: increment error!\n#0      CounterCubit.increment (file:///main.dart:7:56)\n#1      main (file:///main.dart:41:7)\n#2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)\n#3      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)\n\nCounterCubit Exception: increment error!\n#0      CounterCubit.increment (file:///main.dart:7:56)\n#1      main (file:///main.dart:41:7)\n#2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)\n#3      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)\n\nCounterCubit Change { currentState: 0, nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitOnErrorSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() {\n    addError(Exception('increment error!'), StackTrace.current);\n    emit(state + 1);\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    print('$error, $stackTrace');\n    super.onError(error, stackTrace);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nFuture<void> main() async {\n  final cubit = CounterCubit();\n  final subscription = cubit.stream.listen(print); // 1\n  cubit.increment();\n  await Future.delayed(Duration.zero);\n  await subscription.cancel();\n  await cubit.close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/DebounceEventTransformerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nEventTransformer<T> debounce<T>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nCounterBloc() : super(0) {\n  on<Increment>(\n    (event, emit) => emit(state + 1),\n    /// Apply the custom \\`EventTransformer\\` to the \\`EventHandler\\`.\n    transformer: debounce(const Duration(milliseconds: 300)),\n  );\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nCounterCubit Change { currentState: 0, nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('\\${bloc.runtimeType} $change');\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"simple_bloc_observer.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  Bloc.observer = SimpleBlocObserver();\n  CounterCubit()\n    ..increment()\n    ..close();  \n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('\\${bloc.runtimeType} $change');\n  }\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {\n    print('\\${bloc.runtimeType} $error $stackTrace');\n    super.onError(bloc, error, stackTrace);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"simple_bloc_observer.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nCounterBloc Instance of 'CounterIncrementPressed'\nInstance of 'CounterIncrementPressed'\nCounterBloc Transition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nTransition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nCounterBloc Change { currentState: 0, nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onEvent(Bloc bloc, Object? event) {\n    super.onEvent(bloc, event);\n    print('\\${bloc.runtimeType} $event');\n  }\n\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('\\${bloc.runtimeType} $change');\n  }\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {\n    super.onTransition(bloc, transition);\n    print('\\${bloc.runtimeType} $transition');\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"simple_bloc_observer.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nCounterBloc Transition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nTransition { currentState: 0, event: Instance of 'CounterIncrementPressed', nextState: 1 }\nCounterBloc Change { currentState: 0, nextState: 1 }\nChange { currentState: 0, nextState: 1 }\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('\\${bloc.runtimeType} $change');\n  }\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {\n    super.onTransition(bloc, transition);\n    print('\\${bloc.runtimeType} $transition');\n  }\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {\n    print('\\${bloc.runtimeType} $error $stackTrace');\n    super.onError(bloc, error, stackTrace);\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"simple_bloc_observer.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  Bloc.observer = SimpleBlocObserver();\n  CounterBloc()\n    ..add(CounterIncrementPressed())\n    ..close();  \n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/StreamsMainSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() async {\n    /// Initialize a stream of integers 0-9\n    Stream<int> stream = countStream(10);\n    /// Compute the sum of the stream of integers\n    int sum = await sumStream(stream);\n    /// Print the sum\n    print(sum); // 45\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/bloc/SumStreamSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nFuture<int> sumStream(Stream<int> stream) async {\n    int sum = 0;\n    await for (int value in stream) {\n        sum += value;\n    }\n    return sum;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"sum_stream.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocBuilder<BlocA, BlocAState>(\n  buildWhen: (previousState, state) {\n    // return true/false to determine whether or not\n    // to rebuild the widget with state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocBuilder<BlocA, BlocAState>(\n  bloc: blocA, // provide the local bloc instance\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocBuilderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocBuilder<BlocA, BlocAState>(\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocConsumer<BlocA, BlocAState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether or not\n    // to invoke listener with state\n  },\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  buildWhen: (previous, current) {\n    // return true/false to determine whether or not\n    // to rebuild the widget with state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocConsumerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocConsumer<BlocA, BlocAState>(\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocListener<BlocA, BlocAState>(\n  listenWhen: (previousState, state) {\n    // return true/false to determine whether or not\n    // to call listener with state\n  },\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  child: const SizedBox(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocListener<BlocA, BlocAState>(\n  bloc: blocA,\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  child: const SizedBox(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocListenerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocListener<BlocA, BlocAState>(\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  child: const SizedBox(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocProvider(\n  lazy: false,\n  create: (BuildContext context) => BlocA(),\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n// with extensions\ncontext.read<BlocA>();\n\n// without extensions\nBlocProvider.of<BlocA>(context);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocProvider(\n  create: (BuildContext context) => BlocA(),\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocProvider.value(\n  value: BlocProvider.of<BlocA>(context),\n  child: ScreenA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/BlocSelectorSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocSelector<BlocA, BlocAState, SelectedState>(\n  selector: (state) {\n    // return selected state based on the provided state.\n  },\n  builder: (context, state) {\n    // return widget here based on the selected state.\n  },\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/CounterBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\nfinal class CounterIncrementPressed extends CounterEvent {}\nfinal class CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/CounterMainSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() => runApp(CounterApp());\n\nclass CounterApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: BlocProvider(\n        create: (_) => CounterBloc(),\n        child: CounterPage(),\n      ),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/CounterPageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass CounterPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: Text('Counter')),\n      body: BlocBuilder<CounterBloc, int>(\n        builder: (context, count) {\n          return Center(\n            child: Text(\n              '$count',\n              style: TextStyle(fontSize: 24.0),\n            ),\n          );\n        },\n      ),\n      floatingActionButton: Column(\n        crossAxisAlignment: CrossAxisAlignment.end,\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: <Widget>[\n          Padding(\n            padding: EdgeInsets.symmetric(vertical: 5.0),\n            child: FloatingActionButton(\n              child: Icon(Icons.add),\n              onPressed: () => context.read<CounterBloc>().add(CounterIncrementPressed()),\n            ),\n          ),\n          Padding(\n            padding: EdgeInsets.symmetric(vertical: 5.0),\n            child: FloatingActionButton(\n              child: Icon(Icons.remove),\n              onPressed: () => context.read<CounterBloc>().add(CounterDecrementPressed()),\n            ),\n          ),\n        ],\n      ),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMultiBlocListener(\n  listeners: [\n    BlocListener<BlocA, BlocAState>(\n      listener: (context, state) {},\n    ),\n    BlocListener<BlocB, BlocBState>(\n      listener: (context, state) {},\n    ),\n    BlocListener<BlocC, BlocCState>(\n      listener: (context, state) {},\n    ),\n  ],\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMultiBlocProvider(\n  providers: [\n    BlocProvider<BlocA>(\n      create: (BuildContext context) => BlocA(),\n    ),\n    BlocProvider<BlocB>(\n      create: (BuildContext context) => BlocB(),\n    ),\n    BlocProvider<BlocC>(\n      create: (BuildContext context) => BlocC(),\n    ),\n  ],\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMultiRepositoryProvider(\n  providers: [\n    RepositoryProvider<RepositoryA>(\n      create: (context) => RepositoryA(),\n    ),\n    RepositoryProvider<RepositoryB>(\n      create: (context) => RepositoryB(),\n    ),\n    RepositoryProvider<RepositoryC>(\n      create: (context) => RepositoryC(),\n    ),\n  ],\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocListener<BlocA, BlocAState>(\n  listener: (context, state) {},\n  child: BlocListener<BlocB, BlocBState>(\n    listener: (context, state) {},\n    child: BlocListener<BlocC, BlocCState>(\n      listener: (context, state) {},\n      child: ChildA(),\n    ),\n  ),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nBlocProvider<BlocA>(\n  create: (BuildContext context) => BlocA(),\n  child: BlocProvider<BlocB>(\n    create: (BuildContext context) => BlocB(),\n    child: BlocProvider<BlocC>(\n      create: (BuildContext context) => BlocC(),\n      child: ChildA(),\n    ),\n  ),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nRepositoryProvider<RepositoryA>(\n  create: (context) => RepositoryA(),\n  child: RepositoryProvider<RepositoryB>(\n    create: (context) => RepositoryB(),\n    child: RepositoryProvider<RepositoryC>(\n      create: (context) => RepositoryC(),\n      child: ChildA(),\n    ),\n  ),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nRepositoryProvider<RepositoryA>(\n  create: (context) => RepositoryA(),\n  dispose: (repository) => repository.dispose(),\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n// with extensions\ncontext.read<RepositoryA>();\n\n// without extensions\nRepositoryProvider.of<RepositoryA>(context)\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nRepositoryProvider(\n  create: (context) => RepositoryA(),\n  child: ChildA(),\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/WeatherAppSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:weather_repository/weather_repository.dart';\n\nclass WeatherApp extends StatelessWidget {\n  const WeatherApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider(\n      create: (_) => WeatherRepository(),\n      dispose: (repository) => repository.dispose(),\n      child: BlocProvider(\n        create: (context) => WeatherCubit(context.read<WeatherRepository>()),\n        child: const WeatherAppView(),\n      ),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"app.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/WeatherMainSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_weather/app.dart';\n\nvoid main() => runApp(const WeatherApp());\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/WeatherPageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:weather_repository/weather_repository.dart';\n\nclass WeatherPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (context) => WeatherCubit(context.read<WeatherRepository>()),\n      child: WeatherView(),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"weather_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass WeatherRepository {\n  WeatherRepository({\n    WeatherApiClient? weatherApiClient\n  }) : _weatherApiClient = weatherApiClient ?? WeatherApiClient();\n\n  final WeatherApiClient _weatherApiClient;\n\n  Future<Weather> getWeather(String city) async {\n    final location = await _weatherApiClient.locationSearch(city);\n    final woeid = location.woeid;\n    final weather = await _weatherApiClient.getWeather(woeid);\n    return Weather(\n      temperature: weather.theTemp,\n      location: location.title,\n      condition: weather.weatherStateAbbr.toCondition,\n    );\n  }\n\n  void dispose() => _weatherApiClient.close();\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"weather_repository.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocExternalForEachSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc({required UserRepository userRepository})\n    : _userRepository = userRepository, super(...) {\n    on<Started>(_onStarted);\n  }\n\n  Future<void> _onStarted(Started event, Emitter<MyState> emit) {\n    return emit.forEach(\n      _userRepository.user,\n      onData: (user) => MyState(...)\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocInternalAddEventSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc({required UserRepository userRepository}) : super(...) {\n    on<_UserChanged>(_onUserChanged);\n    _userSubscription = userRepository.user.listen(\n      (user) => add(_UserChanged(user)),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocInternalEventSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class MyEvent {}\n\n// \\`EventA\\` is an external event.\nfinal class EventA extends MyEvent {}\n\n// \\`EventB\\` is an internal event.\n// We are explicitly making \\`EventB\\` private so that it can only be used\n// within the bloc.\nfinal class _EventB extends MyEvent {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_event.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocProviderBad1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n@override\nWidget build(BuildContext context) {\n  return BlocProvider(\n    create: (_) => BlocA(),\n    child: ElevatedButton(\n      onPressed: () {\n        final blocA = BlocProvider.of<BlocA>(context);\n        ...\n      }\n    )\n  );\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_widget.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocProviderGood1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n@override\nWidget build(BuildContext context) {\n  return BlocProvider(\n    create: (_) => BlocA(),\n    child: MyChild();\n  );\n}\n\nclass MyChild extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return ElevatedButton(\n      onPressed: () {\n        final blocA = BlocProvider.of<BlocA>(context);\n        ...\n      },\n    )\n    ...\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_widget.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/BlocProviderGood2Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n@override\nWidget build(BuildContext context) {\n  return BlocProvider(\n    create: (_) => BlocA(),\n    child: Builder(\n      builder: (context) => ElevatedButton(\n        onPressed: () {\n          final blocA = BlocProvider.of<BlocA>(context);\n          ...\n        },\n      ),\n    ),\n  );\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_widget.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/EquatableBlocTestSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nblocTest(\n    '...',\n    build: () => MyBloc(),\n    act: (bloc) => bloc.add(MyEvent()),\n    expect: [\n        MyStateA(),\n        MyStateB(),\n    ],\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/EquatableEmitSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMyBloc() {\n    on<MyEvent>((event, emit) {\n        emit(StateA('hi'));\n        emit(StateA('hi'));\n    });\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/NoEquatableBlocTestSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nblocTest(\n    '...',\n    build: () => MyBloc(),\n    act: (bloc) => bloc.add(MyEvent()),\n    expect: [\n        isA<MyStateA>(),\n        isA<MyStateB>(),\n    ],\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/SingleStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nenum Status { initial, loading, success, failure }\n\nclass MyState {\n  const MyState({\n    this.data = Data.empty,\n    this.error = '',\n    this.status = Status.initial,\n  });\n\n  final Data data;\n  final String error;\n  final Status status;\n\n  MyState copyWith({Data data, String error, Status status}) {\n    return MyState(\n      data: data ?? this.data,\n      error: error ?? this.error,\n      status: status ?? this.status,\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/SingleStateUsageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\non<DataRequested>((event, emit) {\n  try {\n    final data = await _repository.getData();\n    emit(state.copyWith(status: Status.success, data: data));\n  } catch(error) {\n    emit(state.copyWith(status: Status.failure, error: 'Something went wrong!'));\n  }\n});\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingBad1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class MyState extends Equatable {\n    const MyState();\n}\n\nfinal class StateA extends MyState {\n    final String property;\n\n    const StateA(this.property);\n\n    @override\n    List<Object> get props => [];\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingBad2Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class MyState extends Equatable {\n    const MyState();\n}\n\nfinal class StateA extends MyState {\n    final String property;\n\n    const StateA(this.property);\n\n    @override\n    List<Object> get props => null;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingBad3Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMyBloc() {\n    on<MyEvent>((event, emit) {\n        // never modify/mutate state\n        state.property = event.property;\n        // never emit the same instance of state\n        emit(state);\n    });\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingGood1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class MyState extends Equatable {\n    const MyState();\n}\n\nfinal class StateA extends MyState {\n    final String property;\n\n    const StateA(this.property);\n\n    @override\n    List<Object> get props => [property]; // pass all properties to props\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingGood2Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMyBloc() {\n    on<MyEvent>((event, emit) {\n        // always create a new instance of the state you are going to yield\n        emit(state.copyWith(property: event.property));\n    });\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/faqs/StateNotUpdatingGood3Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nMyBloc() {\n    on<MyEvent>((event, emit) {\n        final data = _getData(event.info);\n        // always create a new instance of the state you are going to yield\n        emit(MyState(data: data));\n    });\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"my_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/getting-started/ImportTabs.astro",
    "content": "---\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n---\n\n<Tabs>\n\t<TabItem label=\"Dart\">\n\t\t<Code code=\"import 'package:bloc/bloc.dart';\" lang=\"dart\" title=\"main.dart\" />\n\t</TabItem>\n\t<TabItem label=\"Flutter\">\n\t\t<Code code=\"import 'package:flutter_bloc/flutter_bloc.dart';\" lang=\"dart\" title=\"main.dart\" />\n\t</TabItem>\n\t<TabItem label=\"Angular Dart\">\n\t\t<Code code=\"import 'package:angular_bloc/angular_bloc.dart';\" lang=\"dart\" title=\"main.dart\" />\n\t</TabItem>\n</Tabs>\n"
  },
  {
    "path": "docs/src/components/getting-started/InstallationTabs.astro",
    "content": "---\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\nconst installBloc = `\n# Add bloc to your project.\ndart pub add bloc\n`;\nconst installFlutterBloc = `\n# Add flutter_bloc to your project.\nflutter pub add flutter_bloc\n`;\nconst installAngularBloc = `\n# Add angular_bloc to your project.\ndart pub add angular_bloc\n`;\n---\n\n<Tabs>\n\t<TabItem label=\"Dart\">\n\t\t<Code code={installBloc} lang=\"bash\" />\n\t</TabItem>\n\t<TabItem label=\"Flutter\">\n\t\t<Code code={installFlutterBloc} lang=\"bash\" />\n\t</TabItem>\n\t<TabItem label=\"Angular Dart\">\n\t\t<Code code={installAngularBloc} lang=\"bash\" />\n\t</TabItem>\n</Tabs>\n"
  },
  {
    "path": "docs/src/components/landing/Card.astro",
    "content": "---\nimport { Card as StarlightCard } from '@astrojs/starlight/components';\nexport type Props = Parameters<typeof StarlightCard>[0];\n---\n\n<div class=\"landing-card\">\n\t<StarlightCard {...Astro.props}><slot /></StarlightCard>\n</div>\n\n<style>\n\t.landing-card {\n\t\t--card-bg: rgba(23, 25, 30, 0.8);\n\t\tdisplay: contents;\n\t\tcolor: var(--sl-color-white);\n\t}\n\n\t:global(:root[data-theme='light']) .landing-card {\n\t\t--card-bg: rgba(255, 255, 255, 0.4);\n\t}\n\n\t.landing-card :global(.card) {\n\t\toverflow: hidden;\n\t\tbackground: var(--card-bg);\n\t\tposition: relative;\n\t}\n\n\t.landing-card :global(.card > *) {\n\t\tz-index: 1;\n\t}\n\n\t.landing-card :global(.card) {\n\t\tbackground: var(--card-bg);\n\t\tposition: relative;\n\t}\n\n\t.landing-card :global(.card) {\n\t\t--sl-card-bg: var(--sl-color-blue-low);\n\t\t--sl-card-border: var(--sl-color-blue-high);\n\t\t--landing-card-accent: hsl(185, 90%, 46%);\n\t}\n\n\t.landing-card:nth-child(2n) :global(.card) {\n\t\t--sl-card-bg: var(--sl-color-green-low);\n\t\t--sl-card-border: var(--sl-color-green-high);\n\t\t--landing-card-accent: var(--sl-color-green);\n\t}\n\n\t.landing-card :global(.card) {\n\t\tbackground: var(--card-bg);\n\t\tposition: relative;\n\t}\n\n\t@media screen and (min-width: 50rem) {\n\t\t.landing-card :global(.card::before) {\n\t\t\tcontent: '';\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tinset-inline-end: 0;\n\t\t\twidth: 6.25rem;\n\t\t\theight: 6.25rem;\n\t\t\tbackground: var(--landing-card-accent, var(--sl-color-accent));\n\t\t\tborder-radius: 100%;\n\t\t\tfilter: blur(7rem);\n\t\t\topacity: 0.33;\n\t\t}\n\n\t\t.landing-card :global(.card) {\n\t\t\t--sl-card-bg: var(--sl-color-blue-low);\n\t\t\t--sl-card-border: var(--sl-color-blue-high);\n\t\t\t--landing-card-accent: hsl(185, 90%, 46%);\n\t\t}\n\n\t\t.landing-card:nth-child(2n) :global(.card) {\n\t\t\t--sl-card-bg: var(--sl-color-green-low);\n\t\t\t--sl-card-border: var(--sl-color-green-high);\n\t\t\t--landing-card-accent: var(--sl-color-green);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "docs/src/components/landing/Discord.astro",
    "content": "---\nexport type Props = {\n\tjoinDiscord: string;\n};\n\nimport { LinkCard } from '@astrojs/starlight/components';\nimport { Image } from 'astro:assets';\nimport BlocLogo from '~/assets/bloc.svg';\n\nconst { joinDiscord = 'Join our Discord' } = Astro.props;\n---\n\n<style>\n\t.discord {\n\t\tmargin: 0 auto;\n\t\tpadding: 4rem 0;\n\t\ttext-align: center;\n\t}\n\n\t.discord h2 {\n\t\tfont-size: var(--sl-text-2xl) !important;\n\t\tfont-weight: 400;\n\t}\n\n\t.discord .cta {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t}\n\n\t.hide {\n\t\tmargin-bottom: -1rem;\n\t\twidth: 7rem;\n\t\theight: 7rem;\n\t}\n\n\t@media screen and (min-width: 50rem) {\n\t\t.glow {\n\t\t\tfilter: drop-shadow(0 0 0.5rem #81d9ef);\n\t\t}\n\t}\n</style>\n\n<div class=\"discord\">\n\t<div class=\"cta\">\n\t\t<Image src={BlocLogo} alt=\"bloc logo\" class=\"hide glow\" />\n\t\t<LinkCard title={joinDiscord} href=\"https://discord.gg/bloc\" />\n\t</div>\n</div>\n"
  },
  {
    "path": "docs/src/components/landing/ListCard.astro",
    "content": "---\nimport Card from './Card.astro';\nexport type Props = Parameters<typeof Card>[0];\n---\n\n<Card {...Astro.props}>\n\t<div class=\"link-list\">\n\t\t<slot />\n\t</div>\n</Card>\n\n<style>\n\t.link-list :global(ul) {\n\t\tlist-style-type: none;\n\t\tpadding: 0;\n\t}\n\n\t.link-list :global(li) {\n\t\tborder-bottom: 1px solid var(--sl-color-gray-6);\n\t\tpadding: 0.25rem 0;\n\t\tmargin: 0 !important;\n\t}\n\n\t.link-list :global(li:last-child) {\n\t\tborder-bottom: none;\n\t}\n\n\t.link-list :global(a) {\n\t\tdisplay: flex;\n\t\tgap: 0.5rem;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\ttext-decoration: none;\n\t}\n\n\t/* Add arrow icon to links. */\n\t.link-list :global(a::after) {\n\t\tcontent: '';\n\t\tbackground-color: currentColor;\n\t\twidth: 1rem;\n\t\theight: 1rem;\n\t\tflex-shrink: 0;\n\t\tmask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z'%3E%3C/path%3E%3C/svg%3E\");\n\t\t-webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z'%3E%3C/path%3E%3C/svg%3E\");\n\t\tmask-size: 100%;\n\t\t-webkit-mask-size: 100%;\n\t}\n\t/* Mirror arrow in RTL layouts */\n\t:global([dir='rtl']) .link-list :global(a::after:not(:where([dir='rtl'] [dir='ltr'] *))) {\n\t\ttransform: matrix(-1, 0, 0, 1, 0, 0);\n\t}\n</style>\n"
  },
  {
    "path": "docs/src/components/landing/SplitCard.astro",
    "content": "---\nimport { Card } from '@astrojs/starlight/components';\n\nexport type Props = Parameters<typeof Card>[0];\n---\n\n<div class=\"card--fullwidth\">\n\t<Card {...Astro.props}>\n\t\t<div class=\"split\">\n\t\t\t<slot />\n\t\t</div>\n\t</Card>\n</div>\n\n<style>\n\t.card--fullwidth {\n\t\t--card-bg: rgba(23, 25, 30, 0.8);\n\t\tgrid-column: 1 / -1;\n\t}\n\t:global(:root[data-theme='light']) .card--fullwidth {\n\t\t--card-bg: rgba(255, 255, 255, 0.5);\n\t}\n\n\t.split {\n\t\tmargin-top: 1rem;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 0 1rem;\n\t\tcolor: var(--sl-color-white);\n\t}\n\n\t.card--fullwidth :global(.card) {\n\t\tbackground: var(--card-bg);\n\t\tposition: relative;\n\t}\n\n\t@media screen and (min-width: 78rem) {\n\t\t.split {\n\t\t\tflex-direction: row;\n\t\t\tgap: 2.5rem;\n\t\t}\n\t\t.split > :global(*) {\n\t\t\tmargin-top: 0 !important;\n\t\t\tflex: 1;\n\t\t}\n\t}\n\n\t@media screen and (min-width: 50rem) {\n\t\t.card--fullwidth :global(.card) {\n\t\t\tbackground:\n\t\t\t\tradial-gradient(var(--sl-color-orange-low), transparent 70%) no-repeat 70% -25% / 120% 350%,\n\t\t\t\tvar(--card-bg);\n\t\t\tposition: relative;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "docs/src/components/landing/SponsorsGrid.astro",
    "content": "---\nexport type Props = {\n\tsponsoredBy: string;\n\tbecomeASponsor: string;\n};\n\nimport { Image } from 'astro:assets';\nimport { LinkCard } from '@astrojs/starlight/components';\n\nimport shorebirdLight from '~/assets/sponsors/shorebird-light.png';\nimport shorebirdDark from '~/assets/sponsors/shorebird-dark.png';\nimport stream from '~/assets/sponsors/stream.png';\nimport rettel from '~/assets/sponsors/rettel.png';\n\nconst { sponsoredBy = 'Sponsored with 💖 by', becomeASponsor = 'Become a Sponsor' } = Astro.props;\n\ninterface DynamicImageMetadata {\n\tlight: ImageMetadata;\n\tdark: ImageMetadata;\n}\ninterface Sponsor {\n\timg: DynamicImageMetadata | ImageMetadata;\n\thref: string;\n\talt: string;\n\tinvert: boolean;\n}\n\nconst sponsors: Sponsor[] = [\n\t{\n\t\talt: 'Shorebird Logo',\n\t\timg: {\n\t\t\tlight: shorebirdLight,\n\t\t\tdark: shorebirdDark,\n\t\t},\n\t\thref: 'https://shorebird.dev',\n\t\tinvert: false,\n\t},\n\t{\n\t\talt: 'Stream Logo',\n\t\timg: stream,\n\t\thref: 'https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc',\n\t\tinvert: true,\n\t},\n\t{\n\t\talt: 'Rettel Logo',\n\t\timg: rettel,\n\t\thref: 'https://rettelgame.com',\n\t\tinvert: true,\n\t},\n];\n\nfunction isDynamicImage(\n\tvalue: DynamicImageMetadata | ImageMetadata\n): value is DynamicImageMetadata {\n\treturn value.hasOwnProperty('light') && value.hasOwnProperty('dark');\n}\n---\n\n<style>\n\t:global([data-theme='dark']) .invert-logo {\n\t\tfilter: invert() grayscale() contrast(200%) saturate(200%);\n\t}\n\t:global([data-theme='dark']) .logo {\n\t\tfilter: grayscale() contrast(200%) saturate(200%);\n\t}\n</style>\n\n<div class=\"flex w-full justify-center no-content\">\n\t<p class=\"text-xl font-semibold\">{sponsoredBy}</p>\n</div>\n<div class=\"flex w-full justify-center items-center not-content\">\n\t<div class=\"grid grid-cols-1 sm:gap-4 sm:grid-cols-3\">\n\t\t{\n\t\t\tsponsors.map((sponsor) => {\n\t\t\t\treturn (\n\t\t\t\t\t<div class=\"flex flex-row justify-center items-center\">\n\t\t\t\t\t\t<a href={sponsor.href} aria-label={sponsor.alt}>\n\t\t\t\t\t\t\t{isDynamicImage(sponsor.img) ? (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\t\tclass={`${sponsor.invert ? 'invert-logo' : 'logo'} dark:sl-hidden`}\n\t\t\t\t\t\t\t\t\t\tsrc={sponsor.img.light}\n\t\t\t\t\t\t\t\t\t\twidth={300}\n\t\t\t\t\t\t\t\t\t\tstyle=\"width:150px;height:auto\"\n\t\t\t\t\t\t\t\t\t\talt={sponsor.alt}\n\t\t\t\t\t\t\t\t\t\tloading=\"eager\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\t\tclass={`${sponsor.invert ? 'invert-logo' : 'logo'} light:sl-hidden`}\n\t\t\t\t\t\t\t\t\t\tsrc={sponsor.img.dark}\n\t\t\t\t\t\t\t\t\t\twidth={300}\n\t\t\t\t\t\t\t\t\t\tstyle=\"width:150px;height:auto\"\n\t\t\t\t\t\t\t\t\t\talt={sponsor.alt}\n\t\t\t\t\t\t\t\t\t\tloading=\"eager\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\tclass={sponsor.invert ? 'invert-logo' : 'logo'}\n\t\t\t\t\t\t\t\t\tsrc={sponsor.img}\n\t\t\t\t\t\t\t\t\twidth={300}\n\t\t\t\t\t\t\t\t\tstyle=\"width:150px;height:auto\"\n\t\t\t\t\t\t\t\t\talt={sponsor.alt}\n\t\t\t\t\t\t\t\t\tloading=\"eager\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t})\n\t\t}\n\t</div>\n</div>\n\n<div class=\"w-full flex text-center items-center justify-center\">\n\t<LinkCard\n\t\ttitle={becomeASponsor}\n\t\thref=\"https://github.com/sponsors/felangel\"\n\t\ttarget=\"_blank\"\n\t\trel=\"noreferrer\"\n\t/>\n</div>\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nbloc:\n  rules:\n    - avoid_flutter_imports\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintChangingSeveritySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nbloc:\n  rules:\n    avoid_flutter_imports: info\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintDisablingRulesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ninclude: package:bloc_lint/recommended.yaml\nbloc:\n  rules:\n    avoid_public_bloc_methods: false\n    prefer_bloc: true\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintEnablingRulesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nbloc:\n  rules:\n    - avoid_flutter_imports\n    - avoid_public_bloc_methods\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintExcludingFilesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ninclude: package:bloc_lint/recommended.yaml\nanalyzer:\n  exclude:\n    - \"**.g.dart\"\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintIgnoreForFileSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n// ignore_for_file: prefer_file_naming_conventions\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {}\n\nenum CounterEvent { increment, decrement }\n\nclass CounterBloc extends Bloc<CounterEvent, int> {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintIgnoreForLineSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {} // ignore: prefer_file_naming_conventions\n\nenum CounterEvent { increment, decrement }\n\n// ignore: prefer_file_naming_conventions\nclass CounterBloc extends Bloc<CounterEvent, int> {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"main.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ninclude:\n    - package:lints/recommended.yaml\n    - package:bloc_lint/recommended.yaml\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ninclude: package:bloc_lint/recommended.yaml\n`;\n---\n\n<Code code={code} lang=\"yaml\" title=\"analysis_options.yaml\" />\n"
  },
  {
    "path": "docs/src/components/lint/BlocToolsLintHelpOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n$ bloc lint --help\nLint Dart source code.\n\nUsage: bloc lint [arguments]\n-h, --help    Print this usage information.\n\nRun \"bloc help\" to see global options.\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/ImportFlutterInfoOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ninfo[avoid_flutter_imports]: Avoid importing Flutter within bloc instances.\n --> counter_cubit.dart:2\n  |\n  | import 'package:flutter/material.dart';\n  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  = hint: Blocs should be decoupled from Flutter.\n docs: https://bloclibrary.dev/lint-rules/avoid_flutter_imports\n\n1 issue found\nAnalyzed 1 file\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/ImportFlutterInfoSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n  code={`\n  import 'package:bloc/bloc.dart';\n  import 'packages:flutter/material.dart';\n\n  class CounterCubit extends Cubit<int> {\n    CounterCubit() : super(0);\n  }\n`}\n\tlang=\"dart\"\n\ttitle=\"counter_cubit.dart\"\n\ttransformers={[transformerMetaHighlight()]}\n\tclass=\"info\"\n\tmeta=\"{2}\"\n/>\n\n\n"
  },
  {
    "path": "docs/src/components/lint/ImportFlutterWarningOutputSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nwarning[avoid_flutter_imports]: Avoid importing Flutter within bloc instances.\n --> counter_cubit.dart:2\n  |\n  | import 'package:flutter/material.dart';\n  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  = hint: Blocs should be decoupled from Flutter.\n docs: https://bloclibrary.dev/lint-rules/avoid_flutter_imports\n\n1 issue found\nAnalyzed 1 file\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/ImportFlutterWarningSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n  code={`\n  import 'package:bloc/bloc.dart';\n  import 'packages:flutter/material.dart';\n\n  class CounterCubit extends Cubit<int> {\n    CounterCubit() : super(0);\n  }\n`}\n\tlang=\"dart\"\n\ttitle=\"counter_cubit.dart\"\n\ttransformers={[transformerMetaHighlight()]}\n\tclass=\"warning\"\n\tmeta=\"{2}\"\n/>\n\n\n"
  },
  {
    "path": "docs/src/components/lint/InstallBlocLintSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `dart pub add --dev bloc_lint`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/InstallBlocToolsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `dart pub global activate bloc_tools`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/RunBlocLintCounterCubitSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `bloc lint counter_cubit.dart`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/RunBlocLintInCurrentDirectorySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `bloc lint .`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint/RunBlocLintInSrcTestSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `bloc lint src/ test/`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/EnableRuleSnippet.astro",
    "content": "---\nimport { Code, TabItem, Tabs } from '@astrojs/starlight/components';\nconst { name } = Astro.props;\n\nconst yamlListCode = `\nbloc:\n  rules:\n    - ${name}\n`;\nconst yamlMapCode = `\nbloc:\n  rules:\n    ${name}: true\n`;\n---\n\n<Tabs>\n\t<TabItem label=\"YAML List\">\n\t\t<Code code={yamlListCode} lang=\"yaml\" title=\"analysis_options.yaml\" />\n\t</TabItem>\n\t<TabItem label=\"YAML Map\">\n\t\t<Code code={yamlMapCode} lang=\"yaml\" title=\"analysis_options.yaml\" />\n\t</TabItem>\n</Tabs>\n"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:flutter/material.dart';\n  import 'package:flutter_bloc/flutter_bloc.dart';\n\n  class CounterPage extends StatelessWidget {\n    const CounterPage({super.key});\n\n    @override\n    Widget build(BuildContext context) {\n      final count = context.watch<CounterBloc>().state;\n\n      return Column(\n        children: [\n          Text('Count: $count'),\n          ElevatedButton(\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterIncrement());\n            },\n            child: const Text('Increment'),\n          ),\n        ],\n      );\n    }\n  }\n`}\nlang=\"dart\" title=\"counter_page.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{9,16}\"\n/> \n"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      children: [\n        BlocBuilder<CounterBloc, int>(\n          builder: (context, state) => Text('Count: \\$state'),\n        ),\n        ElevatedButton(\n          onPressed: () {\n            BlocProvider.of<CounterBloc>(context).add(CounterIncrement());\n          },\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n  \n  // Avoid depending on Flutter!\n  // Prefer to keep business logic decoupled from the UI layer.\n  import 'packages:flutter/material.dart';\n\n  class CounterCubit extends Cubit<int> { \n    CounterCubit() : super(0);\n  }\n`}\nlang=\"dart\" title=\"counter_cubit.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{5}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> { \n  CounterCubit() : super(0);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n\n  enum CounterEvent { increment };\n\n  class CounterBloc extends Bloc<CounterEvent, int> { \n    CounterBloc() : super(0) {\n      on<CounterEvent>((event, emit) => emit(state + 1));\n    }\n\n    // Avoid public bloc methods!\n    // Prefer to use [add] directly.\n    void increment() => add(CounterEvent.increment);\n  }\n`}\nlang=\"dart\" title=\"counter_bloc.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{12}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment };\n\nclass CounterBloc extends Bloc<CounterEvent, int> { \n  CounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) => emit(state + 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_public_fields/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n\n  enum CounterEvent { increment };\n\n  class CounterBloc extends Bloc<CounterEvent, int> { \n    CounterBloc() : super(0) {\n      on<CounterEvent>((event, emit) => emit(state + 1));\n    }\n\n    // Avoid public fields!\n    // Prefer to keep all external state in the [state] object.\n    int count = 0;\n  }\n`}\nlang=\"dart\" title=\"counter_bloc.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{12}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/avoid_public_fields/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment };\n\nclass CounterBloc extends Bloc<CounterEvent, int> { \n  CounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) => emit(state + 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_bloc/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n  \n  class CounterCubit extends Cubit<int> { \n    CounterCubit() : super(0);\n\n    void increment() => emit(state + 1);\n  }\n`}\nlang=\"dart\" title=\"counter_cubit.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{3}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_bloc/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment };\n\nclass CounterBloc extends Bloc<CounterEvent, int> { \n  CounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) => emit(state + 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:flutter/material.dart';\n  import 'package:flutter_bloc/flutter_bloc.dart';\n\n  class CounterPage extends StatelessWidget {\n    const CounterPage({super.key});\n\n    @override\n    Widget build(BuildContext context) {\n      return Column(\n        children: [\n          BlocBuilder<CounterBloc, int>(\n            builder: (context, state) => Text('Count: \\$state'),\n          ),\n          ElevatedButton(\n            onPressed: () {\n              BlocProvider.of<CounterBloc>(context).add(CounterIncrement());\n            },\n            child: const Text('Increment'),\n          ),\n        ],\n      );\n    }\n  }\n`}\nlang=\"dart\" title=\"counter_page.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{11,16}\"\n/> \n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final count = context.watch<CounterBloc>().state;\n\n    return Column(\n      children: [\n        Text('Count: $count'),\n        ElevatedButton(\n          onPressed: () {\n            context.read<CounterBloc>().add(CounterIncrement());\n          },\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_cubit/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n\n  enum CounterEvent { increment };\n\n  class CounterBloc extends Bloc<CounterEvent, int> { \n    CounterBloc() : super(0) {\n      on<CounterEvent>((event, emit) => emit(state + 1));\n    }\n  }\n`}\nlang=\"dart\" title=\"counter_bloc.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{5}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_cubit/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> { \n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n\n  class CounterCubit extends Cubit<int> { \n    CounterCubit() : super(0);\n\n    int increment() {\n      emit(state + 1);\n      return state;\n    }\n  }\n`}\nlang=\"dart\" title=\"main.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{3}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> { \n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx",
    "content": "import { Code } from '@astrojs/starlight/components';\nimport { transformerMetaHighlight } from '@shikijs/transformers';\n\n<Code\n\tcode={`\n  import 'package:bloc/bloc.dart';\n\n  class CounterCubit extends Cubit<int> { \n    CounterCubit() : super(0);\n\n    int increment() {\n      emit(state + 1);\n      return state;\n    }\n  }\n`}\nlang=\"dart\" title=\"counter_cubit.dart\"\ntransformers={[transformerMetaHighlight()]} class='warning' meta=\"{6}\"\n/>"
  },
  {
    "path": "docs/src/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> { \n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_cubit.dart\" />\n"
  },
  {
    "path": "docs/src/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nenum TodoStatus { initial, loading, success, failure }\nfinal class TodoState {\n  const TodoState({\n    this.status = TodoStatus.initial,\n    this.todos = const <Todo>[],\n    this.exception = null,\n  });\n  final TodoStatus status;\n  final List<Todos> todos;\n  final Exception? exception;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"todo_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/modeling-state/SealedClassAndSubclassesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class WeatherState {\n  const WeatherState();\n}\nfinal class WeatherInitial extends WeatherState {\n  const WeatherInitial();\n}\nfinal class WeatherLoadInProgress extends WeatherState {\n  const WeatherLoadInProgress();\n}\nfinal class WeatherLoadSuccess extends WeatherState {\n  const WeatherLoadSuccess({required this.weather});\n  final Weather weather;\n}\nfinal class WeatherLoadFailure extends WeatherState {\n  const WeatherLoadFailure({required this.exception});\n  final Exception exception;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"weather_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/naming-conventions/EventExamplesBad1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\nfinal class Initial extends CounterEvent {}\nfinal class CounterInitialized extends CounterEvent {}\nfinal class Increment extends CounterEvent {}\nfinal class DoIncrement extends CounterEvent {}\nfinal class IncrementCounter extends CounterEvent {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_event.dart\" />\n"
  },
  {
    "path": "docs/src/components/naming-conventions/EventExamplesGood1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\nfinal class CounterStarted extends CounterEvent {}\nfinal class CounterIncrementPressed extends CounterEvent {}\nfinal class CounterDecrementPressed extends CounterEvent {}\nfinal class CounterIncrementRetried extends CounterEvent {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_event.dart\" />\n"
  },
  {
    "path": "docs/src/components/naming-conventions/SingleStateExamplesGood1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nenum CounterStatus { initial, loading, success, failure }\nfinal class CounterState {\n  const CounterState({this.status = CounterStatus.initial});\n  final CounterStatus status;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/naming-conventions/StateExamplesBad1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterState {}\nfinal class Initial extends CounterState {}\nfinal class Loading extends CounterState {}\nfinal class Success extends CounterState {}\nfinal class Succeeded extends CounterState {}\nfinal class Loaded extends CounterState {}\nfinal class Failure extends CounterState {}\nfinal class Failed extends CounterState {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/naming-conventions/StateExamplesGood1Snippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterState {}\nfinal class CounterInitial extends CounterState {}\nfinal class CounterLoadInProgress extends CounterState {}\nfinal class CounterLoadSuccess extends CounterState {}\nfinal class CounterLoadFailure extends CounterState {}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_state.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/AddDevDependenciesSnippet.astro",
    "content": "---\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\nconst dartSnippet = `\ndart pub add dev:test dev:bloc_test\n`;\nconst flutterSnippet = `\nflutter pub add dev:test dev:bloc_test\n`;\n---\n\n<Tabs>\n\t<TabItem label=\"Dart\">\n\t\t<Code code={dartSnippet} lang=\"bash\" />\n\t</TabItem>\n\t<TabItem label=\"Flutter\">\n\t\t<Code code={flutterSnippet} lang=\"bash\" />\n\t</TabItem>\n</Tabs>\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nfinal class CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocTestBlocTestSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nblocTest(\n  'emits [1] when CounterIncrementPressed is added',\n  build: () => counterBloc,\n  act: (bloc) => bloc.add(CounterIncrementPressed()),\n  expect: () => [1],\n);\n\nblocTest(\n  'emits [-1] when CounterDecrementPressed is added',\n  build: () => counterBloc,\n  act: (bloc) => bloc.add(CounterDecrementPressed()),\n  expect: () => [-1],\n);\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocTestImportsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:test/test.dart';\nimport 'package:bloc_test/bloc_test.dart';\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocTestInitialStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ngroup(CounterBloc, () {\n  late CounterBloc counterBloc;\n\n  setUp(() {\n    counterBloc = CounterBloc();\n  });\n\n  test('initial state is 0', () {\n    expect(counterBloc.state, equals(0));\n  });\n});\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocTestMainSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvoid main() {\n  group(CounterBloc, () {\n\n  });\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/testing/CounterBlocTestSetupSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ngroup(CounterBloc, () {\n  late CounterBloc counterBloc;\n\n  setUp(() {\n    counterBloc = CounterBloc();\n  });\n});\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"counter_bloc_test.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/FlutterPubGetSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter pub get\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-counter/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_counter\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_firebase_login\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_infinite_list\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter pub get\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport 'package:http/http.dart' as http;\n\nimport 'package:flutter_infinite_list/bloc/bloc.dart';\nimport 'package:flutter_infinite_list/post.dart';\n\npart 'post_event.dart';\npart 'post_state.dart';\n\nclass PostBloc extends Bloc<PostEvent, PostState> {\n  PostBloc({required this.httpClient}) : super(const PostState()) {\n   /// TODO: register on<PostFetched> event\n  }\n\n  final http.Client httpClient;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/posts/bloc/post_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nPostBloc({required this.httpClient}) : super(const PostState()) {\n  on<PostFetched>(_onFetched);\n}\n\nFuture<void> _onFetched(PostFetched event, Emitter<PostState> emit) async {\n  if (state.hasReachedMax) return;\n\n  try {\n    final posts = await _fetchPosts(startIndex: state.posts.length);\n\n    if (posts.isEmpty) {\n      return emit(state.copyWith(hasReachedMax: true));\n    }\n\n    emit(\n      state.copyWith(\n        status: PostStatus.success,\n        posts: [...state.posts, ...posts],\n      ),\n    );\n  } catch (_) {\n    emit(state.copyWith(status: PostStatus.failure));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/posts/bloc/post_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:stream_transform/stream_transform.dart';\n\nconst throttleDuration = Duration(milliseconds: 100);\n\nEventTransformer<E> throttleDroppable<E>(Duration duration) {\n  return (events, mapper) {\n    return droppable<E>().call(events.throttle(duration), mapper);\n  };\n}\n\nclass PostBloc extends Bloc<PostEvent, PostState> {\n  PostBloc({required this.httpClient}) : super(const PostState()) {\n    on<PostFetched>(\n      _onFetched,\n      transformer: throttleDroppable(throttleDuration),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/posts/bloc/post_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n[\n  {\n    \"userId\": 1,\n    \"id\": 1,\n    \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\n    \"body\": \"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"\n  },\n  {\n    \"userId\": 1,\n    \"id\": 2,\n    \"title\": \"qui est esse\",\n    \"body\": \"est rerum tempore vitae\\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\\nqui aperiam non debitis possimus qui neque nisi nulla\"\n  }\n]\n`;\n---\n\n<Code code={code} lang=\"json\" frame=\"none\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-login/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_login\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/ActionsSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass Actions extends StatelessWidget {\n  const Actions({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<TimerBloc, TimerState>(\n      buildWhen: (prev, state) => prev.runtimeType != state.runtimeType,\n      builder: (context, state) {\n        return Row(\n          mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n          children: [\n            ...switch (state) {\n              TimerInitial() => [\n                  FloatingActionButton(\n                    child: const Icon(Icons.play_arrow),\n                    onPressed: () => context\n                        .read<TimerBloc>()\n                        .add(TimerStarted(duration: state.duration)),\n                  ),\n                ],\n              TimerRunInProgress() => [\n                  FloatingActionButton(\n                    child: const Icon(Icons.pause),\n                    onPressed: () =>\n                        context.read<TimerBloc>().add(const TimerPaused()),\n                  ),\n                  FloatingActionButton(\n                    child: const Icon(Icons.replay),\n                    onPressed: () =>\n                        context.read<TimerBloc>().add(const TimerReset()),\n                  ),\n                ],\n              TimerRunPause() => [\n                  FloatingActionButton(\n                    child: const Icon(Icons.play_arrow),\n                    onPressed: () =>\n                        context.read<TimerBloc>().add(const TimerResumed()),\n                  ),\n                  FloatingActionButton(\n                    child: const Icon(Icons.replay),\n                    onPressed: () =>\n                        context.read<TimerBloc>().add(const TimerReset()),\n                  ),\n                ],\n              TimerRunComplete() => [\n                  FloatingActionButton(\n                    child: const Icon(Icons.replay),\n                    onPressed: () =>\n                        context.read<TimerBloc>().add(const TimerReset()),\n                  ),\n                ]\n            }\n          ],\n        );\n      },\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/view/timer_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/BackgroundSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass Background extends StatelessWidget {\n  const Background({Key? key}) : super(key: key);\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      decoration: BoxDecoration(\n        gradient: LinearGradient(\n          begin: Alignment.topCenter,\n          end: Alignment.bottomCenter,\n          colors: [\n            Colors.blue.shade50,\n            Colors.blue.shade500,\n          ],\n        ),\n      ),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/view/timer_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_timer\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  // TODO: set initial state\n  TimerBloc(): super() {\n    // TODO: implement event handlers\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:bloc/bloc.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  static const int _duration = 60;\n\n  TimerBloc() : super(TimerInitial(_duration)) {\n    // TODO: implement event handlers\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'dart:async';\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n   TimerBloc({required Ticker ticker})\n      : _ticker = ticker,\n        super(TimerInitial(_duration)) {\n    on<TimerStarted>(_onStarted);\n    on<TimerPaused>(_onPaused);\n    on<_TimerTicked>(_onTicked);\n  }\n\n  @override\n  Future<void> close() {\n    _tickerSubscription?.cancel();\n    return super.close();\n  }\n\n  void _onStarted(TimerStarted event, Emitter<TimerState> emit) {\n    emit(TimerRunInProgress(event.duration));\n    _tickerSubscription?.cancel();\n    _tickerSubscription = _ticker\n        .tick(ticks: event.duration)\n        .listen((duration) => add(_TimerTicked(duration: duration)));\n  }\n\n  void _onPaused(TimerPaused event, Emitter<TimerState> emit) {\n    if (state is TimerRunInProgress) {\n      _tickerSubscription?.pause();\n      emit(TimerRunPause(state.duration));\n    }\n  }\n\n  void _onTicked(_TimerTicked event, Emitter<TimerState> emit) {\n    emit(\n      event.duration > 0\n          ? TimerRunInProgress(event.duration)\n          : TimerRunComplete(),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'dart:async';\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n  TimerBloc({required Ticker ticker})\n      : _ticker = ticker,\n        super(TimerInitial(_duration)) {\n    on<TimerStarted>(_onStarted);\n    on<TimerPaused>(_onPaused);\n    on<TimerResumed>(_onResumed);\n    on<_TimerTicked>(_onTicked);\n  }\n\n  @override\n  Future<void> close() {\n    _tickerSubscription?.cancel();\n    return super.close();\n  }\n\n  void _onStarted(TimerStarted event, Emitter<TimerState> emit) {\n    emit(TimerRunInProgress(event.duration));\n    _tickerSubscription?.cancel();\n    _tickerSubscription = _ticker\n        .tick(ticks: event.duration)\n        .listen((duration) => add(_TimerTicked(duration: duration)));\n  }\n\n  void _onPaused(TimerPaused event, Emitter<TimerState> emit) {\n    if (state is TimerRunInProgress) {\n      _tickerSubscription?.pause();\n      emit(TimerRunPause(state.duration));\n    }\n  }\n\n  void _onResumed(TimerResumed resume, Emitter<TimerState> emit) {\n    if (state is TimerRunPause) {\n      _tickerSubscription?.resume();\n      emit(TimerRunInProgress(state.duration));\n    }\n  }\n\n  void _onTicked(_TimerTicked event, Emitter<TimerState> emit) {\n    emit(\n      event.duration > 0\n          ? TimerRunInProgress(event.duration)\n          : TimerRunComplete(),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'dart:async';\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n  TimerBloc({required Ticker ticker})\n      : _ticker = ticker,\n        super(TimerInitial(_duration)) {\n    on<TimerStarted>(_onStarted);\n  }\n\n  @override\n  Future<void> close() {\n    _tickerSubscription?.cancel();\n    return super.close();\n  }\n\n  void _onStarted(TimerStarted event, Emitter<TimerState> emit) {\n    emit(TimerRunInProgress(event.duration));\n    _tickerSubscription?.cancel();\n    _tickerSubscription = _ticker\n        .tick(ticks: event.duration)\n        .listen((duration) => add(_TimerTicked(duration: duration)));\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'dart:async';\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n  TimerBloc({required Ticker ticker})\n      : _ticker = ticker,\n        super(TimerInitial(_duration)) {\n    on<TimerStarted>(_onStarted);\n    on<_TimerTicked>(_onTicked);\n  }\n\n  @override\n  Future<void> close() {\n    _tickerSubscription?.cancel();\n    return super.close();\n  }\n\n  void _onStarted(TimerStarted event, Emitter<TimerState> emit) {\n    emit(TimerRunInProgress(event.duration));\n    _tickerSubscription?.cancel();\n    _tickerSubscription = _ticker\n        .tick(ticks: event.duration)\n        .listen((duration) => add(_TimerTicked(duration: duration)));\n  }\n\n  void _onTicked(_TimerTicked event, Emitter<TimerState> emit) {\n    emit(\n      event.duration > 0\n          ? TimerRunInProgress(event.duration)\n          : TimerRunComplete(),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'dart:async';\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n  TimerBloc({required Ticker ticker})\n      : _ticker = ticker,\n        super(TimerInitial(_duration)) {\n    // TODO: implement event handlers\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/bloc/timer_bloc.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-timer/TimerPageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\nimport 'package:flutter_timer/timer/timer.dart';\n\nclass TimerPage extends StatelessWidget {\n  const TimerPage({Key? key}) : super(key: key);\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => TimerBloc(ticker: Ticker()),\n      child: const TimerView(),\n    );\n  }\n}\n\nclass TimerView extends StatelessWidget {\n  const TimerView({Key? key}) : super(key: key);\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Flutter Timer')),\n      body: Stack(\n        children: [\n          const Background(),\n          Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            crossAxisAlignment: CrossAxisAlignment.center,\n            children: const <Widget>[\n              Padding(\n                padding: EdgeInsets.symmetric(vertical: 100.0),\n                child: Center(child: TimerText()),\n              ),\n              Actions(),\n            ],\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass TimerText extends StatelessWidget {\n  const TimerText({Key? key}) : super(key: key);\n  @override\n  Widget build(BuildContext context) {\n    final duration = context.select((TimerBloc bloc) => bloc.state.duration);\n    final minutesStr =\n        ((duration / 60) % 60).floor().toString().padLeft(2, '0');\n    final secondsStr = (duration % 60).floor().toString().padLeft(2, '0');\n    return Text(\n      '$minutesStr:$secondsStr',\n      style: Theme.of(\n        context,\n      ).textTheme.displayLarge?.copyWith(fontWeight: FontWeight.w500),\n    );\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/timer/view/timer_page.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart pub global activate very_good_cli\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n├── BlocProvider<EditTodosBloc>\n│   └── EditTodosPage\n│       └── BlocListener<EditTodosBloc>\n│           └── EditTodosView\n│               ├── TitleField\n│               ├── DescriptionField\n│               └── Floating Action Button\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n# create package:todos_api under packages/todos_api\nvery_good create dart_package todos_api --desc \"The interface and models for an API providing access to todos.\" -o packages\n\n# create package:local_storage_todos_api under packages/local_storage_todos_api\nvery_good create flutter_package local_storage_todos_api --desc \"A Flutter implementation of the TodosApi that uses local storage.\" -o packages\n\n# create package:todos_repository under packages/todos_repository\nvery_good create dart_package todos_repository --desc \"A repository that handles todo related requests.\" -o packages\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvery_good create flutter_app flutter_todos --desc \"An example todos app that showcases bloc state management patterns.\"\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/HomePageTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n├── HomePage\n│   └── BlocProvider<HomeCubit>\n│       └── HomeView\n│           ├── context.select<HomeCubit, HomeTab>\n│           └── BottomAppBar\n│               └── HomeTabButton(s)\n│                   └── context.read<HomeCubit>\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/ProjectStructureSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n├── lib\n├── packages\n│   ├── local_storage_todos_api\n│   ├── todos_api\n│   └── todos_repository\n└── test\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n├── StatsPage\n│   └── BlocProvider<StatsBloc>\n│       └── StatsView\n│           ├── context.watch<StatsBloc>\n│           └── Column\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n├── TodosOverviewPage\n│   └── BlocProvider<TodosOverviewBloc>\n│       └── TodosOverviewView\n│           ├── BlocListener<TodosOverviewBloc>\n│           └── BlocListener<TodosOverviewBloc>\n│               └── BlocBuilder<TodosOverviewBloc>\n│                   └── ListView\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nvery_good packages get --recursive\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart run build_runner build\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/FeatureTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n  |-- search/\n  |-- settings/\n  |-- theme/\n  |-- weather/\n  |-- main.dart\n|-- test/\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create --template=package open_meteo_api\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create --template=package weather_repository\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_weather\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter test --coverage\ngenhtml coverage/lcov.info -o coverage\nopen coverage/index.html\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n/// Fetches [Weather] for a given [latitude] and [longitude].\nFuture<Weather> getWeather({\n  required double latitude,\n  required double longitude,\n}) async {\n  final weatherRequest = Uri.https(_baseUrlWeather, 'v1/forecast', {\n    'latitude': '$latitude',\n    'longitude': '$longitude',\n    'current_weather': 'true'\n  });\n\n  final weatherResponse = await _httpClient.get(weatherRequest);\n\n  if (weatherResponse.statusCode != 200) {\n    throw WeatherRequestFailure();\n  }\n\n  final bodyJson = jsonDecode(weatherResponse.body) as Map<String, dynamic>;\n\n  if (!bodyJson.containsKey('current_weather')) {\n    throw WeatherNotFoundFailure();\n  }\n\n  final weatherJson = bodyJson['current_weather'] as Map<String, dynamic>;\n\n  return Weather.fromJson(weatherJson);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/LocationDartSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass Location {\n  const Location({\n    required this.id,\n    required this.name,\n    required this.latitude,\n    required this.longitude,\n  });\n\n  final int id;\n  final String name;\n  final double latitude;\n  final double longitude;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/open_meteo_api/lib/src/models/location.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/LocationJsonSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n{\n  \"results\": [\n    {\n      \"id\": 4887398,\n      \"name\": \"Chicago\",\n      \"latitude\": 41.85003,\n      \"longitude\": -87.65005\n    }\n  ]\n}\n`;\n---\n\n<Code code={code} lang=\"json\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n/// Finds a [Location] \\`/v1/search/?name=(query)\\`.\nFuture<Location> locationSearch(String query) async {\n  final locationRequest = Uri.https(\n    _baseUrlGeocoding,\n    '/v1/search',\n    {'name': query, 'count': '1'},\n  );\n\n  final locationResponse = await _httpClient.get(locationRequest);\n\n  if (locationResponse.statusCode != 200) {\n    throw LocationRequestFailure();\n  }\n\n  final locationJson = jsonDecode(locationResponse.body) as Map;\n\n  if (!locationJson.containsKey('results')) throw LocationNotFoundFailure();\n\n  final results = locationJson['results'] as List;\n\n  if (results.isEmpty) throw LocationNotFoundFailure();\n\n  return Location.fromJson(results.first as Map<String, dynamic>);\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n|-- test/\n|-- packages/\n  |-- open_meteo_api/\n    |-- lib/\n      |-- src/\n        |-- models/\n          |-- location.dart\n          |-- location.g.dart\n          |-- weather.dart\n          |-- weather.g.dart\n          |-- models.dart\n        |-- open_meteo_api_client.dart\n      |-- open_meteo_api.dart\n    |-- test/\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nlibrary open_meteo_api;\n\nexport 'src/models/models.dart';\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/open_meteo_api/lib/open_meteo_api.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n|-- test/\n|-- packages/\n  |-- open_meteo_api/\n    |-- lib/\n      |-- src/\n        |-- models/\n          |-- location.dart\n          |-- weather.dart\n          |-- models.dart\n      |-- open_meteo_api.dart\n    |-- test/\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n|-- test/\n|-- packages/\n  |-- open_meteo_api/\n    |-- lib/\n      |-- src/\n        |-- models/\n          |-- location.dart\n          |-- weather.dart\n    |-- test/\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n|-- test/\n|-- packages/\n  |-- open_meteo_api/\n  |-- weather_repository/\n    |-- lib/\n      |-- src/\n        |-- models/\n          |-- models.dart\n          |-- weather.dart\n    |-- test/\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nexport 'models/models.dart';\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"lib/weather/weather.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter_weather\n|-- lib/\n  |-- weather/\n    |-- cubit/\n      |-- weather_cubit.dart\n      |-- weather_state.dart\n`;\n---\n\n<Code code={code} />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/WeatherDartSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nclass Weather {\n  const Weather({required this.temperature, required this.weatherCode});\n\n  final double temperature;\n  final double weatherCode;\n}\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/open_meteo_api/lib/src/models/weather.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/WeatherJsonSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\n{\n  \"current_weather\": {\n    \"temperature\": 15.3,\n    \"weathercode\": 63\n  }\n}\n`;\n---\n\n<Code code={code} lang=\"json\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nlibrary weather_repository;\n\nexport 'src/models/models.dart';\n`;\n---\n\n<Code code={code} lang=\"dart\" title=\"packages/weather_repository/lib/weather_repository.dart\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/github-search/ActivateStagehandSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart pub global activate stagehand\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/github-search/DartPubGetSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart pub get\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/github-search/FlutterCreateSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nflutter create flutter_github_search\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/github-search/SetupSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nmkdir -p github_search/common_github_search\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/github-search/StagehandSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nstagehand web-angular\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart pub global activate stagehand\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\ndart pub get\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/components/tutorials/ngdart-counter/StagehandSnippet.astro",
    "content": "---\nimport { Code } from '@astrojs/starlight/components';\n\nconst code = `\nstagehand web-angular\n`;\n---\n\n<Code code={code} lang=\"sh\" />\n"
  },
  {
    "path": "docs/src/content/config.ts",
    "content": "import { defineCollection } from 'astro:content';\nimport { docsSchema } from '@astrojs/starlight/schema';\n\nexport const collections = { docs: defineCollection({ schema: docsSchema() }) };\n"
  },
  {
    "path": "docs/src/content/docs/ar/architecture.mdx",
    "content": "---\ntitle: الهندسة المعمارية\ndescription: نظرة عامة على أنماط الهندسة المعمارية الموصى بها عند استخدام bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nيتيح لنا استخدام مكتبة bloc تقسيم التطبيق إلى ثلاث طبقات رئيسية:\n\n- طبقة العرض (Presentation)\n- طبقة منطق الأعمال (Business Logic)\n- طبقة البيانات (Data)\n  - المستودع (Repository)\n  - مزود البيانات (Data Provider)\n\nسنبدأ من الطبقة الأدنى (الأبعد عن واجهة المستخدم) ونتدرج صعودًا حتى نصل إلى طبقة\nالعرض.\n\n## طبقة البيانات (Data Layer)\n\nتتمثل مسؤولية طبقة البيانات في جلب البيانات ومعالجتها من مصدر واحد أو أكثر.\n\nيمكن تقسيم هذه الطبقة إلى جزأين:\n\n- المستودع (Repository)\n- مزود البيانات (Data Provider)\n\nتُعد هذه الطبقة الأدنى في التطبيق، وهي المسؤولة عن التفاعل مع قواعد البيانات،\nوطلبات الشبكة، ومصادر البيانات غير المتزامنة الأخرى.\n\n### مزود البيانات (Data Provider)\n\nتتمثل مهمة مزود البيانات في توفير البيانات الخام. يجب أن يكون عامًا ومرنًا\nوقابلًا لإعادة الاستخدام.\n\nعادةً ما يوفّر مزود البيانات واجهات برمجية بسيطة لتنفيذ\n[عمليات الإنشاء والقراءة والتحديث والحذف (CRUD)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete).\nقد يتضمن ذلك دوال مثل `createData` و `readData` و `updateData` و `deleteData`\nضمن طبقة البيانات.\n\n<DataProviderSnippet />\n\n### المستودع (Repository)\n\nتمثل طبقة المستودع غلافًا (wrapper) حول مزود بيانات واحد أو أكثر، وتتواصل معها\nطبقة الـ Bloc.\n\n<RepositorySnippet />\n\nكما نلاحظ، يمكن للمستودع التعامل مع عدة مزودي بيانات وإجراء تحويلات على البيانات\nقبل تمرير النتائج إلى طبقة منطق الأعمال.\n\n## طبقة منطق الأعمال (Business Logic Layer)\n\nتتمثل مسؤولية طبقة منطق الأعمال في الاستجابة لمدخلات طبقة العرض بإنتاج حالات\nجديدة. يمكن أن تعتمد هذه الطبقة على مستودع واحد أو أكثر للحصول على البيانات\nاللازمة لبناء حالة التطبيق.\n\nيمكن اعتبار هذه الطبقة جسرًا بين واجهة المستخدم (طبقة العرض) وطبقة البيانات. فهي\nتستقبل الأحداث أو الإجراءات من طبقة العرض، ثم تتواصل مع المستودع لبناء حالة\nجديدة تستهلكها طبقة العرض.\n\n<BusinessLogicComponentSnippet />\n\n### التواصل بين الـ Blocs (Bloc-to-Bloc Communication)\n\nنظرًا لأن الـ blocs تعتمد على الـ streams، قد يكون من المغري إنشاء bloc يستمع\nإلى bloc آخر. يجب **تجنب** هذا الأسلوب. توجد بدائل أفضل من اللجوء إلى الكود\nالتالي:\n\n<BlocTightCouplingSnippet />\n\nعلى الرغم من أن الكود أعلاه صحيح ويعالج الموارد بشكل مناسب، إلا أنه يخلق مشكلة\nأكبر: وهي إنشاء تبعية مباشرة بين bloc وآخر.\n\nبشكل عام، يجب تجنب التبعيات بين كيانات في نفس الطبقة المعمارية قدر الإمكان، لأن\nذلك يؤدي إلى اقتران محكم (tight coupling) يصعب صيانته. وبما أن الـ blocs تنتمي\nإلى طبقة منطق الأعمال، فلا ينبغي لأي bloc أن يعرف عن bloc آخر.\n\n![Application Architecture Layers](~/assets/architecture/architecture.png)\n\nيجب أن يتلقى الـ bloc المعلومات فقط عبر الأحداث (events) أو من خلال المستودعات\nالتي يتم حقنها فيه عبر المُنشئ (constructor).\n\nإذا كنت بحاجة إلى أن يستجيب bloc لآخر، فهناك خياران أفضل: إما رفع الحل إلى طبقة\nالعرض، أو نقله إلى طبقة النطاق (Domain).\n\n#### ربط الـ Blocs عبر طبقة العرض\n\nيمكن استخدام `BlocListener` للاستماع إلى bloc معين، وإضافة حدث إلى bloc آخر عند\nتغيّر حالته.\n\n<BlocLooseCouplingPresentationSnippet />\n\nيمنع هذا الأسلوب `SecondBloc` من معرفة أي شيء عن `FirstBloc`، مما يعزز الاقتران\nالمرن (loose coupling). يستخدم تطبيق\n[flutter_weather](/ar/tutorials/flutter-weather),\n[هذه التقنية](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nلتغيير سمة التطبيق بناءً على بيانات الطقس.\n\nفي بعض الحالات، قد لا يكون من المناسب الربط بين bloc وآخر في طبقة العرض. بدلاً\nمن ذلك، قد يكون من الأفضل أن يشتركا في نفس مصدر البيانات ويقوما بالتحديث عند\nتغيّرها.\n\n#### ربط الـ Blocs عبر طبقة النطاق (Domain)\n\nيمكن لبلوكين الاستماع إلى Stream من مستودع مشترك وتحديث حالاتهما بشكل مستقل عند\nتغير البيانات. يُعد هذا النهج شائعًا في التطبيقات المؤسسية واسعة النطاق.\n\nأولًا، أنشئ أو استخدم مستودعًا يوفر Stream للبيانات. على سبيل المثال، يوفر\nالمستودع التالي تدفقًا مستمرًا لبعض أفكار التطبيقات:\n\n<AppIdeasRepositorySnippet />\n\nيمكن حقن نفس المستودع في كل bloc يحتاج إلى الاستجابة لهذه البيانات. فيما يلي\n`AppIdeaRankingBloc` الذي يُنتج حالة لكل فكرة جديدة واردة من المستودع:\n\n<AppIdeaRankingBlocSnippet />\n\nلمزيد من التفاصيل حول استخدام streams مع Bloc، راجع:\n[كيفية استخدام Bloc مع الـ streams والتزامن](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## طبقة العرض (Presentation Layer)\n\nتتمثل مسؤولية طبقة العرض في تحديد كيفية عرض الواجهة بناءً على حالة bloc واحدة أو\nأكثر، بالإضافة إلى التعامل مع مدخلات المستخدم وأحداث دورة حياة التطبيق.\n\nغالبًا ما تبدأ تدفقات التطبيق بحدث `AppStart` الذي يؤدي إلى جلب البيانات الأولية\nلعرضها للمستخدم.\n\nفي هذا السيناريو، تقوم طبقة العرض بإضافة حدث `AppStart`.\n\nكما يجب عليها تحديد ما سيتم عرضه على الشاشة بناءً على الحالة القادمة من طبقة الـ\nbloc.\n\n<PresentationComponentSnippet />\n\nحتى الآن، وعلى الرغم من عرض بعض مقتطفات الكود، ما زال الشرح على مستوى مفاهيمي.\nفي قسم الدروس التعليمية، سنجمع كل هذه المفاهيم معًا أثناء بناء عدة تطبيقات\nمثالية مختلفة.\n"
  },
  {
    "path": "docs/src/content/docs/ar/bloc-concepts.mdx",
    "content": "---\ntitle: مفاهيم Bloc\ndescription: نظرة عامة على المفاهيم الأساسية لحزمة package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nيرجى التأكد من قراءة الأقسام التالية بعناية قبل العمل مع\n[`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nهناك عدة مفاهيم أساسية تُعد ضرورية لفهم كيفية استخدام حزمة bloc.\n\nفي الأقسام القادمة، سنناقش كل مفهوم بالتفصيل، ونستعرض كيف ينطبق ذلك على تطبيق\nعدّاد (counter app).\n\n## التدفقات (Streams)\n\n:::note\n\nاطّلع على [توثيق Dart الرسمي](https://dart.dev/tutorials/language/streams) لمزيد\nمن المعلومات حول `Streams`.\n\n:::\n\nالتدفق (Stream) هو سلسلة من البيانات غير المتزامنة.\n\nلاستخدام مكتبة bloc، من الضروري امتلاك فهم أساسي لـ `Streams` وكيف تعمل.\n\nإذا لم تكن مألوفًا بـ `Streams`، فتخيلها كأنبوب يجري فيه الماء: الأنبوب هو\n`Stream` والماء هو البيانات غير المتزامنة.\n\nيمكننا إنشاء `Stream` في Dart عبر كتابة دالة `async*` (مولّد غير متزامن - async\ngenerator).\n\n<CountStreamSnippet />\n\nعند تعليم الدالة بـ `async*` يصبح بإمكاننا استخدام الكلمة المفتاحية `yield`\nوإرجاع `Stream` من البيانات. في المثال أعلاه، نُرجع `Stream` من أعداد صحيحة حتى\nقيمة المُعامل `max`.\n\nفي كل مرة نستخدم فيها `yield` داخل دالة `async*`، فإننا ندفع تلك القيمة عبر الـ\n`Stream`.\n\nيمكننا استهلاك الـ `Stream` أعلاه بعدة طرق. إذا أردنا كتابة دالة تُرجع مجموع\n`Stream` من الأعداد الصحيحة، فقد تكون كالتالي:\n\n<SumStreamSnippet />\n\nعند تعليم الدالة أعلاه بـ `async` يصبح بإمكاننا استخدام `await` وإرجاع `Future`\nمن الأعداد الصحيحة. في هذا المثال، ننتظر كل قيمة في التدفق ثم نُرجع مجموع جميع\nالأعداد الموجودة فيه.\n\nيمكننا جمع كل ذلك معًا كالتالي:\n\n<StreamsMainSnippet />\n\nالآن بعد أن أصبح لدينا فهم أساسي لكيفية عمل `Streams` في Dart، نحن جاهزون للتعرف\nعلى المكوّن الأساسي في حزمة bloc: `Cubit`.\n\n## Cubit\n\n`Cubit` هو صنف (class) يوسّع `BlocBase` ويمكن توسيعه لإدارة أي نوع من الحالة.\n\n![هيكلية Cubit](~/assets/concepts/cubit_architecture_full.png)\n\nيمكن لـ `Cubit` أن يوفّر دوالًا يمكن استدعاؤها لتحفيز تغييرات الحالة.\n\nالحالات (States) هي مخرجات `Cubit` وتمثل جزءًا من حالة تطبيقك. يمكن إبلاغ مكونات\nواجهة المستخدم (UI) بالحالات وإعادة رسم أجزاء منها بناءً على الحالة الحالية.\n\n:::note\n\nلمزيد من المعلومات حول أصل `Cubit`، راجع\n[المشكلة التالية](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### إنشاء Cubit\n\nيمكننا إنشاء `CounterCubit` كالتالي:\n\n<CounterCubitSnippet />\n\nعند إنشاء `Cubit`، نحتاج إلى تحديد نوع الحالة التي سيديرها. في مثال\n`CounterCubit` أعلاه، يمكن تمثيل الحالة باستخدام `int`، لكن في الحالات الأكثر\nتعقيدًا قد نحتاج إلى استخدام `class` بدلًا من النوع البدائي (primitive type).\n\nالخطوة الثانية عند إنشاء `Cubit` هي تحديد الحالة الابتدائية (initial state).\nيمكننا فعل ذلك باستدعاء `super` وتمرير قيمة الحالة الابتدائية. في المثال أعلاه\nنُعيّن الحالة الابتدائية إلى `0` داخليًا، لكن يمكننا جعل `Cubit` أكثر مرونة\nبقبول قيمة خارجية:\n\n<CounterCubitInitialStateSnippet />\n\nوهذا يتيح لنا إنشاء مثيلات `CounterCubit` بحالات ابتدائية مختلفة مثل:\n\n<CounterCubitInstantiationSnippet />\n\n### تغييرات حالة Cubit\n\nكل `Cubit` يمكنه إخراج حالة جديدة عبر `emit`.\n\n<CounterCubitIncrementSnippet />\n\nفي المثال أعلاه، يوفّر `CounterCubit` دالة عامة باسم `increment` يمكن استدعاؤها\nخارجيًا لطلب زيادة الحالة. عند استدعاء `increment`، يمكننا الوصول إلى الحالة\nالحالية عبر getter باسم `state` ثم `emit` حالة جديدة بإضافة 1 إلى الحالة\nالحالية.\n\n:::caution\n\nالدالة `emit` محمية (protected)، ما يعني أنه يجب استخدامها فقط داخل `Cubit`.\n\n:::\n\n### استخدام Cubit\n\nيمكننا الآن أخذ `CounterCubit` الذي قمنا ببنائه واستخدامه!\n\n#### الاستخدام الأساسي\n\n<CounterCubitBasicUsageSnippet />\n\nفي المثال أعلاه، نبدأ بإنشاء مثيل من `CounterCubit`. ثم نطبع الحالة الحالية وهي\nالحالة الابتدائية (لأنه لم يتم إصدار حالات جديدة بعد). بعد ذلك، نستدعي\n`increment` لتحفيز تغيير الحالة. أخيرًا، نطبع حالة `Cubit` مرة أخرى وقد انتقلت\nمن `0` إلى `1`، ثم نستدعي `close` لإغلاق تدفق الحالة الداخلي.\n\n#### استخدام التدفق (Stream Usage)\n\nيوفّر `Cubit` تدفقًا (`Stream`) يتيح لنا استقبال تحديثات الحالة في الوقت الفعلي:\n\n<CounterCubitStreamUsageSnippet />\n\nفي المثال أعلاه، نشترك في `CounterCubit` ونطبع عند كل تغيير للحالة. ثم نستدعي\n`increment` لإصدار حالة جديدة. أخيرًا، نستدعي `cancel` على `subscription` عندما\nلا نعود بحاجة للتحديثات، ونغلق `Cubit`.\n\n:::note\n\nتمت إضافة `await Future.delayed(Duration.zero)` في هذا المثال لتجنب إلغاء\nالاشتراك فورًا.\n\n:::\n\n:::caution\n\nعند استدعاء `listen` على `Cubit` سيتم استقبال تغييرات الحالة اللاحقة فقط.\n\n:::\n\n### مراقبة Cubit\n\nعندما يرسل `Cubit` حالة جديدة، يحدث `Change`. يمكننا مراقبة جميع `Changes` لـ\n`Cubit` معين عبر تجاوز `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nيمكننا بعد ذلك التفاعل مع `Cubit` ومشاهدة جميع التغييرات المطبوعة على وحدة\nالتحكم (console).\n\n<CounterCubitOnChangeUsageSnippet />\n\nسيكون الإخراج في المثال أعلاه:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nيحدث `Change` قبل تحديث حالة `Cubit` مباشرة. ويتكون من `currentState` و\n`nextState`.\n\n:::\n\n#### BlocObserver\n\nمن مزايا استخدام مكتبة bloc أننا نستطيع الوصول إلى جميع `Changes` في مكان واحد.\nرغم أن هذا التطبيق يحتوي على `Cubit` واحد فقط، إلا أنه شائع في التطبيقات الكبيرة\nوجود عدة `Cubits` تدير أجزاء مختلفة من حالة التطبيق.\n\nإذا أردنا تنفيذ إجراء استجابةً لجميع `Changes`، يمكننا ببساطة إنشاء\n`BlocObserver` خاص بنا.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nكل ما نحتاجه هو توسيع `BlocObserver` وتجاوز `onChange`.\n\n:::\n\nلاستخدام `SimpleBlocObserver` نحتاج فقط إلى تعديل الدالة `main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nوسيكون الإخراج:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nيُستدعى تجاوز `onChange` الداخلي أولًا، ثم يستدعي `super.onChange` لإشعار\n`onChange` في `BlocObserver`.\n\n:::\n\n:::tip\n\nفي `BlocObserver` لدينا إمكانية الوصول إلى مثيل `Cubit` بالإضافة إلى `Change`\nنفسه.\n\n:::\n\n### معالجة أخطاء Cubit\n\nيمتلك كل `Cubit` دالة `addError` يمكن استخدامها للإشارة إلى حدوث خطأ.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\nيمكن تجاوز `onError` داخل `Cubit` لمعالجة جميع الأخطاء الخاصة بـ `Cubit` محدد.\n\n:::\n\nيمكن أيضًا تجاوز `onError` في `BlocObserver` لمعالجة جميع الأخطاء المُبلّغ عنها\nبشكل عام.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nإذا شغّلنا نفس البرنامج مرة أخرى، ينبغي أن نرى الإخراج التالي:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n`Bloc` هو صنف أكثر تقدمًا يعتمد على `events` لتحفيز تغييرات `state` بدلًا من\nالدوال. كما أنه يوسّع `BlocBase`، ما يعني أن لديه واجهة عامة مشابهة لـ `Cubit`.\nلكن بدلًا من استدعاء دالة على `Bloc` وإصدار `state` جديد مباشرة، تستقبل `Blocs`\nأحداثًا (`events`) وتحوّل الأحداث الواردة إلى حالات صادرة (`states`).\n\n![هيكلية Bloc](~/assets/concepts/bloc_architecture_full.png)\n\n### إنشاء Bloc\n\nإنشاء `Bloc` يشبه إنشاء `Cubit`، لكن بالإضافة إلى تحديد الحالة التي سنديرها، يجب\nأيضًا تحديد الحدث الذي سيتمكن `Bloc` من معالجته.\n\nالأحداث هي مدخلات Bloc. عادة تُضاف استجابةً لتفاعلات المستخدم مثل ضغط الأزرار أو\nلأحداث دورة الحياة مثل تحميل الصفحة.\n\n<CounterBlocSnippet />\n\nوكما في `CounterCubit`، يجب تحديد الحالة الابتدائية بتمريرها إلى الصنف الأب عبر\n`super`.\n\n### تغييرات حالة Bloc\n\nيتطلب `Bloc` تسجيل معالجات الأحداث عبر واجهة `on<Event>`، بخلاف الدوال في\n`Cubit`. معالج الحدث مسؤول عن تحويل أي أحداث واردة إلى صفر أو أكثر من الحالات\nالصادرة.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nيمتلك `EventHandler` إمكانية الوصول إلى الحدث المضاف، إضافةً إلى `Emitter` الذي\nيمكن استخدامه لإصدار صفر أو أكثر من الحالات استجابةً للحدث الوارد.\n\n:::\n\nيمكننا بعد ذلك تحديث `EventHandler` لمعالجة حدث `CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nفي المثال أعلاه، سجّلنا `EventHandler` لإدارة جميع أحداث\n`CounterIncrementPressed`. ولكل حدث وارد، يمكننا الوصول إلى الحالة الحالية عبر\ngetter باسم `state` ثم استدعاء `emit(state + 1)`.\n\n:::note\n\nبما أن `Bloc` يوسّع `BlocBase`، فيمكننا الوصول إلى الحالة الحالية في أي وقت عبر\n`state` تمامًا كما في `Cubit`.\n\n:::\n\n:::caution\n\nلا ينبغي على `Blocs` إصدار حالات جديدة مباشرة عبر `emit`. بدلًا من ذلك، يجب أن\nينتج كل تغيير في الحالة استجابةً لحدث وارد داخل `EventHandler`.\n\n:::\n\n:::caution\n\nيتجاهل كل من blocs و cubits الحالات المكررة. إذا قمنا بإصدار `State nextState`\nبحيث `state == nextState`، فلن يحدث أي تغيير في الحالة.\n\n:::\n\n### استخدام Bloc\n\nفي هذه المرحلة، يمكننا إنشاء مثيل من `CounterBloc` واستخدامه!\n\n#### الاستخدام الأساسي\n\n<CounterBlocUsageSnippet />\n\nفي المثال أعلاه، نبدأ بإنشاء مثيل من `CounterBloc`. ثم نطبع الحالة الحالية وهي\nالحالة الابتدائية (لأنه لم يتم إصدار حالات جديدة بعد). بعد ذلك، نضيف حدث\n`CounterIncrementPressed` لتحفيز تغيير الحالة. أخيرًا، نطبع حالة `Bloc` مرة أخرى\nوقد انتقلت من `0` إلى `1`، ثم نستدعي `close` لإغلاق تدفق الحالة الداخلي.\n\n:::note\n\nتمت إضافة `await Future.delayed(Duration.zero)` لضمان انتظار الدورة التالية من\nevent-loop (ما يسمح لـ `EventHandler` بمعالجة الحدث).\n\n:::\n\n#### استخدام التدفق (Stream Usage)\n\nكما هو الحال مع `Cubit`، فإن `Bloc` نوع خاص من `Stream`، ما يعني أنه يمكننا\nأيضًا الاشتراك في `Bloc` للحصول على تحديثات فورية لحالته:\n\n<CounterBlocStreamUsageSnippet />\n\nفي المثال أعلاه، نشترك في `CounterBloc` ونطبع عند كل تغيير للحالة. ثم نضيف حدث\n`CounterIncrementPressed` الذي يفعّل `EventHandler` الخاص بـ\n`on<CounterIncrementPressed>` ويُصدر حالة جديدة. أخيرًا، نستدعي `cancel` على\nالاشتراك عندما لا نعود بحاجة للتحديثات، ونغلق `Bloc`.\n\n:::note\n\nتمت إضافة `await Future.delayed(Duration.zero)` في هذا المثال لتجنب إلغاء\nالاشتراك فورًا.\n\n:::\n\n### مراقبة Bloc\n\nبما أن `Bloc` يوسّع `BlocBase`، يمكننا مراقبة جميع تغييرات الحالة لـ `Bloc`\nباستخدام `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nيمكننا بعد ذلك تحديث `main.dart` إلى:\n\n<CounterBlocOnChangeUsageSnippet />\n\nالآن إذا شغّلنا المثال أعلاه، سيكون الإخراج:\n\n<CounterBlocOnChangeOutputSnippet />\n\nأحد الفروق الجوهرية بين `Bloc` و `Cubit` هو أنه بما أن `Bloc` يعتمد على الأحداث،\nيمكننا أيضًا التقاط معلومات حول ما الذي حفّز تغيير الحالة.\n\nيمكننا فعل ذلك بتجاوز `onTransition`.\n\nالانتقال من حالة إلى أخرى يُسمى `Transition`. ويتكون `Transition` من الحالة\nالحالية والحدث والحالة التالية.\n\n<CounterBlocOnTransitionSnippet />\n\nإذا أعدنا تشغيل نفس مثال `main.dart` السابق، ينبغي أن نرى الإخراج التالي:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\nيُستدعى `onTransition` قبل `onChange` ويحتوي على الحدث الذي حفّز التغيير من\n`currentState` إلى `nextState`.\n\n:::\n\n#### BlocObserver\n\nكما في السابق، يمكننا تجاوز `onTransition` في `BlocObserver` مخصص لمراقبة جميع\nالانتقالات من مكان واحد.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nيمكننا تهيئة `SimpleBlocObserver` تمامًا كما فعلنا سابقًا:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nالآن إذا شغّلنا المثال أعلاه، يجب أن يكون الإخراج كالتالي:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\nيُستدعى `onTransition` أولًا (المحلي قبل العام) ثم يليه `onChange`.\n\n:::\n\nميزة أخرى فريدة في `Bloc` هي إمكانية تجاوز `onEvent`، والتي تُستدعى كلما تمت\nإضافة حدث جديد إلى `Bloc`. وكما في `onChange` و `onTransition`، يمكن تجاوز\n`onEvent` محليًا وعالميًا.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nيمكننا تشغيل نفس `main.dart` كما في السابق، وينبغي أن نرى الإخراج التالي:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\nيتم استدعاء `onEvent` بمجرد إضافة الحدث. ويُستدعى `onEvent` المحلي قبل `onEvent`\nالعام داخل `BlocObserver`.\n\n:::\n\n### معالجة أخطاء Bloc\n\nكما هو الحال مع `Cubit`، يمتلك كل `Bloc` دالتي `addError` و `onError`. يمكننا\nالإشارة إلى حدوث خطأ عبر استدعاء `addError` من أي مكان داخل `Bloc`. ثم يمكننا\nالاستجابة لجميع الأخطاء بتجاوز `onError` تمامًا كما في `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nإذا أعدنا تشغيل نفس `main.dart` كما في السابق، يمكننا رؤية شكل الإبلاغ عن الخطأ:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nيُستدعى `onError` المحلي أولًا ثم يليه `onError` العام داخل `BlocObserver`.\n\n:::\n\n:::note\n\nتعمل `onError` و `onChange` بالطريقة نفسها تمامًا لكل من `Bloc` و `Cubit`.\n\n:::\n\n:::caution\n\nأي استثناءات غير معالجة تحدث داخل `EventHandler` يتم الإبلاغ عنها أيضًا إلى\n`onError`.\n\n:::\n\n## Cubit مقابل Bloc\n\nالآن بعد أن غطّينا أساسيات `Cubit` و `Bloc`، قد تتساءل متى تستخدم `Cubit` ومتى\nتستخدم `Bloc`.\n\n### مزايا Cubit\n\n#### البساطة\n\nمن أكبر مزايا `Cubit` هي البساطة. عند إنشاء `Cubit` نحتاج فقط لتحديد الحالة\nوالدوال التي نريد توفيرها لتغيير الحالة. بالمقارنة، عند إنشاء `Bloc` نحتاج\nلتحديد الحالات والأحداث وتطبيق `EventHandler`. هذا يجعل `Cubit` أسهل في الفهم مع\nكود أقل.\n\nلنلقِ نظرة الآن على تنفيذي العداد:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nتنفيذ `Cubit` أكثر اختصارًا، وبدلًا من تعريف الأحداث بشكل منفصل، تقوم الدوال\nبدور الأحداث. إضافةً إلى ذلك، عند استخدام `Cubit` يمكننا ببساطة استدعاء `emit`\nمن أي مكان لتحفيز تغيير الحالة.\n\n### مزايا Bloc\n\n#### قابلية التتبع (Traceability)\n\nمن أكبر مزايا `Bloc` معرفة تسلسل تغييرات الحالة، ومعرفة ما الذي حفّز تلك\nالتغييرات بدقة. عندما تكون الحالة حساسة أو حاسمة لوظائف التطبيق، قد يكون من\nالمفيد استخدام نهج قائم على الأحداث لالتقاط الأحداث إضافةً إلى تغييرات الحالة.\n\nحالة استخدام شائعة هي إدارة `AuthenticationState`. وللتبسيط، لنفترض أننا نمثل\n`AuthenticationState` عبر `enum`:\n\n<AuthenticationStateSnippet />\n\nقد توجد أسباب عديدة لتغير حالة التطبيق من `authenticated` إلى `unauthenticated`.\nعلى سبيل المثال، قد يضغط المستخدم زر تسجيل الخروج ويطلب تسجيل خروجه. أو ربما تم\nإلغاء رمز وصول المستخدم وتم تسجيل خروجه إجباريًا. عند استخدام `Bloc` يمكننا\nتتبّع كيف وصلت حالة التطبيق إلى حالة معينة بوضوح.\n\n<AuthenticationTransitionSnippet />\n\nيوفر لنا `Transition` أعلاه كل المعلومات اللازمة لفهم سبب تغير الحالة. ولو\nاستخدمنا `Cubit` لإدارة `AuthenticationState` فستبدو السجلات كالتالي:\n\n<AuthenticationChangeSnippet />\n\nهذا يخبرنا أن المستخدم خرج من النظام، لكنه لا يوضح السبب، وقد يكون ذلك مهمًا\nلتصحيح الأخطاء وفهم كيفية تغيّر حالة التطبيق مع الزمن.\n\n#### تحويلات الأحداث المتقدمة\n\nمجال آخر يتفوق فيه `Bloc` على `Cubit` هو عندما نحتاج للاستفادة من العوامل\nالتفاعلية مثل `buffer` و `debounceTime` و `throttle` وغيرها.\n\n:::tip\n\nراجع [`package:stream_transform`](https://pub.dev/packages/stream_transform) و\n[`package:rxdart`](https://pub.dev/packages/rxdart) لمحولات التدفق (stream\ntransformers).\n\n:::\n\nيمتلك `Bloc` قناة إدخال للأحداث (event sink) تتيح لنا التحكم في تدفق الأحداث\nالواردة وتحويله.\n\nعلى سبيل المثال، إذا كنا نبني بحثًا لحظيًا، فمن المرجح أننا سنحتاج إلى تطبيق\n`debounce` على طلبات الخلفية لتجنب rate-limiting، وكذلك لتقليل التكلفة/الضغط على\nالخادم.\n\nباستخدام `Bloc`، يمكننا توفير `EventTransformer` مخصص لتغيير طريقة معالجة\nالأحداث الواردة.\n\n<DebounceEventTransformerSnippet />\n\nباستخدام الكود أعلاه، يمكننا بسهولة تطبيق `debounce` على الأحداث الواردة مع\nمقدار بسيط جدًا من الكود الإضافي.\n\n:::tip\n\nاطّلع على\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) للحصول\nعلى مجموعة مُوجهة من محولات الأحداث.\n\n:::\n\nإذا لم تكن متأكدًا مما يجب استخدامه، ابدأ بـ `Cubit` ويمكنك لاحقًا إعادة الهيكلة\nأو التوسّع إلى `Bloc` عند الحاجة.\n"
  },
  {
    "path": "docs/src/content/docs/ar/faqs.mdx",
    "content": "---\ntitle: الأسئلة الشائعة (FAQs)\ndescription: إجابات للأسئلة المتكررة بخصوص مكتبة bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## عدم تحديث الحالة (State Not Updating)\n\n❔ **سؤال**: أقوم بإصدار حالة داخل الـ bloc، لكن واجهة المستخدم لا تتحدّث. ما\nالخطأ؟\n\n💡 **إجابة**: إذا كنت تستخدم `Equatable`، فتأكّد من تضمين جميع الخصائص داخل\n`props`.\n\n✅ **صحيح (GOOD)**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **خاطئ (BAD)**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nكذلك تأكّد من إصدار نسخة جديدة من الحالة داخل الـ bloc، وليس إعادة استخدام نفس\nالكائن.\n\n✅ **صحيح (GOOD)**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **خاطئ (BAD)**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nيجب دائمًا نسخ الخصائص في الكائنات التي تستخدم `Equatable` بدل تعديلها مباشرة.\nوإذا كانت الفئة تحتوي على `List` أو `Map`، فاستخدم `List.of` أو `Map.of` حتى\nتُحتسب المساواة بناءً على القيم لا على مرجع الكائن.\n\n:::\n\n## متى يجب استخدام Equatable\n\n❔ **سؤال**: متى أستخدم Equatable؟\n\n💡 **إجابة**:\n\n<EquatableEmitSnippet />\n\nفي المثال أعلاه، إذا كانت `StateA` ترث من `Equatable` فلن يحدث سوى انتقال حالة\nواحد (سيُتجاهل `emit` الثاني). بشكل عام، استخدم `Equatable` عندما تريد تقليل\nإعادة البناء وتحسين الأداء. ولا تستخدمه إذا كنت تحتاج أن تؤدي الحالة نفسها عند\nتكرارها إلى انتقالات متعددة.\n\nكما أن `Equatable` يسهّل اختبار الـ blocs، لأننا نستطيع توقّع حالات محددة\nمباشرةً بدل الاعتماد على `Matchers` أو `Predicates`.\n\n<EquatableBlocTestSnippet />\n\nبدون `Equatable` سيفشل الاختبار أعلاه، وسنحتاج لإعادة كتابته بهذا الشكل:\n\n<NoEquatableBlocTestSnippet />\n\n## التعامل مع الأخطاء (Handling Errors)\n\n❔ **سؤال**: كيف أتعامل مع الخطأ مع الاستمرار في عرض البيانات السابقة؟\n\n💡 **إجابة**:\n\nيعتمد ذلك بدرجة كبيرة على طريقة نمذجة حالة الـ bloc. إذا كنت تحتاج للاحتفاظ\nبالبيانات حتى عند وقوع خطأ، فمن الأفضل استخدام فئة حالة واحدة.\n\n<SingleStateSnippet />\n\nبهذه الطريقة يمكن للـ widgets الوصول إلى `data` و `error` في الوقت نفسه، ويمكن\nللـ bloc استخدام `state.copyWith` للاحتفاظ بالبيانات السابقة عند حدوث الخطأ.\n\n<SingleStateUsageSnippet />\n\n## Bloc مقابل Redux\n\n❔ **سؤال**: ما الفرق بين Bloc و Redux؟\n\n💡 **إجابة**:\n\nالـ BLoC هو نمط تصميم يقوم على القواعد التالية:\n\n1. مدخلات ومخرجات الـ BLoC هي `Streams` و`Sinks`.\n2. يجب أن تكون الاعتماديات قابلة للحقن ومستقلة عن المنصة.\n3. لا يُسمح بتفرعات منطقية مرتبطة بمنصة محددة.\n4. تفاصيل التنفيذ مرنة ما دمت ملتزمًا بالقواعد السابقة.\n\nأما إرشادات طبقة الواجهة فهي:\n\n1. كل مكوّن \"معقّد بما يكفي\" ينبغي أن يكون له BLoC خاص به.\n2. على المكوّنات إرسال المدخلات كما هي.\n3. وعلى المكوّنات عرض المخرجات بأقل قدر ممكن من التحويل.\n4. وكل التفرعات المنطقية في الواجهة يجب أن تعتمد على مخارج Boolean بسيطة من الـ\n   BLoC.\n\nمكتبة Bloc تطبق نمط BLoC، وتهدف إلى تجريد `RxDart` لتبسيط تجربة التطوير.\n\nأما Redux فيقوم على ثلاثة مبادئ:\n\n1. مصدر واحد للحقيقة.\n2. الحالة للقراءة فقط.\n3. التعديلات تتم عبر دوال نقية.\n\nمكتبة bloc لا تتوافق مع المبدأ الأول في Redux، لأن الحالة فيها موزعة عبر عدة\nblocs. كذلك لا يوجد فيها مفهوم `middleware` بالشكل الموجود في Redux، وهي مصممة\nلتسهيل تغييرات الحالة غير المتزامنة وإتاحة إصدار عدة حالات لحدث واحد.\n\n## Bloc مقابل Provider\n\n❔ **سؤال**: ما الفرق بين Bloc و Provider؟\n\n💡 **إجابة**: حزمة `provider` مخصّصة أساسًا لحقن الاعتماديات (وهي غلاف حول\n`InheritedWidget`). ما زلت بحاجة لاختيار أسلوب إدارة الحالة بنفسك\n(`ChangeNotifier` أو `Bloc` أو `Mobx` وغيرها). مكتبة Bloc تستخدم `provider`\nداخليًا لتسهيل توفير الـ blocs والوصول إليها عبر شجرة الـ widgets.\n\n## فشل BlocProvider.of() في العثور على Bloc\n\n❔ **سؤال**: عند استخدام `BlocProvider.of(context)`، لا يمكنه العثور على الـ\nbloc. كيف يمكنني إصلاح ذلك؟\n\n💡 **إجابة**: لا يمكنك الوصول إلى bloc من نفس الـ `BuildContext` الذي قمت\nبتوفيره فيه. لذلك تأكّد من استدعاء `BlocProvider.of()` من `BuildContext` تابع\n(ابن).\n\n✅ **صحيح (GOOD)**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **خاطئ (BAD)**\n\n<BlocProviderBad1Snippet />\n\n## هيكلة المشروع (Project Structure)\n\n❔ **سؤال**: كيف يجب أن أقوم بهيكلة مشروعي؟\n\n💡 **إجابة**: لا توجد إجابة واحدة صحيحة أو خاطئة تمامًا لهذا السؤال، لكن هذه\nمراجع مفيدة:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nالأهم أن تكون هيكلة المشروع **متسقة** و**مقصودة**.\n\n## إضافة الأحداث داخل Bloc\n\n❔ **سؤال**: هل من المقبول إضافة الأحداث داخل bloc؟\n\n💡 **إجابة**: في أغلب الحالات تُضاف الأحداث من خارج الـ bloc، لكن في حالات محددة\nقد يكون من المنطقي إضافتها من داخله.\n\nأشيع سيناريو للأحداث الداخلية هو عندما يلزم تغيير الحالة استجابةً لتحديثات لحظية\nقادمة من `repository`. هنا يكون مصدر التحفيز هو المستودع نفسه، وليس حدثًا\nخارجيًا مثل ضغط زر.\n\nفي المثال التالي، تعتمد حالة `MyBloc` على المستخدم الحالي الذي يتم كشفه عبر\n`Stream<User>` من `UserRepository`. يستمع `MyBloc` لتغيّرات المستخدم الحالي\nويضيف حدثًا داخليًا `_UserChanged` كلما صدر مستخدم جديد من هذا الـ stream.\n\n<BlocInternalAddEventSnippet />\n\nعند إضافة حدث داخلي يمكننا أيضًا تحديد `transformer` مخصّص للتحكم في طريقة\nمعالجة أحداث `_UserChanged` المتعددة. افتراضيًا، تُعالج هذه الأحداث بالتوازي.\n\nيوصى بشدة بأن تكون الأحداث الداخلية `private`. هذا يوضح أن الحدث مخصّص للاستخدام\nالداخلي فقط، ويمنع المكوّنات الخارجية من الاعتماد عليه.\n\n<BlocInternalEventSnippet />\n\nيمكن أيضًا تعريف حدث خارجي `Started` واستخدام `emit.forEach` للتعامل مع تحديثات\nالمستخدم اللحظية:\n\n<BlocExternalForEachSnippet />\n\nمزايا هذا النهج:\n\n- لا حاجة لحدث داخلي `_UserChanged`.\n- لا حاجة لإدارة `StreamSubscription` يدويًا.\n- تحكم كامل في توقيت اشتراك الـ bloc في stream تحديثات المستخدم.\n\nعيوب هذا النهج:\n\n- لا يمكن إيقاف (`pause`) الاشتراك أو استئنافه (`resume`) بسهولة.\n- نحتاج إلى كشف حدث عام `Started` ليُضاف من الخارج.\n- لا يمكن استخدام `transformer` مخصص لضبط طريقة التعامل مع تحديثات المستخدم.\n\n## كشف الدوال العامة (Exposing Public Methods)\n\n❔ **سؤال**: هل من المقبول كشف دوال عامة (public methods) على مثيلات bloc و\ncubit الخاصة بي؟\n\n💡 **إجابة**\n\nعند إنشاء cubit، يُنصح بأن تكون الدوال العامة مخصّصة فقط لتحفيز تغيّر الحالة.\nلذلك، من الأفضل غالبًا أن تعيد هذه الدوال `void` أو `Future<void>`.\n\nوعند إنشاء bloc، يُنصح بتجنب كشف دوال عامة مخصّصة، والاكتفاء بإبلاغه بالأحداث\nعبر استدعاء `add`.\n"
  },
  {
    "path": "docs/src/content/docs/ar/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: مفاهيم Flutter Bloc\ndescription: نظرة عامة على المفاهيم الأساسية لحزمة flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nيرجى التأكد من قراءة الأقسام التالية بعناية قبل البدء بالعمل مع حزمة\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nجميع الـ Widgets التي تصدرها حزمة `flutter_bloc` تتكامل مع كل من مثيلات `Cubit`\nو `Bloc`.\n\n:::\n\n## Widgets الـ Bloc\n\n### BlocBuilder\n\n**BlocBuilder** هو ويدجت (Widget) في Flutter يتطلب `Bloc` ودالة `builder`.\nيتعامل `BlocBuilder` مع بناء الـ Widget استجابةً للحالات الجديدة (new states).\nيشبه `BlocBuilder` إلى حد كبير `StreamBuilder` لكنه يوفر واجهة (API) أبسط لتقليل\nكمية الكود المتكرر (boilerplate) المطلوبة. قد يتم استدعاء دالة `builder` مرات\nعديدة، لذلك يُفضل أن تكون\n[دالة نقية (pure function)](https://en.wikipedia.org/wiki/Pure_function) تعيد\nويدجت استجابةً للحالة.\n\nراجع `BlocListener` إذا كنت تريد \"تنفيذ\" شيء استجابةً لتغيرات الحالة مثل التنقل\n(navigation) أو إظهار مربع حوار (dialog) وغيرها.\n\nإذا تم حذف معامل `bloc`، فسيقوم `BlocBuilder` تلقائيًا بإجراء بحث (lookup)\nباستخدام `BlocProvider` و `BuildContext` الحالي.\n\n<BlocBuilderSnippet />\n\nقم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc سيكون نطاقه (scoped) مقتصرًا\nعلى ويدجت واحد ولا يمكن الوصول إليه عبر `BlocProvider` أبوي و `BuildContext`\nالحالي.\n\n<BlocBuilderExplicitBlocSnippet />\n\nلتحكم أدق في وقت استدعاء دالة `builder`، يمكن تمرير دالة اختيارية `buildWhen`.\nتأخذ `buildWhen` حالة الـ bloc السابقة والحالة الحالية وتعيد قيمة منطقية\n(boolean). إذا أعادت `true` فسيتم استدعاء `builder` وإعادة بناء الـ Widget، وإذا\nأعادت `false` فلن يتم استدعاء `builder` ولن تحدث إعادة بناء.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** هو ويدجت في Flutter يشبه `BlocBuilder` لكنه يسمح بتصفية\nالتحديثات عبر اختيار قيمة جديدة بناءً على حالة الـ bloc الحالية. يتم منع عمليات\nالبناء غير الضرورية إذا لم تتغير القيمة المختارة. يجب أن تكون القيمة المختارة\nغير قابلة للتغيير (immutable) حتى يتمكن `BlocSelector` من تحديد ما إذا كان يجب\nاستدعاء `builder` مجددًا بدقة.\n\nإذا تم حذف معامل `bloc`، فسيقوم `BlocSelector` تلقائيًا بإجراء بحث باستخدام\n`BlocProvider` و `BuildContext` الحالي.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** هو ويدجت في Flutter يوفر bloc لأبنائه عبر\n`BlocProvider.of<T>(context)`. يُستخدم كويدجت لحقن التبعية (Dependency\nInjection - DI) بحيث يمكن توفير مثيل واحد من الـ bloc لعدة ويدجتات داخل شجرة\nفرعية (subtree).\n\nفي أغلب الحالات، يجب استخدام `BlocProvider` لإنشاء blocs جديدة ستكون متاحة لبقية\nالشجرة الفرعية. وبما أن `BlocProvider` هو المسؤول عن الإنشاء، فسيتولى تلقائيًا\nإغلاق الـ bloc.\n\n<BlocProviderSnippet />\n\nبشكل افتراضي، ينشئ `BlocProvider` الـ bloc بشكل كسول (lazily)، أي أن `create`\nسيتم تنفيذه عند البحث عن الـ bloc عبر `BlocProvider.of<BlocA>(context)`.\n\nلتجاوز هذا السلوك وإجبار `create` على العمل فورًا، يمكن تعيين `lazy` إلى\n`false`.\n\n<BlocProviderEagerSnippet />\n\nفي بعض الحالات، يمكن استخدام `BlocProvider` لتوفير bloc موجود بالفعل لجزء جديد\nمن شجرة الـ Widgets (مثلاً عند فتح Route جديد). في هذه الحالة، لن يقوم\n`BlocProvider` بإغلاق الـ bloc تلقائيًا لأنه لم يقم بإنشائه.\n\n<BlocProviderValueSnippet />\n\nبعد ذلك، من أي من `ChildA` أو `ScreenA` يمكننا استرداد `BlocA` باستخدام:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** هو ويدجت في Flutter يدمج عدة `BlocProvider` في ويدجت واحد.\nيحسن قابلية القراءة ويزيل الحاجة إلى تداخل (nesting) عدة `BlocProvider`.\nباستخدام `MultiBlocProvider` يمكننا الانتقال من:\n\n<NestedBlocProviderSnippet />\n\nإلى:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nعندما يتم تعريف `BlocProvider` ضمن سياق `MultiBlocProvider`، سيتم تجاهل أي\n`child`.\n\n:::\n\n### BlocListener\n\n**BlocListener** هو ويدجت في Flutter يأخذ `BlocWidgetListener` و `Bloc` اختياري،\nويستدعي `listener` استجابةً لتغيرات الحالة في الـ bloc. يُستخدم للوظائف التي يجب\nأن تحدث مرة واحدة لكل تغيير حالة مثل التنقل، إظهار `SnackBar` أو `Dialog`.\n\nيتم استدعاء `listener` مرة واحدة فقط لكل تغيير في الحالة (**لا** يشمل الحالة\nالأولية) على عكس `builder` في `BlocBuilder`، وهو دالة `void`.\n\nإذا تم حذف معامل `bloc`، فسيقوم `BlocListener` تلقائيًا بإجراء بحث باستخدام\n`BlocProvider` و `BuildContext` الحالي.\n\n<BlocListenerSnippet />\n\nقم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc لا يمكن الوصول إليه عبر\n`BlocProvider` و `BuildContext` الحالي.\n\n<BlocListenerExplicitBlocSnippet />\n\nلتحكم أدق في وقت استدعاء `listener`، يمكن تمرير `listenWhen`. تأخذ `listenWhen`\nحالة الـ bloc السابقة والحالة الحالية وتعيد قيمة منطقية. إذا أعادت `true` فسيتم\nاستدعاء `listener`، وإذا أعادت `false` فلن يتم استدعاؤه.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** يدمج عدة `BlocListener` في ويدجت واحد لتحسين قابلية\nالقراءة وتجنب التداخل. باستخدامه يمكننا الانتقال من:\n\n<NestedBlocListenerSnippet />\n\nإلى:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nعندما يتم تعريف `BlocListener` ضمن سياق `MultiBlocListener`، سيتم تجاهل أي\n`child`.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** يجمع بين `builder` و `listener` للتفاعل مع الحالات الجديدة.\nيشبه استخدام `BlocListener` و `BlocBuilder` معًا لكنه يقلل من الكود المتكرر.\nيُستخدم فقط عندما تحتاج إلى إعادة بناء UI وتنفيذ تفاعل آخر مع تغيرات الحالة في\nنفس الوقت.\n\nإذا تم حذف معامل `bloc`، فسيقوم `BlocConsumer` تلقائيًا بإجراء بحث باستخدام\n`BlocProvider` و `BuildContext` الحالي.\n\n<BlocConsumerSnippet />\n\nيمكن تمرير `listenWhen` و `buildWhen` اختياريًا للتحكم بشكل أدق في وقت استدعاء\n`listener` و `builder`. سيتم استدعاؤهما عند كل تغيير في `state` الخاص بالـ bloc.\nكل منهما يأخذ الحالة السابقة والحالية ويعيد `bool`. إذا لم يتم توفيرهما فالقيمة\nالافتراضية هي `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** هو ويدجت في Flutter يوفر مستودعًا (repository) لأبنائه\nعبر `RepositoryProvider.of<T>(context)`. يُستخدم لحقن التبعية (DI) لتوفير مثيل\nواحد من المستودع لعدة ويدجتات داخل شجرة فرعية. يُستخدم `BlocProvider` للـ blocs\nبينما يُستخدم `RepositoryProvider` للمستودعات فقط.\n\n<RepositoryProviderSnippet />\n\nثم من `ChildA` يمكننا استرداد مثيل `Repository` باستخدام:\n\n<RepositoryProviderLookupSnippet />\n\nيمكن للمستودعات التي تدير موارد يجب التخلص منها (dispose) القيام بذلك عبر دالة\n`dispose`:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** يدمج عدة `RepositoryProvider` في ويدجت واحد لتحسين\nقابلية القراءة وتجنب التداخل. باستخدامه يمكننا الانتقال من:\n\n<NestedRepositoryProviderSnippet />\n\nإلى:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nعندما يتم تعريف `RepositoryProvider` ضمن سياق `MultiRepositoryProvider`، سيتم\nتجاهل أي `child`.\n\n:::\n\n## استخدام BlocProvider\n\nدعونا نلقي نظرة على كيفية استخدام `BlocProvider` لتوفير `CounterBloc` إلى\n`CounterPage` والتفاعل مع تغيرات الحالة باستخدام `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nفي هذه المرحلة، نجحنا في فصل طبقة العرض (presentational layer) عن طبقة منطق\nالأعمال (business logic layer). لاحظ أن ويدجت `CounterPage` لا يعرف شيئًا عما\nيحدث عندما ينقر المستخدم على الأزرار. يخبر الـ Widget ببساطة `CounterBloc` بأن\nالمستخدم ضغط زر الزيادة أو النقصان.\n\n## استخدام RepositoryProvider\n\nسنلقي نظرة على كيفية استخدام `RepositoryProvider` ضمن سياق مثال\n[`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nفي ملف `main.dart` نستدعي `runApp` مع ويدجت `WeatherApp`.\n\n<WeatherMainSnippet />\n\nسنقوم بحقن مثيل `WeatherRepository` في شجرة الـ Widgets عبر\n`RepositoryProvider`.\n\nعند إنشاء bloc، يمكننا الوصول إلى مثيل المستودع عبر `context.read` وحقن المستودع\nفي الـ bloc عبر المُنشئ (constructor).\n\n<WeatherAppSnippet />\n\n:::tip\n\nإذا كان لديك أكثر من مستودع واحد، يمكنك استخدام `MultiRepositoryProvider` لتوفير\nعدة مستودعات للشجرة الفرعية.\n\n:::\n\n:::note\n\nاستخدم `dispose` للتعامل مع تحرير الموارد عند إزالة `RepositoryProvider` من شجرة\nالـ Widgets.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## دوال الامتداد (Extension Methods)\n\n[دوال الامتداد](https://dart.dev/guides/language/extension-methods)، التي تم\nتقديمها في Dart 2.7، هي طريقة لإضافة وظائف إلى المكتبات الموجودة. في هذا القسم،\nسنلقي نظرة على دوال الامتداد في `package:flutter_bloc` وكيف يمكن استخدامها.\n\nتعتمد `flutter_bloc` على [package:provider](https://pub.dev/packages/provider)\nوالتي تبسط استخدام\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nداخليًا، تستخدم `package:flutter_bloc` حزمة `provider` لتنفيذ: `BlocProvider` و\n`MultiBlocProvider` و `RepositoryProvider` و `MultiRepositoryProvider`. كما تصدر\nامتدادات `ReadContext` و `WatchContext` و `SelectContext` من `provider`.\n\n:::note\n\nتعرف على المزيد حول [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\nتقوم `context.read<T>()` بالبحث عن أقرب مثيل سلف من النوع `T` وهي مكافئة وظيفيًا\nلـ `BlocProvider.of<T>(context)`. تُستخدم غالبًا لاسترداد مثيل bloc لإضافة حدث\nداخل `onPressed`.\n\n:::note\n\n`context.read<T>()` لا تستمع إلى `T` - إذا تغير الكائن المقدم من النوع `T` فلن\nتؤدي `context.read` إلى إعادة بناء الـ Widget.\n\n:::\n\n#### الاستخدام\n\n✅ **افعل** استخدام `context.read` لإضافة الأحداث في callbacks.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **تجنب** استخدام `context.read` لاسترداد الحالة داخل `build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nالاستخدام أعلاه عرضة للخطأ لأن ويدجت `Text` لن يتم إعادة بناؤه إذا تغيرت حالة\nالـ bloc.\n\n:::caution\n\nاستخدم `BlocBuilder` أو `context.watch` بدلًا من ذلك لإعادة البناء عند تغير\nالحالة.\n\n:::\n\n### context.watch\n\nمثل `context.read<T>()`، توفر `context.watch<T>()` أقرب مثيل سلف من النوع `T`\nلكنها تستمع أيضًا للتغييرات على المثيل. وهي مكافئة وظيفيًا لـ\n`BlocProvider.of<T>(context, listen: true)`.\n\nإذا تغير الكائن المقدم من النوع `T` فستؤدي `context.watch` إلى إعادة بناء\n(rebuild).\n\n:::caution\n\n`context.watch` متاحة فقط داخل `build` في `StatelessWidget` أو `State`.\n\n:::\n\n#### الاستخدام\n\n✅ **افعل** استخدام `BlocBuilder` بدلًا من `context.watch` لتحديد نطاق إعادة\nالبناء بشكل صريح.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Whenever the state changes, only the Text is rebuilt.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nبدلًا من ذلك، استخدم `Builder` لتحديد نطاق إعادة البناء.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever the state changes, only the Text is rebuilt.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **افعل** استخدام `Builder` و `context.watch` كبديل لـ `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n❌ **تجنب** استخدام `context.watch` عندما لا يعتمد الـ Widget الأب على الحالة.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nسيؤدي استخدام `context.watch` في جذر `build` إلى إعادة بناء الـ Widget بالكامل\nعند تغير حالة الـ bloc.\n\n:::\n\n### context.select\n\nتمامًا مثل `context.watch<T>()`، توفر `context.select<T, R>(...)` أقرب مثيل سلف\nمن النوع `T` وتستمع إلى التغييرات عليه، لكنها تسمح بالاستماع إلى جزء أصغر من\nالحالة.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nسيُعاد بناء الـ Widget فقط عند تغير خاصية `name` في حالة `ProfileBloc`.\n\n#### الاستخدام\n\n✅ **افعل** استخدام `BlocSelector` بدلًا من `context.select` لتحديد نطاق إعادة\nالبناء بشكل صريح.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Whenever the state.name changes, only the Text is rebuilt.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nبدلًا من ذلك، استخدم `Builder` لتحديد نطاق إعادة البناء.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever state.name changes, only the Text is rebuilt.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **تجنب** استخدام `context.select` عندما لا يعتمد الـ Widget الأب على الحالة.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state.value changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nسيؤدي استخدام `context.select` في جذر `build` إلى إعادة بناء الـ Widget بالكامل\nعند تغير الاختيار (selection).\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ar/getting-started.mdx",
    "content": "---\ntitle: دليل البدء\ndescription: كل ما تحتاجه لبدء البناء باستخدام Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## الحزم\n\nيتكوّن نظام Bloc من عدة حزم مدرجة أدناه:\n\n| الحزمة                                                                                     | الوصف                              | الرابط                                                                                                         |\n| ------------------------------------------------------------------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | مكونات AngularDart                 | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | واجهات برمجة تطبيقات Dart الأساسية | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | محولات الأحداث                     | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | مدقق مخصص                          | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | واجهات برمجة تطبيقات للاختبار      | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | أدوات سطر الأوامر                  | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | عناصر واجهة Flutter                | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | دعم التخزين المؤقت/الاستمرارية     | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | دعم التراجع/الإعادة                | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## التثبيت\n\n<InstallationTabs />\n\n:::note\n\nمن أجل البدء باستخدام bloc، يجب أن يكون\n[Dart SDK مُثبت](https://dart.dev/get-dart) على جهازك.\n\n:::\n\n## الاستيراد\n\nالآن بعد أن قمنا بتثبيت bloc بنجاح، يمكننا إنشاء ملف `main.dart` واستيراد حزمة\n`bloc` المناسبة.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/ar/index.mdx",
    "content": "---\ntemplate: splash\ntitle: مكتبة Bloc لإدارة الحالة\ndescription:\n  التوثيق الرسمي لمكتبة Bloc لإدارة الحالة. تدعم Dart وFlutter وAngularDart.\n  يتضمن أمثلة ودروسًا تعليمية.\nbanner:\n  content: |\n    ✨ تفضل بزيارة\n    <a href=\"https://shop.bloclibrary.dev\">متجر Bloc</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: مكتبة لإدارة الحالة في Dart تعتمد على قابلية التنبؤ.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: ابدأ الآن\n      link: /ar/getting-started/\n      variant: primary\n      icon: rocket\n    - text: عرض على GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid sponsoredBy=\"برعاية 💖 بواسطة\" becomeASponsor=\"اصبح كفيل\" />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"ابدأ الآن\" icon=\"rocket\">\n\t```sh\n\t# .إلى مشروعك bloc أضف\n\tdart pub add bloc\n\t```\n\nيوفر [دليل البدء](/ar/getting-started) إرشادات خطوة بخطوة حول كيفية بدء استخدام\nBloc خلال بضع دقائق فقط.\n\n</SplitCard>\n\n<Card title=\"قم بجولة إرشادية\" icon=\"star\">\n\tأكمل [الدروس الرسمية](/ar/tutorials/flutter-counter) لتتعلم أفضل الممارسات\n\tوبناء مجموعة متنوعة من التطبيقات المعتمدة على Bloc.\n</Card>\n\n<Card title=\"أنشئ باستخدام Bloc\" icon=\"laptop\">\n\tاستكشف [أمثلة تطبيقات عالية الجودة ومختبرة\n\tبالكامل](https://github.com/felangel/bloc/tree/master/examples) مثل العداد،\n\tوالمؤقت، والقائمة اللانهائية، والطقس، والمهام، والمزيد.\n</Card>\n\n<ListCard title=\"تعلّم\" icon=\"open-book\">\n\n    - [لماذا Bloc؟](/ar/why-bloc)\n    - [المفاهيم الأساسية](/ar/bloc-concepts)\n    - [البنية المعمارية](/ar/architecture)\n    - [الاختبارات](/ar/testing)\n    - [اتفاقيات التسمية](/ar/naming-conventions)\n    - [الأسئلة الشائعة](/ar/faqs)\n\n</ListCard>\n\n  <ListCard title=\"التكامل\" icon=\"puzzle\">\n    - [التكامل مع VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [التكامل مع IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [التكامل مع Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [التكامل مع Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [قوالب مخصصة](https://brickhub.dev/search?q=bloc)\n    - [أدوات المطورين](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"انضم معنا في Discord\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint/configuration.mdx",
    "content": "---\ntitle: إعداد أداة التدقيق (Linter Configuration)\ndescription: كيفية إعداد أداة التدقيق الخاصة بـ bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nافتراضيًا، لن تعرض أداة التدقيق في bloc أي تشخيصات ما لم تُعدّ خيارات التحليل\n(analysis options) في المشروع بشكل صريح.\n\nللبدء، أنشئ ملف `analysis_options.yaml` أو عدّله في جذر المشروع، بحيث يحتوي على\nقائمة القواعد تحت المفتاح الأعلى `bloc`:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nشغّل أداة التدقيق باستخدام الأمر التالي في terminal:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nسيحلّل الأمر أعلاه جميع الملفات في المجلد الحالي ومجلداته الفرعية. ويمكنك أيضًا\nتدقيق ملفات أو مجلدات محددة عبر تمريرها كوسائط لسطر الأوامر (command-line\narguments):\n\n<RunBlocLintInSrcTestSnippet />\n\nسيحلّل الأمر أعلاه كل الأكواد البرمجية داخل مجلدي `src` و`test`.\n\nإذا كانت قاعدة `avoid_flutter_imports` مفعّلة، فسيُبلّغ عن أي ملف `bloc` أو\n`cubit` يحتوي على `import` لـ Flutter على أنه تحذير:\n\n<AvoidFlutterImportsWarningSnippet />\n\nيمكنك مشاهدة التحذير عبر تشغيل الأمر `bloc lint`:\n\n<RunBlocLintCounterCubitSnippet />\n\nيجب أن يكون المخرج (output) بالشكل التالي:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nفيما يلي جميع قواعد التدقيق المدعومة (lint rules):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint/customizing-rules.mdx",
    "content": "---\ntitle: تخصيص قواعد أداة التدقيق (Lint Rules)\ndescription: تخصيص قواعد أداة التدقيق الخاصة بـ bloc\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nيمكنك تخصيص سلوك أداة التدقيق في bloc عبر تغيير مستوى الشدة (`severity`) لكل\nقاعدة، أو تمكين القواعد وتعطيلها بشكل فردي، أو استثناء ملفات من التحليل الساكن.\n\n## تمكين وتعطيل القواعد\n\nتدعم أداة التدقيق في bloc قائمة متزايدة من القواعد. وتجدر الإشارة إلى أن هذه\nالقواعد قد لا تكون متوافقة دائمًا مع تفضيلات الجميع. على سبيل المثال، قد يفضّل\nبعض المطورين استخدام blocs (`prefer_bloc`) بينما يفضّل آخرون استخدام cubits\n(`prefer_cubit`).\n\n:::note\n\nبعكس التحليل الساكن، قد تنتج قواعد التدقيق أحيانًا **إنذارات كاذبة\n(`false positives`)**. إذا صادفت ذلك أو أي مشكلة أخرى، يمكنك الإبلاغ عنها عبر\n[فتح Issue](https://github.com/felangel/bloc/issues/new/choose).\n\n:::\n\n### تمكين القواعد الموصى بها\n\nتوفّر مكتبة bloc مجموعة قواعد تدقيق موصى بها ضمن حزمة\n[`bloc_lint`](https://pub.dev/packages/bloc_lint).\n\nلتمكين هذه المجموعة، أضف حزمة `bloc_lint` كاعتماد تطوير (`dev dependency`):\n\n<InstallBlocLintSnippet />\n\nثم عدّل ملف `analysis_options.yaml` لإضافة مجموعة القواعد:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nعند إصدار نسخة جديدة من `bloc_lint`، قد تبدأ الأكواد البرمجية التي كانت تجتاز\nالتحليل الساكن سابقًا في الفشل. نوصي بتحديث الأكواد البرمجية لتتوافق مع القواعد\nالجديدة، أو تمكين/تعطيل القواعد الفردية حسب الحاجة.\n\n:::\n\n### تمكين القواعد الفردية\n\nلتمكين قواعد فردية، أضف `bloc:` إلى ملف `analysis_options.yaml` كمفتاح من\nالمستوى الأعلى (`top-level key`)، ثم أضف `rules:` كمفتاح من المستوى الثاني. بعد\nذلك، اكتب القواعد المطلوبة على شكل قائمة YAML (مسبوقة بعلامة الشرطة `-`).\n\nعلى سبيل المثال:\n\n<BlocLintEnablingRulesSnippet />\n\n### تعطيل القواعد الفردية\n\nإذا كنت تضمّن مجموعة قواعد موجودة مسبقًا، مثل `recommended`، فقد تحتاج إلى تعطيل\nقاعدة واحدة أو أكثر من القواعد المضمّنة. آلية التعطيل مشابهة للتمكين، لكنها\nتتطلب استخدام YAML map بدلًا من قائمة.\n\nعلى سبيل المثال، الإعداد التالي يستخدم مجموعة القواعد الموصى بها مع تعطيل\n`avoid_public_bloc_methods`، ويُفعّل أيضًا قاعدة `prefer_bloc`:\n\n<BlocLintDisablingRulesSnippet />\n\n## تخصيص شدة القاعدة\n\nيمكنك تغيير شدة أي قاعدة بالشكل التالي:\n\n<BlocLintChangingSeveritySnippet />\n\nالآن سيتم الإبلاغ عن القاعدة نفسها بمستوى `info` بدلًا من `warning`:\n\n<ImportFlutterInfoSnippet />\n\nيجب أن تبدو المخرجات (output) لأمر `bloc lint` كما يلي:\n\n<ImportFlutterInfoOutputSnippet />\n\nمستويات الشدة المدعومة:\n\n| الشدة (`Severity`) | الوصف (`Description`)                      |\n| :----------------- | :----------------------------------------- |\n| `error`            | يشير إلى أن هذا النمط غير مسموح.           |\n| `warning`          | يشير إلى أن النمط مريب لكنه مسموح.         |\n| `info`             | يقدّم معلومات للمستخدم، لكنه لا يعد مشكلة. |\n| `hint`             | يقترح طريقة أفضل للوصول إلى النتيجة.       |\n\n## استثناء الملفات\n\nأحيانًا يكون من المقبول تجاهل فشل التحليل الساكن. على سبيل المثال، قد ترغب في\nتجاهل التحذيرات أو الأخطاء في الأكواد البرمجية المُولّدة (`generated code`) التي\nلم يكتبها فريقك. وكما في قواعد تدقيق Dart الرسمية، يمكنك استخدام خيار المحلل\n`exclude:` لاستثناء ملفات من التحليل الساكن.\n\nيمكنك إما تحديد ملفات بعينها أو استخدام أنماط\n[`glob`](https://pub.dev/packages/glob).\n\n:::note\n\nيجب أن تكون جميع أنماط `glob` نسبيةً إلى المجلد الذي يحتوي على ملف\n`analysis_options.yaml` المقابل.\n\n:::\n\nعلى سبيل المثال، يمكننا استثناء جميع أكواد Dart المُولّدة عبر إعدادات التحليل\nالتالية:\n\n<BlocLintExcludingFilesSnippet />\n\n## تجاهل القواعد\n\nتمامًا كما هو الحال مع قواعد تدقيق Dart الرسمية، يمكنك تجاهل قواعد bloc لملف\nمعيّن أو سطر من الأكواد البرمجية باستخدام `// ignore_for_file` و `// ignore` على\nالتوالي.\n\n:::note\n\nلتجاهل عدة قواعد في سطر أو ملف معيّن، استخدم قائمة مفصولة بفواصل.\n\n:::\n\n### تجاهل الأسطر\n\nيمكنك تجاهل حالات محددة من انتهاكات القواعد بإضافة تعليق `ignore` إما فوق السطر\nالمخالف مباشرة، أو في نهاية السطر نفسه.\n\nعلى سبيل المثال، يمكن تجاهل حالات محددة من `prefer_file_naming_conventions` في\nملف معيّن:\n\n<BlocLintIgnoreForLineSnippet />\n\n### تجاهل الملفات\n\nيمكنك تجاهل جميع حالات انتهاكات القواعد داخل ملف عبر إضافة تعليق\n`ignore_for_file` في أي مكان في الملف.\n\nعلى سبيل المثال، يمكن تجاهل جميع حالات `prefer_file_naming_conventions` في ملف\nمعيّن:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint/index.mdx",
    "content": "---\ntitle: نظرة عامة على أداة التدقيق (Linter Overview)\ndescription: مقدمة إلى أداة التدقيق الخاصة بـ bloc.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nالتدقيق (Linting) هو عملية تحليل ساكن للكود لاكتشاف الأخطاء المحتملة، إلى جانب\nالمشكلات البرمجية ومشكلات الأسلوب.\n\nتوفّر مكتبة Bloc أداة تدقيق مدمجة يمكن استخدامها عبر بيئة التطوير (IDE)، أو من\nخلال [`أدوات سطر أوامر bloc`](https://pub.dev/packages/bloc_tools) باستخدام\nالأمر `bloc lint`.\n\nبمساعدة أداة التدقيق في bloc، يمكنك رفع جودة قاعدة الأكواد البرمجية وفرض الاتساق\nدون تنفيذ أي سطر من الكود.\n\nعلى سبيل المثال، قد تستورد بالخطأ اعتمادًا خاصًا بـ Flutter داخل الـ cubit:\n\n<AvoidFlutterImportsWarningSnippet />\n\nإذا كانت الأداة مُعدّة بشكل صحيح، فستشير إلى هذا الاستيراد وتُظهر التحذير\nالتالي:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nفي الأقسام التالية، سنشرح كيفية تثبيت أداة التدقيق في bloc وإعدادها وتخصيصها،\nحتى تستفيد من التحليل الساكن في مشروعك.\n\n## البدء السريع (Quick Start)\n\nابدأ باستخدام أداة التدقيق في bloc عبر خطوات سريعة وبسيطة.\n\n:::note\n\nلبدء استخدام bloc، يجب أن تكون [Dart SDK](https://dart.dev/get-dart) مثبتة على\nجهازك.\n\n:::\n\n1.  ثبّت [أدوات سطر أوامر bloc](https://pub.dev/packages/bloc_tools)\n\n    <InstallBlocToolsSnippet />\n\n2.  ثبّت حزمة [bloc_lint](https://pub.dev/packages/bloc_lint)\n\n    <InstallBlocLintSnippet />\n\n3.  أضف ملف `analysis_options.yaml` إلى جذر مشروعك مع القواعد الموصى بها\n\n    <BlocLintRecommendedAnalysisOptionsSnippet />\n\n4.  شغّل أداة التدقيق\n\n    <RunBlocLintInCurrentDirectorySnippet />\n\nوهذا كل ما تحتاج إليه 🎉\n\nتابع القراءة للحصول على شرح أعمق لإعداد أداة التدقيق في bloc وتخصيصها.\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint/installation.mdx",
    "content": "---\ntitle: تثبيت أداة التدقيق (Linter Installation)\ndescription: كيفية تثبيت أداة التدقيق الخاصة بـ bloc.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## أدوات سطر الأوامر\n\nلاستخدام أداة التدقيق من سطر الأوامر، ثبّت حزمة\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) عبر الأمر التالي:\n\n<InstallBlocToolsSnippet />\n\nبعد تثبيت أدوات سطر أوامر bloc، يمكنك تشغيل أداة التدقيق عبر الأمر `bloc lint`:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## مجموعة القواعد الموصى بها\n\nلتثبيت مجموعة قواعد التدقيق الموصى بها، ثبّت حزمة\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) كاعتماد تطوير\n(`dev dependency`) باستخدام الأمر التالي:\n\n<InstallBlocLintSnippet />\n\nثم أضف ملف `analysis_options.yaml` إلى جذر المشروع مع مجموعة القواعد الموصى بها:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nعند الحاجة، يمكنك تضمين أكثر من مجموعة قواعد عبر تعريفها كقائمة:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## تكاملات بيئات التطوير المتكاملة (IDE Integrations)\n\nتدعم بيئات التطوير المتكاملة (IDEs) التالية رسميًا أداة التدقيق في bloc وخادم\nاللغة (language server) لتوفير تشخيصات فورية مباشرة داخل بيئة التطوير الخاصة بك.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tيتوفر دعم [إضافة Bloc لـ\n\t\tVSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tابتداءً من الإصدار `v6.8.0`.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tيتوفر دعم [إضافة Bloc لـ\n\t\tIntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) ابتداءً من\n\t\tالإصدار `v4.1.0`.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: تجنب امتدادات BuildContext (Avoid BuildContext Extensions)\ndescription: قاعدة `avoid_build_context_extensions`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nتجنب استخدام امتدادات `BuildContext` للوصول إلى instances من `Bloc` أو `Cubit`.\n\n:::note\n\nتم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.0` من\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint).\n\n:::\n\n## المبرر (Rationale)\n\nلتحقيق الاتساق والوضوح، يفضَّل استخدام الطرق الأساسية (methods) بشكل مباشر بدلًا\nمن امتدادات `BuildContext`. وهذا مفيد أيضًا في الاختبارات، لأنه **لا يمكن**\nإنشاء محاكاة (`mock`) لـ `extension method`.\n\n| الامتداد (extension) | الطريقة الصريحة (explicit method)                                   |\n| -------------------- | ------------------------------------------------------------------- |\n| `context.read`       | `BlocProvider.of<Bloc>(context, listen: false)`                     |\n| `context.watch`      | `BlocBuilder<Bloc, State>(...)` أو `BlocProvider.of<Bloc>(context)` |\n| `context.select`     | `BlocSelector<Bloc, State>(...)`                                    |\n\n## أمثلة (Examples)\n\n**تجنب** استخدام امتدادات `BuildContext` للتعامل مع نسخ (instances) من `Bloc` أو\n`Cubit`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `avoid_build_context_extensions`، أضفها إلى `analysis_options.yaml`\nضمن `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: تجنب استيراد Flutter (Avoid Flutter Imports)\ndescription: قاعدة `avoid_flutter_imports` الخاصة بـ Bloc.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nتجنب إضافة تبعيات (dependencies) على Flutter داخل مكونات منطق الأعمال (Business\nLogic Components) (نسخ (instances) من `Bloc` أو `Cubit`).\n\n## المبرر (Rationale)\n\nيعد تقسيم التطبيق إلى طبقات (Layering an application) جزءًا أساسيًا لبناء قاعدة\nأكواد برمجية قابلة للصيانة (maintainable codebase)، كما يساعد المطورين على\nالتطوير بسرعة وثقة. يجب أن تمتلك كل طبقة مسؤولية واحدة (single responsibility)\nوأن تكون قابلة للعمل والاختبار بشكل مستقل (in isolation). هذا يسهّل حصر\nالتغييرات داخل طبقات محددة ويقلل تأثيرها على التطبيق بالكامل.\n\nبناءً على ذلك، من الأفضل أن تركز مكونات منطق الأعمال (Business Logic Components)\nعلى إدارة حالة الميزة (`feature state`) فقط، مع بقائها مفصولة عن طبقة واجهة\nالمستخدم (UI layer). يجب أن تتدفق الأحداث (`events`) من طبقة واجهة المستخدم (UI\nlayer) إلى مكونات منطق الأعمال (Business Logic Components)، بينما تتدفق الحالة\n(`state`) من طبقة منطق الأعمال (Business Logic layer) إلى طبقة واجهة المستخدم\n(UI layer).\n\nفصل مكونات منطق الأعمال (Business Logic Components) عن Flutter يسهّل إعادة\nاستخدام منطق الأعمال عبر منصات أو أطر عمل (frameworks) متعددة (مثل Flutter\nوAngularDart وJaspr وغيرها).\n\n## أمثلة (Examples)\n\n**لا تقم** باستيراد Flutter ضمن مكونات منطق الأعمال الخاصة بك.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `avoid_flutter_imports`، أضفها إلى `analysis_options.yaml` ضمن\n`bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: تجنب استخدام دوال Bloc العامة\ndescription: قاعدة `avoid_public_bloc_methods`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nتجنب كشف الدوال العامة (public methods) على instances من `Bloc`.\n\n## المبرر (Rationale)\n\nيتفاعل الـ Bloc مع الأحداث الواردة (incoming events) ويُصدر الحالات الصادرة\n(outgoing states). لذلك، الطريقة الموصى بها للتواصل مع instance من `Bloc` هي عبر\nالدالة `add`. في معظم الحالات، لا حاجة لبناء تجريدات إضافية (additional\nabstractions) فوق واجهة `add` (API).\n\n![هيكلية Bloc](~/assets/concepts/bloc_architecture_full.png)\n\n## أمثلة (Examples)\n\n**لا تقم** بكشف الدوال العامة على instances من `Bloc`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `avoid_public_bloc_methods`، أضفها إلى `analysis_options.yaml` ضمن\n`bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: تجنب استخدام الحقول العامة\ndescription: قاعدة `avoid_public_fields`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nتجنب كشف الحقول العامة (public fields) على instances من `Bloc` و`Cubit`.\n\n## المبرر (Rationale)\n\nتحافظ مكونات منطق الأعمال (Business Logic Components) على `state` الخاص بها\nوتُصدر تغييرات الحالة عبر واجهة `emit` (API). لذلك، يجب كشف كل الحالة الظاهرة\nللعامة (public-facing state) من خلال كائن `state`.\n\n## أمثلة (Examples)\n\n**لا تقم** بكشف الحقول العامة على instances من `Bloc` و`Cubit`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `avoid_public_fields`، أضفها إلى `analysis_options.yaml` ضمن\n`bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: تفضيل Bloc\ndescription: قاعدة `prefer_bloc`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nيفضَّل استخدام instances من `Bloc` بدلًا من instances من `Cubit`.\n\n## المبرر (Rationale)\n\nهذه قاعدة أسلوبية (stylistic rule) بحتة. في بعض الحالات، قد تفضّل الفرق توحيد\nاستخدام instances من `Bloc` فقط على مستوى التطبيق بالكامل لتحقيق الاتساق\n(consistency).\n\n:::tip\n\nتعرّف على المزيد حول مزايا `Bloc` في\n[المفاهيم الأساسية](/ar/bloc-concepts/#مزايا-bloc).\n\n:::\n\n## أمثلة (Examples)\n\n**تجنب** استخدام instances من `Cubit`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `prefer_bloc`، أضفها إلى `analysis_options.yaml` ضمن `bloc` >\n`rules`:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: تفضيل استخدام امتدادات BuildContext\ndescription: قاعدة `prefer_build_context_extensions`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nيفضَّل استخدام امتدادات `BuildContext` للوصول إلى instance من `Bloc` أو\n`Repository`.\n\n:::note\n\nتم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.2` من\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint).\n\n:::\n\n## المبرر (Rationale)\n\nلتحقيق الاتساق (consistency)، يفضَّل استخدام امتدادات `BuildContext` مثل\n`context.read` و`context.watch` و`context.select` بدلًا من `BlocProvider.of` و\n`RepositoryProvider.of` و`BlocBuilder` و`BlocSelector`.\n\n| الطريقة الصريحة (explicit method)                                   | الامتداد (extension)  |\n| ------------------------------------------------------------------- | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                     | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` أو `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                    | `context.select`      |\n\n## أمثلة (Examples)\n\n**تجنب** استخدام `BlocProvider.of<T>(context)` للوصول إلى instance من `Bloc`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `prefer_build_context_extensions`، أضفها إلى\n`analysis_options.yaml` ضمن `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: تفضيل Cubit\ndescription: قاعدة `prefer_cubit`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nيفضَّل استخدام instances من `Cubit` بدلًا من instances من `Bloc`.\n\n## المبرر (Rationale)\n\nهذه قاعدة أسلوبية (stylistic rule) بحتة. في بعض الحالات، قد تفضّل الفرق توحيد\nاستخدام instances من `Cubit` فقط على مستوى التطبيق بالكامل لتحقيق الاتساق\n(consistency).\n\n:::tip\n\nتعرّف على المزيد حول مزايا `Cubit` في\n[المفاهيم الأساسية](/ar/bloc-concepts/#مزايا-cubit).\n\n:::\n\n## أمثلة (Examples)\n\n**تجنب** استخدام instances من `Bloc`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `prefer_cubit`، أضفها إلى `analysis_options.yaml` ضمن `bloc` >\n`rules`:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: تفضيل اصطلاحات تسمية الملفات\ndescription: قاعدة `prefer_file_naming_conventions`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nيفضَّل اتباع اصطلاحات تسمية الملفات.\n\n:::note\n\nتم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.0` من حزمة\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## المبرر (Rationale)\n\nلتحقيق الاتساق وسهولة الصيانة وفصل الاهتمامات (separation of concerns)، يفضَّل\nتعريف instances من `bloc` و`cubit` في ملفات Dart الخاصة بها بدلًا من تضمينها\nمباشرة (inlining).\n\n:::tip\n\nفكر في استخدام الأمر `bloc new <component>` من حزمة\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) لإنشاء instances جديدة\nمن bloc/cubit بسرعة وبشكل متسق.\n\n:::\n\n## أمثلة (Examples)\n\n**يفضَّل** التصريح عن instances من bloc/cubit في ملفاتها الخاصة.\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `prefer_file_naming_conventions`، أضفها إلى ملف\n`analysis_options.yaml` ضمن `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: تفضيل الدوال العامة ذات القيمة المرتجعة Void في Cubit\ndescription: قاعدة `prefer_void_public_cubit_methods`.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nيفضَّل أن تكون الدوال العامة (public methods) في instances من `Cubit` ذات قيمة\nمرتجعة `void`.\n\n:::note\n\nتم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.2.0-dev.2` من حزمة\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## المبرر (Rationale)\n\nيجب استخدام الدوال العامة (public methods) في instances من `Cubit` لإخطار\n`Cubit` وبدء تغييرات الحالة عبر الدالة `emit`. وإذا احتاج المستدعي (caller) إلى\nأي معلومات عن الحالة، فيفترض أن يحصل عليها من `state` بدلًا من ذلك.\n\n:::note\n\nالقواعد التالية مرتبطة، وغالبًا ما تُفعّل مع قاعدة\n`prefer_void_public_cubit_methods`:\n\n- [`avoid_public_bloc_methods`](/ar/lint-rules/avoid_public_bloc_methods)\n- [`avoid_public_fields`](/ar/lint-rules/avoid_public_fields)\n\n:::\n\n## أمثلة (Examples)\n\n**تجنب** الدوال العامة (public methods) ذات القيمة المرتجعة غير `void` في\ninstances من `Cubit`.\n\n**مثال سيئ (BAD)**:\n\n<BadSnippet />\n\n**مثال جيد (GOOD)**:\n\n<GoodSnippet />\n\n## التفعيل (Enable)\n\nلتفعيل قاعدة `prefer_void_public_cubit_methods`، أضفها إلى ملف\n`analysis_options.yaml` ضمن `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/ar/migration.mdx",
    "content": "---\ntitle: دليل الترقية (Migration Guide)\ndescription: الترقية إلى أحدث إصدار مستقر من Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nيرجى الرجوع إلى [سجل الإصدارات](https://github.com/felangel/bloc/releases)\nللاطلاع على تفاصيل التغييرات في كل إصدار.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ فصل `blocTest` عن `BlocBase`\n\n:::note[ما الذي تغير؟]\n\nفي إصدار bloc_test v10.0.0، لم يعد واجهة برمجة التطبيقات (API) لـ `blocTest`\nمرتبطة بشكل وثيق بـ `BlocBase`.\n\n:::\n\n##### الأسباب (Rationale)\n\nيجب أن يستخدم `blocTest` واجهات bloc الأساسية كلما أمكن ذلك لزيادة المرونة\nوقابلية إعادة الاستخدام. سابقاً، لم يكن هذا ممكناً لأن `BlocBase` كان يطبق\n`StateStreamableSource` وهو ما لم يكن كافياً لـ `blocTest` بسبب الاعتماد الداخلي\nعلى API الـ `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ دعم WebAssembly\n\n:::note[ما الذي تغير؟]\n\nفي إصدار hydrated_bloc v10.0.0، تمت إضافة دعم للترجمة البرمجية (compiling) إلى\nWebAssembly (wasm).\n\n:::\n\n##### الأسباب (Rationale)\n\nسابقاً، لم يكن من الممكن ترجمة التطبيقات إلى wasm عند استخدام `hydrated_bloc`.\nفي الإصدار v10.0.0، تمت إعادة هيكلة الحزمة للسماح بالترجمة إلى wasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 إزالة الواجهات المهجورة\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v9.0.0، تمت إزالة جميع الواجهات التي تم الإعلان عنها سابقًا كـ\n\"مهجورة\" (deprecated).\n\n:::\n\n##### ملخص\n\n- تمت إزالة `BlocOverrides` واستبداله بـ `Bloc.observer` و `Bloc.transformer`.\n\n#### ❗✨ تقديم واجهة `EmittableStateStreamableSource`\n\n:::note[ما الذي تغير؟]\n\nفي هذا الإصدار، تم تقديم واجهة أساسية جديدة باسم\n`EmittableStateStreamableSource`.\n\n:::\n\n##### السبب\n\nكان `package:bloc_test` مرتبطًا بشكل مباشر بـ `BlocBase`. تم تقديم هذه الواجهة\nلفصل `blocTest` عن التنفيذ الفعلي لـ `BlocBase`.\n\n### `package:hydrated_bloc`\n\n#### ✨ إعادة تقديم `HydratedBloc.storage`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار hydrated_bloc v9.0.0، تمت إزالة `HydratedBlocOverrides` واستبداله\nباستخدام `HydratedBloc.storage`.\n\n:::\n\n##### السبب\n\nراجع:\n[سبب إعادة تقديم Bloc.observer و Bloc.transformer](/ar/migration/#-إعادة-تقديم-واجهات-برمجة-التطبيقات-blocobserver-و-bloctransformer)\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ إعادة تقديم واجهات برمجة التطبيقات `Bloc.observer` و `Bloc.transformer`\n\n:::note[ما الذي تغير؟]\n\nفي إصدار bloc v8.1.0، تم وضع علامة \"مهجور\" على `BlocOverrides` لصالح واجهات\n`Bloc.observer` و `Bloc.transformer`.\n\n:::\n\n##### الأسباب (Rationale)\n\nتم تقديم API الـ `BlocOverrides` في الإصدار v8.0.0 في محاولة لدعم نطاق (scoping)\nإعدادات الـ bloc المحددة مثل `BlocObserver` و `EventTransformer` و\n`HydratedStorage`. في تطبيقات Dart الصرفة، عملت التغييرات بشكل جيد؛ ومع ذلك، في\nتطبيقات Flutter، تسبب API الجديد في مشاكل أكثر مما حلها.\n\nتم استلهام API الـ `BlocOverrides` من واجهات مماثلة في Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**المشاكل**\n\nعلى الرغم من أنه لم يكن السبب الرئيسي لهذه التغييرات، إلا أن API الـ\n`BlocOverrides` أضاف تعقيداً إضافياً للمطورين. فبالإضافة إلى زيادة مقدار التداخل\n(nesting) وعدد أسطر الكود اللازمة لتحقيق نفس التأثير، تطلب API الـ\n`BlocOverrides` من المطورين أن يكون لديهم فهم قوي للـ\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) في Dart.\nالـ `Zones` ليست مفهوماً سهلاً للمبتدئين، والفشل في فهم كيفية عملها قد يؤدي إلى\nظهور أخطاء (مثل عدم تهيئة المراقبين، المحولات، أو مثيلات التخزين).\n\nعلى سبيل المثال، كان لدى العديد من المطورين كود مثل:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nالكود أعلاه قد يبدو بسيطًا وغير ضار، لكنه قد يؤدي فعليًا إلى أخطاء يصعب تتبعها.\n\nالـ Zone التي يتم استدعاء `WidgetsFlutterBinding.ensureInitialized` منها في\nالبداية ستكون هي نفسها المستخدمة لمعالجة أحداث الواجهة (مثل `onTap` و\n`onPressed`) وذلك بسبب `GestureBinding.initInstances`.\n\nوهذا مجرد مثال واحد من العديد من المشاكل الناتجة عن استخدام `zoneValues`.\n\nبالإضافة إلى ذلك، يقوم Flutter بالعديد من العمليات خلف الكواليس التي تتضمن إنشاء\nأو تعديل الـ Zones (خصوصًا أثناء تشغيل الاختبارات)، مما قد يؤدي إلى سلوك غير\nمتوقع، وفي بعض الحالات سلوك خارج عن سيطرة المطور (انظر المشاكل أدناه).\n\nبسبب استخدام\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html)، أدى\nالانتقال إلى `BlocOverrides` إلى اكتشاف عدة مشاكل وقيود في Flutter، خاصة في\nاختبارات Widget و Integration:\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nكما أثرت هذه المشاكل على العديد من مستخدمي مكتبة bloc:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ تقديم `BlocOverrides`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، تمت إزالة `Bloc.observer` و `Bloc.transformer`\nواستبدالهما بـ `BlocOverrides`.\n\n:::\n\n##### السبب\n\nكان النهج السابق لتجاوز `BlocObserver` و `EventTransformer` يعتمد على singleton\nعام لكل منهما.\n\nونتيجة لذلك، لم يكن من الممكن:\n\n- استخدام عدة تطبيقات مختلفة من `BlocObserver` أو `EventTransformer` ضمن أجزاء\n  مختلفة من التطبيق\n- حصر (scoping) هذه الإعدادات داخل حزمة (package) معينة\n  - في حال قامت حزمة تعتمد على `package:bloc` بتسجيل `BlocObserver` خاص بها،\n    سيُجبر المستخدم إما على استبداله أو التعامل معه كما هو\n\nكما أن هذا النهج جعل عملية الاختبار أكثر صعوبة بسبب وجود حالة عامة مشتركة بين\nالاختبارات.\n\nيقدم الإصدار v8.0.0 فئة `BlocOverrides` التي تتيح للمطورين تخصيص `BlocObserver`\nو/أو `EventTransformer` ضمن `Zone` محددة، بدلاً من الاعتماد على singleton عام\nقابل للتغيير.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nستستخدم كائنات `Bloc` كلًا من `BlocObserver` و/أو `EventTransformer` الخاصة بالـ\n`Zone` الحالية عبر `BlocOverrides.current`. في حال عدم وجود `BlocOverrides` لهذه\nالـ `Zone`، سيتم استخدام القيم الافتراضية الداخلية (دون أي تغيير في السلوك أو\nالوظائف).\n\nيسمح ذلك لكل `Zone` بالعمل بشكل مستقل باستخدام `BlocOverrides` الخاصة بها.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA and eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Blocs in this zone report to BlocObserverA\n    // and use eventTransformerA as the default transformer.\n    // ...\n\n    // Later...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB and eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Blocs in this zone report to BlocObserverB\n        // and use eventTransformerB as the default transformer.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ تحسين معالجة الأخطاء والإبلاغ عنها\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، تمت إزالة `BlocUnhandledErrorException`.\n\nكما يتم الآن دائمًا الإبلاغ عن أي استثناء غير مُعالج إلى `onError` ثم إعادة رميه\n(بغض النظر عن وضع debug أو release).\n\nأما `addError`، فيقوم بالإبلاغ عن الأخطاء إلى `onError`، لكنه لا يعتبر هذه\nالأخطاء استثناءات غير مُعالجة.\n\n:::\n\n##### السبب\n\nتهدف هذه التغييرات إلى:\n\n- جعل الاستثناءات غير المُعالجة واضحة بشكل أكبر مع الحفاظ على استقرار bloc\n- دعم استخدام `addError` دون التأثير على تدفق التنفيذ\n\nسابقًا، كان سلوك معالجة الأخطاء يختلف بين وضعي debug و release.\n\nكما أن الأخطاء المُبلغ عنها عبر `addError` كانت تُعامل كاستثناءات غير مُعالجة في\nوضع debug، مما كان يؤدي إلى تجربة غير جيدة للمطورين (خصوصًا عند كتابة اختبارات\nالوحدة).\n\nفي الإصدار v8.0.0، يمكن استخدام `addError` بأمان للإبلاغ عن الأخطاء، كما يمكن\nاستخدام `blocTest` للتحقق من ذلك.\n\nلا تزال جميع الأخطاء تُرسل إلى `onError`، ولكن يتم إعادة رمي الاستثناءات غير\nالمُعالجة فقط (بغض النظر عن وضع التشغيل).\n\n#### ❗🧹 جعل `BlocObserver` فئة مجردة\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، تم تحويل `BlocObserver` إلى فئة `abstract`، مما يعني أنه\nلا يمكن إنشاء instance منها مباشرة.\n\n:::\n\n##### السبب\n\nتم تصميم `BlocObserver` ليكون بمثابة واجهة (interface).\n\nوبما أن التنفيذ الافتراضي لا يحتوي على أي سلوك (no-op)، فقد تم تحويله إلى فئة\n`abstract` لتوضيح أنه مخصص للتوسيع (extension) وليس للاستخدام المباشر.\n\n```dart\nvoid main() {\n  // كان من الممكن إنشاء مثيل من الفئة الأساسية.\nfinal observer = BlocObserver();\n}\n\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // لا يمكن إنشاء مثيل من الفئة الأساسية.\n  final observer = BlocObserver(); // خطأ (ERROR)\n\n  // قم بتوسيع `BlocObserver` بدلاً من ذلك.\n  final observer = MyBlocObserver(); // مقبول (OK)\n}\n```\n\n#### ❗✨ استدعاء `add` يرمي `StateError` عند إغلاق الـ Bloc\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، يؤدي استدعاء `add` على Bloc مغلق إلى رمي `StateError`.\n\n:::\n\n##### السبب\n\nسابقًا، كان من الممكن استدعاء `add` على Bloc مغلق، وكان الخطأ الداخلي يتم\nتجاهله، مما يجعل من الصعب معرفة سبب عدم معالجة الحدث.\n\nلجعل هذا السيناريو أكثر وضوحًا، في v8.0.0، يؤدي استدعاء `add` على Bloc مغلق إلى\nرمي `StateError`، ويتم التعامل معه كاستثناء غير مُعالج ويتم تمريره إلى\n`onError`.\n\n#### ❗✨ استدعاء `emit` يرمي `StateError` عند إغلاق الـ Bloc\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، يؤدي استدعاء `emit` داخل Bloc مغلق إلى رمي `StateError`.\n\n:::\n\n##### السبب\n\nسابقًا، كان من الممكن استدعاء `emit` داخل Bloc مغلق دون حدوث أي تغيير في الحالة،\nودون وجود أي مؤشر على الخطأ، مما يجعل عملية التصحيح صعبة.\n\nلجعل هذا السلوك أكثر وضوحًا، في v8.0.0، يؤدي استدعاء `emit` داخل Bloc مغلق إلى\nرمي `StateError`، ويتم التعامل معه كاستثناء غير مُعالج ويتم تمريره إلى\n`onError`.\n\n#### ❗🧹 إزالة الواجهات المهجورة\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v8.0.0، تمت إزالة جميع الواجهات التي تم الإعلان عنها سابقًا كـ\nمهجورة (deprecated).\n\n:::\n\n##### ملخص\n\n- تمت إزالة `mapEventToState` واستبداله بـ `on<Event>`\n- تمت إزالة `transformEvents` واستبداله بـ `EventTransformer`\n- تمت إزالة `TransitionFunction` واستبداله بـ `EventTransformer`\n- تمت إزالة `listen` واستبداله بـ `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ لم يعد `MockBloc` و `MockCubit` يتطلبان `registerFallbackValue`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc_test v9.0.0، لم يعد من الضروري استدعاء `registerFallbackValue`\nعند استخدام `MockBloc` أو `MockCubit`.\n\n:::\n\n##### ملخص\n\nيُستخدم `registerFallbackValue` فقط عند استخدام `any()` من `package:mocktail` مع\nأنواع مخصصة.\n\nسابقًا، كان يجب استدعاؤه لكل `Event` و `State` عند استخدام `MockBloc` أو\n`MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // الاختبارات...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // الاختبارات...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ تقديم `HydratedBlocOverrides`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار hydrated_bloc v8.0.0، تمت إزالة `HydratedBloc.storage` واستبداله بـ\n`HydratedBlocOverrides`.\n\n:::\n\n##### السبب\n\nسابقًا، كان يتم الاعتماد على singleto عام لتخصيص تنفيذ `Storage`.\n\nونتيجة لذلك، لم يكن من الممكن استخدام أكثر من تنفيذ لـ `Storage` ضمن أجزاء\nمختلفة من التطبيق.\n\nكما أن هذا النهج جعل الاختبارات أكثر صعوبة بسبب وجود حالة عامة مشتركة بين\nالاختبارات.\n\nيقدم الإصدار v8.0.0 فئة `HydratedBlocOverrides` التي تتيح للمطورين تخصيص\n`Storage` ضمن `Zone` محددة، بدلاً من الاعتماد على singleton عام قابل للتغيير.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\nستستخدم كائنات `HydratedBloc` الـ `Storage` الخاصة بالـ `Zone` الحالية عبر\n`HydratedBlocOverrides.current`.\n\nيسمح ذلك لكل `Zone` بالعمل بشكل مستقل باستخدام الإعدادات الخاصة بها.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ تقديم `on<Event>`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v7.2.0، تم إيقاف استخدام `mapEventToState` (deprecated)\nواستبداله بـ `on<Event>`. وسيتم إزالة `mapEventToState` في الإصدار v8.0.0.\n\n:::\n\n##### السبب\n\nتم تقديم `on<Event>` كجزء من:\n[مقترح: استبدال mapEventToState بـ `on<Event>` في Bloc](https://github.com/felangel/bloc/issues/2526).\n\nبسبب [مشكلة في Dart](https://github.com/dart-lang/sdk/issues/44616)، قد لا يكون\nمن الواضح دائمًا ما ستكون عليه قيمة `state` عند التعامل مع مولدات غير متزامنة\nمتداخلة (`async*`).\n\nعلى الرغم من وجود حلول بديلة، إلا أن أحد المبادئ الأساسية لمكتبة bloc هو\nالقابلية للتنبؤ.\n\nلذلك، تم تقديم `on<Event>` لجعل استخدام المكتبة أكثر أمانًا وتقليل أي غموض متعلق\nبتغيّر الحالة.\n\n:::tip\n\nلمزيد من التفاصيل:\n[اقرأ المقترح الكامل](https://github.com/felangel/bloc/issues/2526)\n\n:::\n\n##### ملخص\n\nيتيح لك `on<E>` تسجيل معالج أحداث لجميع الأحداث من النوع `E`.\n\nبشكل افتراضي، تتم معالجة الأحداث بشكل متوازي (concurrently) عند استخدام `on<E>`،\nعلى عكس `mapEventToState` الذي يعالج الأحداث بشكل تسلسلي (sequentially).\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nيعمل كل `EventHandler` بشكل مستقل، لذلك من المهم تسجيل معالجات الأحداث بناءً على\nنوع الـ transformer الذي ترغب في استخدامه.\n\n:::\n\nإذا كنت ترغب في الحفاظ على نفس السلوك تمامًا كما في v7.1.0، يمكنك تسجيل معالج\nأحداث واحد لجميع الأحداث وتطبيق transformer تسلسلي (`sequential`).\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: المنطق البرمجي يوضع هنا...\n  }\n}\n```\n\nيمكنك أيضًا تجاوز `EventTransformer` الافتراضي لجميع الـ blocs في تطبيقك:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ تقديم `EventTransformer`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v7.2.0، تم إيقاف استخدام `transformEvents` (deprecated)\nواستبداله بـ `EventTransformer`. وسيتم إزالة `transformEvents` في الإصدار\nv8.0.0.\n\n:::\n\n##### السبب\n\nأتاح `on<Event>` إمكانية تخصيص event transformer لكل معالج أحداث (event\nhandler).\n\nتم تقديم typedef جديد باسم `EventTransformer`، والذي يتيح للمطورين تحويل تدفق\nالأحداث الواردة لكل معالج بشكل مستقل، بدلاً من استخدام transformer واحد لكافة\nالأحداث.\n\n##### ملخص\n\nيقوم `EventTransformer` باستقبال تدفق الأحداث الواردة مع `EventMapper` (معالج\nالأحداث)، وإرجاع تدفق جديد من الأحداث.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nيقوم الـ `EventTransformer` الافتراضي بمعالجة جميع الأحداث بشكل متزامن ويبدو\nكالتالي:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nتحقق من [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency)\nللحصول على مجموعة مختارة من محولات الأحداث المخصصة.\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// تعريف EventTransformer مخصص\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// تطبيق EventTransformer المخصص على معالج الحدث\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ إيقاف استخدام `transformTransitions`\n\n:::note[ما الذي تغير؟]\n\nفي الإصدار bloc v7.2.0، تم إيقاف استخدام `transformTransitions` (deprecated)\nواستبداله بتخصيص `stream`.\n\nوسيتم إزالة `transformTransitions` في الإصدار v8.0.0.\n\n:::\n\n##### السبب\n\nيتيح getter الخاص بـ `stream` في `Bloc` تخصيص تدفق الحالات (states) الخارجة\nبسهولة، لذلك لم تعد هناك حاجة للاحتفاظ بواجهة منفصلة مثل `transformTransitions`.\n\n##### ملخص\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ أصبح كل من `Bloc` و `Cubit` يعتمدان على `BlocBase`\n\n##### السبب\n\nكمطور، كانت العلاقة بين blocs و cubits غير واضحة إلى حدٍ ما.\n\nعند تقديم Cubit لأول مرة، كان يُستخدم كفئة أساسية (base class) لـ blocs، وهو ما\nكان منطقيًا لأنه يوفر جزءًا من الوظائف، بينما تقوم blocs بتوسيعه وإضافة المزيد\nمن الـ APIs.\n\nلكن هذا التصميم أدى إلى بعض المشاكل:\n\n- كان من الضروري إما إعادة تسمية APIs لتدعم cubit بشكل دقيق، أو الإبقاء على\n  تسميات bloc للحفاظ على الاتساق، رغم أن ذلك غير دقيق من الناحية الهيكلية  \n  ([#1708](https://github.com/felangel/bloc/issues/1708)، [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- كان على Cubit أن يرث من `Stream` ويطبق `EventSink` لتوفير قاعدة مشتركة يمكن أن\n  تعتمد عليها مكونات مثل `BlocBuilder` و `BlocListener`  \n  ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\n---\n\nلاحقًا، تم تجربة عكس العلاقة وجعل bloc هو الفئة الأساسية.\n\nهذا الحل عالج جزئيًا المشكلة الأولى، لكنه تسبب في مشاكل أخرى:\n\n- أصبح API الخاص بـ cubit معقدًا بسبب وراثة APIs من bloc مثل `mapEventToState` و\n  `add` وغيرها  \n  ([#2228](https://github.com/felangel/bloc/issues/2228))\n\n  - ويمكن للمطورين استخدام هذه APIs بشكل غير صحيح مما يؤدي إلى مشاكل\n\n- استمرت مشكلة كشف cubit لكامل واجهة `Stream` كما كانت سابقًا  \n  ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nلحل هذه التحديات، تم تقديم فئة أساسية جديدة مشتركة بين `Bloc` و `Cubit` باسم\n`BlocBase`.\n\nيسمح ذلك للمكونات الأعلى في التطبيق بالتعامل مع bloc و cubit بطريقة موحدة، دون\nالحاجة إلى كشف كامل واجهات `Stream` و `EventSink`.\n\n##### ملخص\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗ `seed` تُعيد دالة لدعم القيم الديناميكية\n\n##### السبب\n\nلدعم استخدام قيمة seed قابلة للتغيير ويمكن تحديثها ديناميكيًا داخل `setUp`،\nأصبحت `seed` تُعيد دالة بدلاً من قيمة ثابتة.\n\n##### ملخص\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗ `expect` تُعيد دالة لدعم القيم الديناميكية مع دعم `Matchers`\n\n##### السبب\n\nلدعم استخدام توقع (expectation) قابل للتغيير ويمكن تحديثه ديناميكيًا داخل\n`setUp`، أصبحت `expect` تُعيد دالة بدلاً من قيمة ثابتة.\n\nكما تدعم `expect` استخدام `Matchers`.\n\n##### ملخص\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// يمكن أن يكون أيضاً `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗ `errors` تُعيد دالة لدعم القيم الديناميكية مع دعم `Matchers`\n\n##### السبب\n\nلدعم استخدام قائمة أخطاء (errors) قابلة للتغيير ويمكن تحديثها ديناميكيًا داخل\n`setUp`، أصبحت `errors` تُعيد دالة بدلاً من قيمة ثابتة.\n\nكما تدعم `errors` استخدام `Matchers`.\n\n##### ملخص\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// يمكن أن يكون أيضاً `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗ `MockBloc` و `MockCubit`\n\n##### السبب\n\nلدعم محاكاة (stubbing) واجهات برمجة التطبيقات الأساسية بشكل أفضل، تم توفير\n`MockBloc` و `MockCubit` ضمن حزمة `bloc_test`.\n\nسابقًا، كان يتم استخدام `MockBloc` لكل من `Bloc` و `Cubit`، وهو ما لم يكن واضحًا\nأو بديهيًا للمطورين.\n\n##### ملخص\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗ التكامل مع `Mocktail`\n\n##### السبب\n\nبسبب القيود المختلفة في الحزمة الداعمة لـ null-safety\n[package:mockito](https://pub.dev/packages/mockito) والمُوضحة\n[هنا](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing)،\nتم اعتماد [package:mocktail](https://pub.dev/packages/mocktail) في `MockBloc` و\n`MockCubit`.\n\nيتيح ذلك للمطورين استخدام واجهة محاكاة مألوفة دون الحاجة إلى كتابة stubs يدويًا\nأو الاعتماد على توليد الكود.\n\n##### ملخص\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> يرجى الرجوع إلى [#347](https://github.com/dart-lang/mockito/issues/347)\n> بالإضافة إلى\n> [توثيق mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> لمزيد من التفاصيل.\n\n### `package:flutter_bloc`\n\n#### ❗ إعادة تسمية المعامل `cubit` إلى `bloc`\n\n##### السبب\n\nنتيجة لإعادة الهيكلة في `package:bloc` وتقديم `BlocBase` الذي يتم توريثه من قبل\nكلٍ من `Bloc` و `Cubit`، تم تغيير اسم المعامل في `BlocBuilder` و `BlocConsumer`\nو `BlocListener` من `cubit` إلى `bloc`، لأن هذه الواجهات تعمل على النوع\n`BlocBase`.\n\nكما أن هذا التغيير يتماشى بشكل أفضل مع اسم المكتبة، ويساهم في تحسين وضوح وقابلية\nقراءة الكود.\n\n##### ملخص\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗ أصبح `storageDirectory` مطلوبًا عند استدعاء `HydratedStorage.build`\n\n##### السبب\n\nلجعل `package:hydrated_bloc` حزمة Dart صِرفة، تم إزالة الاعتماد على\n[package:path_provider](https://pub.dev/packages/path_provider).\n\nوبالتالي، أصبح تمرير المعامل `storageDirectory` عند استدعاء\n`HydratedStorage.build` إلزاميًا، ولم يعد يتم تعيينه تلقائيًا إلى\n`getTemporaryDirectory`.\n\n##### ملخص\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗ تم وضع علامة \"مهجور\" على `context.bloc` و `context.repository` لصالح `context.read` و `context.watch`\n\n##### السبب\n\nتم تقديم `context.read` و `context.watch` و `context.select` لتتوافق مع واجهة\nبرمجة التطبيقات الخاصة بـ [provider](https://pub.dev/packages/provider)، والتي\nيألفها العديد من المطورين، وكذلك لمعالجة المشكلات التي تم طرحها من قبل المجتمع.\n\nولتحسين أمان الكود والحفاظ على الاتساق، تم وضع علامة \"مهجور\" على `context.bloc`،\nحيث يمكن استبداله بـ `context.read` أو `context.watch` اعتمادًا على ما إذا كان\nيتم استخدامه مباشرة داخل `build`.\n\n##### `context.watch`\n\nيوفر `context.watch` بديلاً عمليًا لطلب\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538)، حيث يمكن مراقبة\nعدة blocs داخل `Builder` واحد من أجل بناء واجهة المستخدم بناءً على حالات متعددة.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // إرجاع Widget يعتمد على حالة BlocA و BlocB و BlocC\n  }\n);\n```\n\n##### `context.select`\n\nيتيح `context.select` للمطورين إعادة بناء (rebuild) واجهة المستخدم بناءً على جزء\nمحدد من حالة الـ bloc، كما يوفر بديلاً أبسط لاستخدام\n[buildWhen](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nتسمح لنا القطعة البرمجية أعلاه بالوصول إلى الـ widget وإعادة بنائه فقط عندما\nيتغير اسم المستخدم الحالي.\n\n##### `context.read`\n\nعلى الرغم من أن `context.read` يبدو مشابهًا لـ `context.bloc`، إلا أن هناك\nاختلافات دقيقة لكنها مهمة. كلاهما يتيح لك الوصول إلى الـ bloc باستخدام\n`BuildContext` دون التسبب في إعادة بناء (rebuild)؛ ومع ذلك، لا يمكن استخدام\n`context.read` مباشرة داخل دالة `build`.\n\nكان هناك سببان رئيسيان لاستخدام `context.bloc` داخل `build`:\n\n1. **للوصول إلى حالة الـ bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nيُعد الاستخدام أعلاه عرضة للأخطاء، لأن عنصر `Text` لن تتم إعادة بنائه عند تغيّر\nحالة الـ bloc. في هذه الحالة، يُنصح باستخدام `BlocBuilder` أو `context.watch`.\n``\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nأو\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nسيؤدي استخدام `context.watch` في جذر دالة `build` إلى إعادة بناء الـ widget\nبالكامل عند تغيّر حالة الـ bloc. إذا لم تكن هناك حاجة لإعادة بناء الـ widget\nبالكامل، يمكنك استخدام `BlocBuilder` لتغليف الأجزاء التي تحتاج إلى إعادة بناء،\nأو استخدام `Builder` مع `context.watch` لتحديد نطاق إعادة البناء، أو تقسيم الـ\nwidget إلى مكونات أصغر.\n\n:::\n\n2. **للوصول إلى الـ bloc من أجل إضافة حدث**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nالاستخدام أعلاه غير فعال لأنه يؤدي إلى البحث عن الـ bloc في كل عملية إعادة بناء\nبينما لا تكون هناك حاجة للـ bloc إلا عندما ينقر المستخدم على الـ\n`ElevatedButton`. في هذا السيناريو، يفضل استخدام `context.read` للوصول إلى الـ\nbloc مباشرة حيث تبرز الحاجة إليه (في هذه الحالة، في استدعاء `onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n##### ملخص\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> إذا كنت بحاجة للوصول إلى bloc لإضافة حدث، فاستخدم `context.read` داخل الـ\ncallback في المكان الذي تحتاجه فيه.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> استخدم `context.watch` عند الوصول إلى حالة الـ bloc لضمان إعادة بناء الـ\nwidget عند تغيّر الحالة.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗ أصبح `BlocObserver.onError` يستقبل `Cubit`\n\n##### السبب\n\nبسبب دمج `Cubit`، أصبحت `onError` مشتركة بين كلٍ من `Bloc` و `Cubit`. وبما أن\n`Cubit` هو الفئة الأساسية، فإن `BlocObserver` يستقبل نوع `Cubit` بدلاً من `Bloc`\nعند تجاوز `onError`.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗ لا يقوم `Bloc` بإصدار الحالة الأخيرة عند الاشتراك\n\n##### السبب\n\nتم إجراء هذا التغيير لمواءمة سلوك `Bloc` و `Cubit` مع سلوك `Stream` المدمج في\nDart.\n\nبالإضافة إلى ذلك، أدى الالتزام بالسلوك السابق في `Cubit` إلى ظهور العديد من\nالآثار الجانبية غير المقصودة، كما زاد من تعقيد التنفيذ الداخلي لحزم أخرى مثل\n`flutter_bloc` و `bloc_test` دون داعٍ (مثل الحاجة إلى استخدام `skip(1)` وغيرها).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nسابقًا، كان الكود أعلاه يقوم بإخراج (output) الحالة الأولية للـ bloc، تليها\nتغييرات الحالة اللاحقة.\n\n**v6.x.x**\n\nفي الإصدار v6.0.0، لم يعد الكود أعلاه يخرج الحالة الأولية، وإنما يقتصر على إخراج\nتغييرات الحالة اللاحقة فقط. يمكن استعادة السلوك السابق باستخدام ما يلي:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **ملاحظة**: يؤثر هذا التغيير فقط على الكود الذي يعتمد على الاشتراك المباشر في\nالـ bloc. عند استخدام `BlocBuilder` أو `BlocListener` أو `BlocConsumer`، لن يكون\nهناك أي تغيير ملحوظ في السلوك.\n\n### `package:bloc_test`\n\n#### ❗ `MockBloc` يتطلب نوع الحالة (`State`) فقط\n\n##### السبب\n\nلم يعد من الضروري تحديد أنواع إضافية، مما يقلل من الكود الزائد، كما يجعل\n`MockBloc` متوافقًا مع `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗ `whenListen` يتطلب نوع الحالة (`State`) فقط\n\n##### السبب\n\nلم يعد من الضروري تحديد أنواع إضافية، مما يقلل من الكود الزائد، كما يجعل\n`whenListen` متوافقًا مع `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗ `blocTest` لا يتطلب نوع الحدث (`Event`)\n\n##### السبب\n\nلم يعد من الضروري تحديد نوع الحدث، مما يقلل من الكود الزائد، كما يجعل `blocTest`\nمتوافقًا مع `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗ القيمة الافتراضية لـ `skip` في `blocTest` أصبحت 0\n\n##### السبب\n\nبما أن مثيلات `bloc` و `cubit` لم تعد تُصدر الحالة الأخيرة عند الاشتراك الجديد،\nلم يعد من الضروري تعيين القيمة الافتراضية لـ `skip` إلى `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nيمكن اختبار الحالة الأولية لـ bloc أو cubit بما يلي:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗ جعل `build` في `blocTest` متزامنًا (synchronous)\n\n##### السبب\n\nسابقًا، كان يتم جعل `build` غير متزامن (`async`) لإتاحة تنفيذ بعض التحضيرات من\nأجل وضع الـ bloc تحت الاختبار في حالة محددة. لم يعد ذلك ضروريًا، كما أنه يساهم\nفي حل عدة مشكلات ناتجة عن التأخير بين مرحلة البناء (build) وبدء الاشتراك\nداخليًا.\n\nبدلًا من الاعتماد على التحضير غير المتزامن للوصول إلى حالة معينة، يمكن الآن\nتعيين حالة الـ bloc مباشرة باستخدام `emit` مع الحالة المطلوبة.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\nتُستخدم `emit` لأغراض الاختبار فقط، ولا ينبغي استخدامها خارج نطاق الاختبارات.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗ إعادة تسمية المعامل `bloc` في `BlocBuilder` إلى `cubit`\n\n##### السبب\n\nلجعل `BlocBuilder` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل\nمن `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗ إعادة تسمية المعامل `bloc` في `BlocListener` إلى `cubit`\n\n##### السبب\n\nلجعل `BlocListener` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل\nمن `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗ إعادة تسمية المعامل `bloc` في `BlocConsumer` إلى `cubit`\n\n##### السبب\n\nلجعل `BlocConsumer` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل\nمن `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية).\n\n**v5.x.x**\n\n```\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗ تمت إزالة `initialState`\n\n##### السبب\n\nكمطور، كان الاضطرار إلى تجاوز `initialState` عند إنشاء bloc يسبب مشكلتين\nرئيسيتين:\n\n- يمكن أن تكون `initialState` ديناميكية، كما يمكن الوصول إليها لاحقًا (حتى من\n  خارج الـ bloc نفسه)، مما قد يُعد تسريبًا لبعض تفاصيله الداخلية إلى طبقة واجهة\n  المستخدم.\n- يتطلب ذلك كتابة كود إضافي وغير ضروري (verbose).\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> لمزيد من المعلومات، راجع\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗ إعادة تسمية `BlocDelegate` إلى `BlocObserver`\n\n##### السبب\n\nلم يكن اسم `BlocDelegate` يعكس بدقة الدور الحقيقي لهذه الفئة. إذ يوحي الاسم بأن\nلها دورًا نشطًا، بينما في الواقع كان الهدف منها أن تكون مكونًا سلبيًا يقوم فقط\nبمراقبة جميع الـ blocs داخل التطبيق.\n\n:::note\n\nمن المفترض ألا يحتوي `BlocObserver` على أي منطق أو وظائف موجهة للمستخدم، بل\nيقتصر دوره على المراقبة فقط.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗ تمت إزالة `BlocSupervisor`\n\n##### السبب\n\nكان `BlocSupervisor` مكونًا إضافيًا يتعين على المطورين التعرف عليه والتعامل معه\nفقط من أجل تعيين `BlocDelegate` مخصص. مع الانتقال إلى `BlocObserver`، أصبح من\nالأبسط والأفضل من ناحية تجربة المطور تعيين المراقب مباشرة على الـ bloc نفسه.\n\n?> كما أتاح هذا التغيير فصل بعض إضافات bloc الأخرى مثل `HydratedStorage` عن\n`BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗ إعادة تسمية `condition` في `BlocBuilder` إلى `buildWhen`\n\n##### السبب\n\nعند استخدام `BlocBuilder`، كان بالإمكان سابقًا تحديد `condition` للتحكم في ما\nإذا كان يجب على `builder` إعادة البناء أم لا.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nلم يكن الاسم `condition` واضحًا أو معبّرًا بشكل كافٍ، والأهم من ذلك أنه أدى إلى\nعدم اتساق في واجهة الاستخدام (API) عند التعامل مع `BlocConsumer`، حيث يمكن\nللمطورين تحديد شرطين مختلفين (أحدهما لـ `builder` والآخر لـ `listener`).\n\nونتيجة لذلك، أصبحت واجهة `BlocConsumer` توفر `buildWhen` و `listenWhen`.\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nلتحقيق اتساق أكبر في واجهة الاستخدام (API) وتوفير تجربة تطوير أكثر سلاسة، تمت\nإعادة تسمية `condition` إلى `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗ إعادة تسمية `condition` في `BlocListener` إلى `listenWhen`\n\n##### السبب\n\nلنفس الأسباب المذكورة أعلاه، تم أيضًا إعادة تسمية `condition` في `BlocListener`\nإلى `listenWhen`.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // إرجاع true/false لتحديد ما إذا كان سيتم استدعاء الـ listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗ إعادة تسمية `HydratedStorage` و `HydratedBlocStorage`\n\n##### السبب\n\nلتحسين إعادة استخدام الكود بين\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) و\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit)، تم تغيير اسم تنفيذ\nالتخزين الافتراضي من `HydratedBlocStorage` إلى `HydratedStorage`.\n\nبالإضافة إلى ذلك، تمت إعادة تسمية واجهة `HydratedStorage` إلى `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗ فصل `HydratedStorage` عن `BlocDelegate`\n\n##### السبب\n\nكما ذُكر سابقًا، تمت إعادة تسمية `BlocDelegate` إلى `BlocObserver`، وأصبح يتم\nتعيينه مباشرة ضمن إعدادات الـ bloc كما يلي:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nتم إجراء هذا التغيير من أجل:\n\n- الحفاظ على الاتساق مع واجهة `BlocObserver` الجديدة\n- إبقاء التخزين (`Storage`) محصورًا ضمن `HydratedBloc` فقط\n- فصل `BlocObserver` عن `Storage`\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗ تبسيط عملية التهيئة (Initialization)\n\n##### السبب\n\nسابقًا، كان على المطورين استدعاء `super.initialState ?? DefaultInitialState()`\nيدويًا من أجل تهيئة مثيلات `HydratedBloc`. كان هذا الأسلوب معقدًا ومطولًا، كما\nأنه غير متوافق مع التغييرات الجوهرية على `initialState` في `bloc`.\n\nونتيجة لذلك، أصبحت تهيئة `HydratedBloc` في الإصدار v5.0.0 مماثلة تمامًا لتهيئة\n`Bloc` العادية.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/ar/modeling-state.mdx",
    "content": "---\ntitle: نمذجة الحالة (Modeling State)\ndescription: نظرة عامة على عدة طرق لنمذجة الحالات عند استخدام حزمة bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nهناك العديد من الأساليب المختلفة عندما يتعلق الأمر بهيكلة حالة التطبيق. لكل\nأسلوب مزاياه وعيوبه. في هذا القسم، سنستعرض عدة أساليب، مع إيجابياتها وسلبياتها،\nومتى يُفضّل استخدام كل منها.\n\nالأساليب التالية هي مجرد توصيات وهي اختيارية بالكامل. لا تتردد في استخدام أي\nأسلوب تفضله. قد تلاحظ أن بعض الأمثلة/التوثيقات لا تتبع هذه الأساليب بشكل صارم،\nوذلك لأسباب تتعلق بالتبسيط/الإيجاز.\n\n:::tip\n\nتركز مقتطفات الكود التالية على هيكل الحالة. من الناحية العملية، قد ترغب أيضًا\nفي:\n\n- توسيع `Equatable` من حزمة\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- استخدام التعليق التوضيحي `@Data()` للفئة من حزمة\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- استخدام التعليق التوضيحي `@immutable` للفئة من حزمة\n  [`package:meta`](https://pub.dev/packages/meta)\n- تنفيذ دالة `copyWith`\n- استخدام الكلمة المفتاحية `const` للمُنشئات (constructors)\n\n:::\n\n## الفئة الملموسة وتعداد الحالة (Concrete Class and Status Enum)\n\nيتكون هذا الأسلوب من **فئة ملموسة واحدة** لجميع الحالات، إلى جانب `enum` يمثل\nحالات/Statuses مختلفة. يتم جعل الخصائص قابلة للقيمة الفارغة (nullable) ويتم\nالتعامل معها بناءً على الحالة الحالية. يعمل هذا الأسلوب بشكل أفضل للحالات التي\nليست حصرية بشكل صارم و/أو تحتوي على الكثير من الخصائص المشتركة.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### الإيجابيات\n\n- **بسيط**: سهل إدارة فئة واحدة وتعداد حالة (status enum)، وجميع الخصائص متاحة\n  بسهولة.\n- **موجز**: يتطلب غالبًا عددًا أقل من أسطر الكود مقارنة بالأساليب الأخرى.\n\n#### السلبيات\n\n- **غير آمن من حيث النوع (Not Type Safe)**: يتطلب التحقق من `status` قبل الوصول\n  إلى الخصائص. من الممكن إصدار حالة غير صحيحة (`emit` a malformed state) مما قد\n  يؤدي إلى أخطاء. كما أن خصائص حالات معينة تكون nullable، وقد يكون التعامل معها\n  مرهقًا ويتطلب إما فك تغليف قسري (force unwrapping) أو إجراء فحوصات null. يمكن\n  التخفيف من بعض هذه السلبيات عبر اختبارات الوحدة (unit tests) أو إنشاء مُنشئات\n  (constructors) مخصصة ومُسماة.\n- **متضخم**: ينتج عنه State واحدة قد تصبح متضخمة مع مرور الوقت بسبب كثرة\n  الخصائص.\n\n#### الحكم\n\nيعمل هذا الأسلوب بشكل أفضل للحالات البسيطة أو عندما تتطلب المتطلبات حالات ليست\nحصرية (مثل إظهار Snackbar عند حدوث خطأ مع الاستمرار في عرض البيانات القديمة من\nآخر حالة نجاح). يوفر مرونة وإيجازًا على حساب أمان النوع.\n\n## الفئة المختومة والفئات الفرعية (Sealed Class and Subclasses)\n\nيتكون هذا الأسلوب من **فئة مختومة (sealed class)** تحتوي على الخصائص المشتركة،\nمع عدة فئات فرعية تمثل الحالات المنفصلة. هذا الأسلوب مناسب جدًا للحالات المنفصلة\nوالحصرية.\n\n<SealedClassAndSubclassesSnippet />\n\n#### الإيجابيات\n\n- **آمن من حيث النوع (Type Safe)**: الكود آمن وقت التجميع (compile-safe) ولا\n  يمكن الوصول بالخطأ إلى خاصية غير صالحة. كل فئة فرعية تحتوي خصائصها الخاصة، مما\n  يجعل من الواضح ما الذي ينتمي لأي حالة.\n- **صريح (Explicit)**: يفصل الخصائص المشتركة عن الخصائص الخاصة بكل حالة.\n- **شامل (Exhaustive)**: يمكنك استخدام `switch` لفحوصات الشمول (exhaustiveness\n  checks) لضمان التعامل مع كل حالة بشكل صريح.\n  - إذا كنت لا تريد\n    [التبديل الشامل](https://dart.dev/language/branches#exhaustiveness-checking)\n    أو تريد إضافة أنواع فرعية لاحقًا دون كسر واجهة الـ API، فاستخدم\n    [final](https://dart.dev/language/class-modifiers#final).\n  - راجع [توثيق sealed class](https://dart.dev/language/class-modifiers#sealed)\n    لمزيد من التفاصيل.\n\n#### السلبيات\n\n- **مطوّل (Verbose)**: يتطلب كودًا أكثر (فئة أساسية + فئة فرعية لكل حالة). وقد\n  يتطلب تكرارًا للخصائص المشتركة عبر الفئات الفرعية.\n- **أكثر تعقيدًا (Complex)**: إضافة خصائص جديدة قد تتطلب تحديث الفئة الأساسية\n  وجميع الفئات الفرعية، مما يزيد التعقيد. وقد يؤدي أيضًا إلى فحوصات نوع إضافية\n  للوصول إلى خصائص معينة.\n\n#### الحكم\n\nيعمل هذا الأسلوب بشكل أفضل للحالات المحددة جيدًا والحصرية ذات الخصائص الفريدة.\nيوفر أمان النوع وفحوصات الشمول، ويؤكد على السلامة أكثر من الإيجاز والبساطة.\n"
  },
  {
    "path": "docs/src/content/docs/ar/naming-conventions.mdx",
    "content": "---\ntitle: اصطلاحات التسمية\ndescription: نظرة عامة على اصطلاحات التسمية الموصى بها عند استخدام bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nاصطلاحات التسمية التالية هي مجرد توصيات وهي اختيارية بالكامل. لا تتردد في\nاستخدام أي أسلوب تسمية تفضله. قد تلاحظ أن بعض الأمثلة أو أجزاء التوثيق لا تلتزم\nبهذه الاصطلاحات، وذلك غالبًا من أجل البساطة أو الإيجاز. ومع ذلك، يُوصى بشدة بهذه\nالاصطلاحات في المشاريع الكبيرة التي يعمل عليها عدة مطورين.\n\n## اصطلاحات الأحداث (Event Conventions)\n\nيجب أن تُسمّى الأحداث بصيغة **الماضي**، لأن الحدث من منظور الـ bloc هو شيء حدث\nبالفعل.\n\n### البنية (Anatomy)\n\n`BlocSubject` + `Noun (اختياري)` + `Verb (الحدث)`\n\nيجب أن تتبع أحداث التحميل الأولي الاصطلاح التالي: `BlocSubject` + `Started`\n\n:::note\n\nيجب أن يكون اسم فئة الحدث الأساسية (base event class) بالشكل التالي:\n`BlocSubject` + `Event`.\n\n:::\n\n### أمثلة\n\n✅ **جيد**\n\n<EventExamplesGood1 />\n\n❌ **سيئ**\n\n<EventExamplesBad1 />\n\n## اصطلاحات الحالات (State Conventions)\n\nيجب أن تكون الحالات أسماء (Nouns)، لأن الحالة تمثل مجرد لقطة (snapshot) في نقطة\nزمنية محددة. هناك طريقتان شائعتان لتمثيل الحالة: باستخدام فئات فرعية\n(subclasses) أو باستخدام فئة واحدة (single class).\n\n### البنية (Anatomy)\n\n#### الفئات الفرعية (Subclasses)\n\n`BlocSubject` + `Verb (الإجراء)` + `State`\n\nعند تمثيل الحالة باستخدام فئات فرعية متعددة، يجب أن تكون `State` واحدة من القيم\nالتالية:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nيجب أن تتبع الحالات الأولية الاصطلاح التالي: `BlocSubject` + `Initial`.\n\n:::\n\n#### الفئة الواحدة (Single Class)\n\n`BlocSubject` + `State`\n\nعند تمثيل الحالة كفئة أساسية واحدة، يجب استخدام تعداد (enum) باسم:\n`BlocSubject` + `Status` لتمثيل حالة الـ State:\n\n`initial` | `success` | `failure` | `loading`\n\n:::note\n\nيجب أن يكون اسم فئة الحالة الأساسية (base state class) دائمًا: `BlocSubject` +\n`State`.\n\n:::\n\n### أمثلة\n\n✅ **جيد**\n\n##### الفئات الفرعية (Subclasses)\n\n<StateExamplesGood1Snippet />\n\n##### الفئة الواحدة (Single Class)\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **سيئ**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/ar/testing.mdx",
    "content": "---\ntitle: الاختبار (Testing)\ndescription: أساسيات كيفية كتابة اختبارات للـ blocs الخاصة بك.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nتم تصميم Bloc ليكون سهل الاختبار إلى حدٍ كبير. في هذا القسم، سنستعرض كيفية كتابة\nاختبارات وحدة (unit tests) للـ bloc.\n\nلأغراض التبسيط، سنقوم بكتابة اختبارات لـ `CounterBloc` الذي أنشأناه في\n[المفاهيم الأساسية](/ar/bloc-concepts).\n\nللتذكير، فإن تنفيذ `CounterBloc` يبدو كالتالي:\n\n<CounterBlocSnippet />\n\n## الإعداد (Setup)\n\nقبل البدء في كتابة الاختبارات، نحتاج إلى إضافة إطار عمل للاختبار إلى تبعيات\nالمشروع.\n\nنحتاج إلى إضافة حزمتَي [test](https://pub.dev/packages/test) و\n[bloc_test](https://pub.dev/packages/bloc_test) إلى مشروعنا.\n\n<AddDevDependenciesSnippet />\n\n## الاختبار (Testing)\n\nلنبدأ بإنشاء ملف لاختبارات `CounterBloc` باسم `counter_bloc_test.dart` واستيراد\nحزمة الاختبار.\n\n<CounterBlocTestImportsSnippet />\n\nبعد ذلك، نحتاج إلى إنشاء دالة `main` بالإضافة إلى مجموعة اختبارات (test group).\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nتُستخدم المجموعات (Groups) لتنظيم الاختبارات الفردية، وكذلك لإنشاء سياق يمكن من\nخلاله مشاركة دوال `setUp` و `tearDown` بين جميع الاختبارات داخل المجموعة.\n\n:::\n\nلنبدأ بإنشاء مثيل من `CounterBloc` سيتم استخدامه عبر جميع اختباراتنا.\n\n<CounterBlocTestSetupSnippet />\n\nالآن يمكننا البدء في كتابة اختباراتنا الفردية.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nيمكن تشغيل جميع الاختبارات باستخدام الأمر `dart test`.\n\n:::\n\nفي هذه المرحلة، يجب أن يكون لدينا أول اختبار ناجح! الآن دعونا نكتب اختبارًا أكثر\nتقدمًا باستخدام حزمة [bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nيجب أن نتمكن من تشغيل الاختبارات والتأكد من أن جميعها ناجحة.\n\nبهذا نكون قد انتهينا. ينبغي أن يكون الاختبار أمرًا سهلًا، وأن نشعر بالثقة عند\nإجراء التعديلات أو إعادة هيكلة الكود.\n\nيمكنك الرجوع إلى\n[تطبيق Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nللاطلاع على مثال لتطبيق مختبَر بالكامل.\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Flutter Counter\ndescription:\n  دليل متعمّق لبناء تطبيق عدّاد (Counter) في Flutter باستخدام مكتبة Bloc.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nفي هذا الدليل التعليمي، سنبني تطبيق **عداد (Counter)** في Flutter باستخدام مكتبة\n**Bloc**.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## المواضيع الرئيسية (Key Topics)\n\n- مراقبة تغييرات الحالة باستخدام\n  [`BlocObserver`](/ar/bloc-concepts#blocobserver).\n- [`BlocProvider`](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في\n  Flutter توفّر Bloc للأبناء.\n- [`BlocBuilder`](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter\n  تتولّى إعادة البناء استجابةً للحالات الجديدة.\n- استخدام Cubit بدلاً من Bloc.\n  [ما هو الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc)\n- إضافة الأحداث باستخدام\n  [`context.read`](/ar/flutter-bloc-concepts#contextread).\n\n## الإعداد (Setup)\n\nسنبدأ بإنشاء مشروع Flutter جديد بالكامل:\n\n<FlutterCreateSnippet />\n\nبعد ذلك، يمكننا استبدال محتويات ملف `pubspec.yaml` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nثم نثبّت جميع التبعيات (dependencies):\n\n<FlutterPubGetSnippet />\n\n## هيكل المشروع (Project Structure)\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nيستخدم التطبيق هيكل مجلدات يعتمد على الميزات (feature-driven directory\nstructure). هذا النمط يساعدنا على توسيع المشروع عبر ميزات مستقلة بذاتها. في هذا\nالمثال لدينا ميزة واحدة فقط (العداد نفسه)، لكن في التطبيقات الأكثر تعقيدًا قد\nنمتلك مئات الميزات المختلفة.\n\n## BlocObserver\n\nأول ما سنراجعه هو إنشاء `BlocObserver` الذي يساعدنا على مراقبة جميع تغييرات\nالحالة في التطبيق.\n\nلننشئ الملف `lib/counter_observer.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\nفي هذه الحالة، نقوم فقط بعمل override للدالة `onChange` لمتابعة جميع تغييرات\nالحالة.\n\n:::note\n\nتعمل الدالة `onChange` بالطريقة نفسها في instances من `Bloc` و`Cubit`.\n\n:::\n\n## main.dart\n\nبعد ذلك، استبدل محتويات الملف `lib/main.dart` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nهنا نهيّئ `CounterObserver` الذي أنشأناه للتو، ثم نستدعي `runApp` باستخدام\nWidget `CounterApp` التي سنراجعها الآن.\n\n## تطبيق العداد (Counter App)\n\nلننشئ الملف `lib/app.dart`:\n\n`CounterApp` هو `MaterialApp` ويحدد `home` على أنه `CounterPage`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nنقوم بعمل extend لـ `MaterialApp` لأن `CounterApp` **هو** `MaterialApp`. في أغلب\nالحالات سننشئ instances من `StatelessWidget` أو `StatefulWidget` ونركّب Widgets\nداخل `build`، لكن في هذا المثال لا توجد Widgets تحتاج إلى تركيب، لذا فإن extend\nالمباشر لـ `MaterialApp` أبسط.\n\n:::\n\nلننتقل الآن إلى `CounterPage`.\n\n## صفحة العداد (Counter Page)\n\nلننشئ الملف `lib/counter/view/counter_page.dart`:\n\nتتولى Widget `CounterPage` إنشاء `CounterCubit` (الذي سنراجعه بعد قليل) وتوفيره\nإلى `CounterView`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\nمن المهم فصل إنشاء `Cubit` عن استهلاكه للحصول على أكواد برمجية أكثر قابلية\nللاختبار وإعادة الاستخدام.\n\n:::\n\n## Counter Cubit\n\nلننشئ الملف `lib/counter/cubit/counter_cubit.dart`:\n\nتعرض class `CounterCubit` طريقتين (methods):\n\n- `increment`: تضيف 1 إلى الحالة الحالية\n- `decrement`: تطرح 1 من الحالة الحالية\n\nنوع الحالة الذي يديره `CounterCubit` هو `int` فقط، والحالة الأولية هي `0`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\nاستخدم\n[إضافة VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nأو [Plugin لـ IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) لإنشاء\nCubits جديدة تلقائيًا.\n\n:::\n\nبعد ذلك، لنراجع `CounterView`، وهي المسؤولة عن استهلاك الحالة والتفاعل مع\n`CounterCubit`.\n\n## عرض العداد (Counter View)\n\nلننشئ الملف `lib/counter/view/counter_view.dart`:\n\n`CounterView` مسؤولة عن عرض قيمة العداد الحالية، وإظهار زري\n`FloatingActionButton` لزيادة/إنقاص العداد.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\nنستخدم `BlocBuilder` لتغليف Widget `Text` بهدف تحديث النص كلما تغيّرت حالة\n`CounterCubit`. بالإضافة إلى ذلك، نستخدم `context.read<CounterCubit>()` للعثور\nعلى أقرب instance من `CounterCubit`.\n\n:::note\n\nتم تغليف Widget `Text` فقط داخل `BlocBuilder` لأنها الوحيدة التي تحتاج إعادة\nبناء عند تغيّر حالة `CounterCubit`. تجنّب تغليف Widgets لا تحتاج لإعادة البناء\nعند تغيّر الحالة.\n\n:::\n\n## Barrel (تجميع الصادرات)\n\nلننشئ الملف `lib/counter/view/view.dart`:\n\nأضف `view.dart` لتصدير جميع الأجزاء العامة (public) الخاصة بعرض العداد.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\nلننشئ الملف `lib/counter/counter.dart`:\n\nأضف `counter.dart` لتصدير جميع الأجزاء العامة (public) الخاصة بميزة العداد.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\nانتهينا! قمنا بفصل طبقة العرض (presentation layer) عن طبقة منطق الأعمال\n(business logic layer). لا تعرف `CounterView` ماذا يحدث عند ضغط المستخدم على\nالزر؛ هي فقط تُخطر `CounterCubit`. وفي المقابل، لا يعرف `CounterCubit` شيئًا عن\nطريقة عرض الحالة (قيمة العداد)، بل يصدر حالات جديدة استجابةً لاستدعاء methods.\n\nيمكننا تشغيل التطبيق بالأمر `flutter run` وعرضه على الجهاز أو\nsimulator/emulator.\n\nيمكن العثور على المصدر الكامل (بما في ذلك اختبارات الوحدة واختبارات Widgets)\nلهذا المثال\n[هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: تسجيل الدخول باستخدام Flutter وFirebase\ndescription:\n  دليل متعمق لبناء تدفق تسجيل دخول (Login Flow) في Flutter باستخدام Bloc\n  وFirebase.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nفي هذا الدليل، سنبني تدفق تسجيل دخول (Firebase Login Flow) في Flutter باستخدام\nمكتبة Bloc.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## المواضيع الرئيسية\n\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهو `widget` من\n  Flutter يوفّر Bloc للأبناء.\n- استخدام Cubit بدلًا من Bloc. [ما الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc)\n- إضافة الأحداث (events) باستخدام\n  [context.read](/ar/flutter-bloc-concepts#contextread).\n- تجنب إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهو\n  `widget` من Flutter يوفّر Repository للأبناء.\n- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهو `widget` من\n  Flutter يستدعي منطق `listener` عند تغيّر الحالة (state) في Bloc.\n- قراءة جزء محدد من الحالة باستخدام\n  [context.select](/ar/flutter-bloc-concepts#contextselect).\n\n## الإعداد\n\nسنبدأ بإنشاء مشروع Flutter جديد تمامًا.\n\n<FlutterCreateSnippet />\n\nوكما فعلنا في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، سننشئ حزمًا\nداخلية لتقسيم معمارية التطبيق (application architecture) إلى طبقات أوضح، مع\nالحفاظ على حدود واضحة بين الطبقات، وتحسين قابلية إعادة الاستخدام (reusability)\nوالاختبار (testability).\n\nفي هذا المثال، ستكون حزمتا\n[firebase_auth](https://pub.dev/packages/firebase_auth) و\n[google_sign_in](https://pub.dev/packages/google_sign_in) هما طبقة البيانات\n(data layer). لذلك سننشئ `AuthenticationRepository` فقط لدمج البيانات من عميلَي\nواجهات البرمجة (API clients).\n\n## مستودع المصادقة (Authentication Repository)\n\nسيكون `AuthenticationRepository` مسؤولًا عن إخفاء تفاصيل التنفيذ الداخلية\n(implementation details) لطريقة مصادقة المستخدم وجلب بياناته. في هذا المثال\nسيتكامل مع Firebase، لكن يمكننا لاحقًا تغيير التنفيذ الداخلي دون التأثير على\nباقي التطبيق.\n\n### الإعداد\n\nسنبدأ بإنشاء `packages/authentication_repository` وملف `pubspec.yaml` في جذر\nالمشروع.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\nبعد ذلك، يمكننا تثبيت dependencies عبر تشغيل:\n\n<FlutterPubGetSnippet />\n\nداخل مجلد `authentication_repository`.\n\nمثل أغلب الحزم، يعرّف `authentication_repository` واجهته العامة (API surface)\nعبر الملف\n`packages/authentication_repository/lib/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\nحزمة `authentication_repository` توفّر `AuthenticationRepository` بالإضافة إلى\nmodels.\n\n:::\n\nالخطوة التالية هي مراجعة models.\n\n### المستخدم (User)\n\nنموذج `User` يصف المستخدم ضمن نطاق المصادقة (authentication domain). وفي هذا\nالمثال، يتكوّن المستخدم من `email` و`id` و`name` و`photo`.\n\n:::note\n\nشكل `User` النهائي يعتمد بالكامل على متطلبات نطاق عملك (domain).\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\nالصنف `User` يرث من [equatable](https://pub.dev/packages/equatable) لتجاوز\nمقارنات التساوي (equality comparisons)، حتى نتمكن من مقارنة كائنات `User`\nالمختلفة بالقيمة (by value).\n\n:::\n\n:::tip\n\nمن المفيد تعريف `User.empty` بشكل `static` لتجنب التعامل مع `null User`،\nوالتعامل دائمًا مع كائن `User` فعلي.\n\n:::\n\n### المستودع (Repository)\n\n`AuthenticationRepository` مسؤول عن تجريد (abstraction) تنفيذ المصادقة الفعلي\nوكذلك آلية جلب بيانات المستخدم.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nيوفّر `AuthenticationRepository` تدفقًا `Stream<User>` يمكننا الاشتراك فيه\nللحصول على إشعار عند تغيّر `User`. كما يوفّر methods مثل `signUp` و\n`logInWithGoogle` و`logInWithEmailAndPassword` و`logOut`.\n\n:::note\n\n`AuthenticationRepository` مسؤول أيضًا عن التعامل مع أخطاء طبقة البيانات (data\nlayer) منخفضة المستوى، ويقدّم مجموعة أخطاء أبسط وأنظف ومتوافقة مع النطاق\n(domain).\n\n:::\n\nهذا كل ما نحتاجه في `AuthenticationRepository`. الخطوة التالية هي دمجه في مشروع\nFlutter الذي أنشأناه.\n\n## إعداد Firebase\n\nنحتاج إلى اتباع\n[تعليمات استخدام firebase_auth](https://pub.dev/packages/firebase_auth#usage)\nلتوصيل التطبيق مع Firebase وتفعيل\n[google_sign_in](https://pub.dev/packages/google_sign_in).\n\n:::caution\n\nتأكد من تحديث `google-services.json` على Android، و`GoogleService-Info.plist`\nو`Info.plist` على iOS، وإلا سيتعطل التطبيق.\n\n:::\n\n## تبعيات المشروع (Project Dependencies)\n\nيمكننا استبدال ملف `pubspec.yaml` المُولّد في جذر المشروع بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nلاحظ أننا نحدد مجلد `assets` لكل الأصول المحلية (local assets) الخاصة بالتطبيق.\nأنشئ مجلد `assets` في جذر المشروع، ثم أضف أصل شعار bloc\n[bloc logo](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\n(سنستخدمه لاحقًا).\n\nبعد ذلك ثبّت جميع dependencies:\n\n<FlutterPubGetSnippet />\n\n:::note\n\nنحن نستخدم حزمة `authentication_repository` عبر `path`، وهذا يتيح لنا التطوير\nبسرعة مع الحفاظ على فصل واضح بين الطبقات.\n\n:::\n\n## main.dart\n\nيمكن استبدال ملف `main.dart` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nالملف يضبط إعدادات عامة للتطبيق، ثم يستدعي `runApp` مع نسخة من `App`.\n\n:::note\n\nنحقن (inject) نسخة واحدة من `AuthenticationRepository` داخل `App` كاعتمادية\nصريحة في المُنشئ (constructor dependency).\n\n:::\n\n## التطبيق (App)\n\nكما في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، يوفّر `app.dart` نسخة\n`AuthenticationRepository` للتطبيق عبر `RepositoryProvider`، كما ينشئ نسخة من\n`AuthenticationBloc` ويجعلها متاحة. بعد ذلك، تتعامل `AppView` مع\n`AuthenticationBloc` وتحدّث المسار الحالي (current route) بناءً على\n`AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\n`AppBloc` مسؤول عن إدارة الحالة العامة (global state) للتطبيق. وهو يعتمد على\n`AuthenticationRepository`، ويشترك في تدفق `user` لإصدار حالات جديدة عند تغيّر\nالمستخدم الحالي.\n\n### الحالة (State)\n\nتتكوّن `AppState` من `AppStatus` و`User`. يقبل المُنشئ الافتراضي `User`\nاختياريًا، ثم يعيد التوجيه إلى المُنشئ الخاص مع حالة المصادقة المناسبة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### الحدث (Event)\n\nيحتوي `AppEvent` على فئتين فرعيتين (subclasses):\n\n- `AppUserSubscriptionRequested` لإعلام الـ Bloc ببدء الاشتراك في تدفق المستخدم.\n- `AppLogoutPressed` لإعلام الـ Bloc بأن المستخدم طلب تسجيل الخروج.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\nداخل المُنشئ (constructor body)، يتم ربط الفئات الفرعية من `AppEvent` مع معالجات\nالأحداث (event handlers) المقابلة لها.\n\nداخل معالج `_onUserSubscriptionRequested`، يستخدم `AppBloc` الدالة `emit.onEach`\nللاشتراك في تدفق المستخدم الخاص بـ `AuthenticationRepository`، ثم إصدار حالة لكل\n`User` جديد.\n\n`emit.onEach` تنشئ اشتراكًا داخليًا في التدفق (stream subscription)، وتتكفل\nبإلغائه عند إغلاق `AppBloc` أو إغلاق تدفق المستخدم.\n\nإذا أصدر تدفق المستخدم خطأً، تقوم `addError` بتمرير الخطأ و`stack trace` إلى أي\n`BlocObserver` يستمع.\n\n:::caution\n\nإذا لم يتم تمرير `onError`، فسيتم اعتبار أي خطأ في تدفق المستخدم غير مُعالَج،\nوسيتم رميه من `onEach`. ونتيجة لذلك سيتم إلغاء الاشتراك في تدفق المستخدم.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/ar/bloc-concepts/#blocobserver) مفيد جدًا لتسجيل أحداث Bloc\nوالأخطاء وتغيّرات الحالة، خصوصًا في سياق التحليلات (analytics) وتقارير الأعطال\n(crash reporting).\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## النماذج (Models)\n\nنماذج إدخال `Email` و`Password` مفيدة لتغليف منطق التحقق (validation logic)،\nوسيتم استخدامها في `LoginForm` و`SignUpForm` (لاحقًا في هذا الدليل).\n\nكلا النموذجين مبنيّ باستخدام حزمة [formz](https://pub.dev/packages/formz)، وهذا\nيسمح لنا بالتعامل مع كائن مُتحقَّق منه (validated object) بدلًا من نوع بدائي\n(primitive type) مثل `String`.\n\n### البريد الإلكتروني (Email)\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### كلمة المرور (Password)\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## صفحة تسجيل الدخول (Login Page)\n\n`LoginPage` مسؤولة عن إنشاء نسخة من `LoginCubit` وتوفيرها إلى `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nمن المهم جدًا فصل مكان إنشاء blocs/cubits عن مكان استهلاكها. هذا يجعل حقن نسخ\nوهمية (mock instances) أسهل، ويسهّل اختبار الواجهة (view) بشكل مستقل (in\nisolation).\n\n:::\n\n## Login Cubit\n\n`LoginCubit` مسؤول عن إدارة `LoginState` الخاص بالنموذج. وهو يوفّر APIs مثل\n`logInWithCredentials` و`logInWithGoogle`، كما يتلقى إشعارات عند تحديث البريد\nالإلكتروني/كلمة المرور.\n\n### الحالة (State)\n\nتتكوّن `LoginState` من `Email` و`Password` و`FormzStatus`. ويرث نموذجا `Email`\nو`Password` من `FormzInput` في حزمة [formz](https://pub.dev/packages/formz).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\nيعتمد `LoginCubit` على `AuthenticationRepository` لتسجيل دخول المستخدم إما عبر\nبيانات الاعتماد (credentials) أو عبر Google Sign-In.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\nاستخدمنا `Cubit` بدلًا من `Bloc` هنا لأن `LoginState` بسيط ومحصور في نطاق صغير.\nحتى بدون events، يمكن فهم ما حدث من خلال الانتقال بين الحالات، مع كود أبسط وأكثر\nاختصارًا.\n\n:::\n\n## نموذج تسجيل الدخول (Login Form)\n\n`LoginForm` مسؤول عن عرض النموذج بناءً على `LoginState`، ويستدعي methods على\n`LoginCubit` استجابةً لتفاعلات المستخدم.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\nكما يعرض `LoginForm` زر \"إنشاء حساب\" (Create Account) الذي ينقل المستخدم إلى\n`SignUpPage` لإنشاء حساب جديد.\n\n## صفحة إنشاء الحساب (Sign Up Page)\n\nبنية `SignUp` تعكس بنية `Login`، وتتكوّن من `SignUpPage` و`SignUpView` و\n`SignUpCubit`.\n\n`SignUpPage` مسؤولة فقط عن إنشاء نسخة من `SignUpCubit` وتوفيرها إلى `SignUpForm`\n(تمامًا كما في `LoginPage`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\nكما في `LoginCubit`، يعتمد `SignUpCubit` على `AuthenticationRepository` لإنشاء\nحسابات مستخدمين جديدة.\n\n:::\n\n## Sign Up Cubit\n\nيدير `SignUpCubit` حالة `SignUpForm`، ويتواصل مع `AuthenticationRepository`\nلإنشاء حسابات جديدة.\n\n### الحالة (State)\n\nتُعيد `SignUpState` استخدام نموذجي إدخال `Email` و`Password` لأن منطق التحقق\n(validation logic) هو نفسه.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\n`SignUpCubit` قريب جدًا من `LoginCubit`، والفرق الأساسي أنه يوفّر API لإرسال\nالنموذج (submit) بدلًا من تسجيل الدخول.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## نموذج إنشاء الحساب (Sign Up Form)\n\n`SignUpForm` مسؤول عن عرض النموذج بناءً على `SignUpState`، ويستدعي methods على\n`SignUpCubit` استجابةً لتفاعلات المستخدم.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## الصفحة الرئيسية (Home Page)\n\nبعد أن يسجل المستخدم الدخول أو ينشئ حسابًا بنجاح، يتم تحديث تدفق `user`، ما يؤدي\nإلى تغيّر الحالة في `AuthenticationBloc`، ثم تدفع `AppView` صفحة `HomePage` إلى\nمكدس التنقل (navigation stack).\n\nمن `HomePage` يمكن للمستخدم عرض معلومات ملفه الشخصي وتسجيل الخروج عبر النقر على\nأيقونة الخروج في `AppBar`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\nتم إنشاء مجلد `widgets` بجانب مجلد `view` داخل ميزة `home` للمكونات القابلة\nلإعادة الاستخدام الخاصة بهذه الميزة. في هذا المثال، يتم تصدير `Avatar` بسيط\nواستخدامه داخل `HomePage`.\n\n:::\n\n:::note\n\nعند النقر على `IconButton` الخاص بتسجيل الخروج، يُضاف الحدث\n`AuthenticationLogoutRequested` إلى `AuthenticationBloc`، فيتم تسجيل خروج\nالمستخدم وإعادته إلى `LoginPage`.\n\n:::\n\nبهذه المرحلة أصبح لدينا تنفيذ قوي لتسجيل الدخول باستخدام Firebase، وتمكّنا من\nفصل طبقة العرض (presentation layer) عن طبقة منطق الأعمال (business logic layer)\nباستخدام مكتبة Bloc.\n\nيمكنك الاطلاع على المصدر الكامل لهذا المثال من\n[هنا](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: قائمة Flutter اللانهائية (Infinite List)\ndescription: دليل متعمّق لبناء قائمة Flutter لانهائية باستخدام مكتبة Bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nفي هذا الدليل، سنبني تطبيقًا يجلب البيانات عبر الشبكة ويحمّلها تدريجيًا أثناء\nتمرير المستخدم، باستخدام Flutter ومكتبة Bloc.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## المواضيع الرئيسية\n\n- مراقبة تغييرات الحالة باستخدام [BlocObserver](/ar/bloc-concepts#blocobserver).\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter\n  توفّر Bloc للأبناء.\n- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter\n  تتولّى إعادة البناء استجابةً للحالات الجديدة.\n- إضافة الأحداث باستخدام [context.read](/ar/flutter-bloc-concepts#contextread).\n- منع إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- استخدام `transformEvents` مع Rx.\n\n## الإعداد\n\nسنبدأ بإنشاء مشروع Flutter جديد بالكامل.\n\n<FlutterCreateSnippet />\n\nبعد ذلك، يمكننا استبدال محتويات `pubspec.yaml` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nثم نثبّت جميع التبعيات (dependencies):\n\n<FlutterPubGetSnippet />\n\n## هيكل المشروع\n\n```\n├── lib\n|   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n|   |   |   └── post_event.dart\n|   |   |   └── post_state.dart\n|   |   └── models\n|   |   |   └── models.dart*\n|   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n|   |   |   └── view.dart*\n|   |   └── widgets\n|   |   |   └── bottom_loader.dart\n|   |   |   └── post_list_item.dart\n|   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nيستخدم التطبيق هيكل مجلدات يعتمد على الميزات (feature-driven directory\nstructure). يتيح لنا هذا النمط توسيع المشروع عبر ميزات مستقلة بذاتها. في هذا\nالمثال، سيكون لدينا ميزة واحدة فقط (ميزة المنشورات `post feature`) وهي مقسمة إلى\nمجلدات خاصة بها مع ملفات تجميع (barrel files)، المشار إليها بعلامة النجمة (\\*).\n\n## واجهة برمجة تطبيقات REST\n\nفي هذا التطبيق التجريبي، سنستخدم\n[jsonplaceholder](http://jsonplaceholder.typicode.com) كمصدر للبيانات.\n\n:::note\n\n`jsonplaceholder` هي REST API عبر الإنترنت تقدم بيانات وهمية؛ وهي مفيدة جدًا\nلبناء النماذج الأولية.\n\n:::\n\nافتح علامة تبويب جديدة في المتصفح وزر الرابط التالي:\n`https://jsonplaceholder.typicode.com/posts?_start=0&_limit=2` لترى ما ترجعه\nواجهة برمجة التطبيقات.\n\n<PostsJsonSnippet />\n\n:::note\n\nفي URL هذا، حددنا `start` و`limit` كمعاملات استعلام (query parameters) في طلب\nGET.\n\n:::\n\nممتاز، بعد أن عرفنا شكل البيانات، لننشئ الـ model.\n\n## نموذج البيانات (Data Model)\n\nأنشئ `post.dart` ولنبدأ ببناء model كائن المنشور (`Post`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post` عبارة عن class بسيط يحتوي على `id` و`title` و`body`.\n\n:::note\n\nنحن نرث من [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة\nكائنات `Posts`. بدون ذلك سنحتاج لتعديل الـ class يدويًا وعمل override لـ\n`equality` و`hashCode` حتى نميّز بين كائنين مختلفين من `Posts`. راجع\n[الحزمة](https://pub.dev/packages/equatable) لمزيد من التفاصيل.\n\n:::\n\nالآن بعد أن أصبح لدينا model كائن `Post`، لنبدأ العمل على مكوّن منطق الأعمال\n(Business Logic Component) أي `bloc`.\n\n## أحداث المنشور (Post Events)\n\nقبل التعمّق في التنفيذ، نحتاج إلى تحديد ما الذي سيفعله `PostBloc`.\n\nعلى مستوى عام، سيستجيب لإدخال المستخدم (التمرير) ويجلب المزيد من المنشورات حتى\nتتمكن طبقة العرض من عرضها. لنبدأ بإنشاء `Event`.\n\nسيستجيب `PostBloc` لحدث واحد فقط هو `PostFetched`، والذي تضيفه طبقة العرض كلما\nاحتاجت إلى مزيد من المنشورات. وبما أن `PostFetched` نوع من `PostEvent`، يمكننا\nإنشاء `bloc/post_event.dart` وتنفيذه كالتالي.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\nباختصار، سيتلقى `PostBloc` أحداث `PostEvents` ويحوّلها إلى حالات `PostStates`.\nبعد تعريف `PostEvents` (`PostFetched`)، ننتقل لتعريف `PostState`.\n\n## حالات المنشور (Post States)\n\nتحتاج طبقة العرض إلى عدة معلومات لتتمكن من البناء بشكل صحيح:\n\n- `PostInitial`: ستخبر طبقة العرض بأنها بحاجة إلى عرض مؤشر تحميل أثناء تحميل\n  الدفعة الأولية من المنشورات.\n- `PostSuccess`: تخبر طبقة العرض أن لديها محتوى جاهزًا للعرض.\n  - `posts`: ستكون قائمة (`List<Post>`) سيتم عرضها.\n  - `hasReachedMax`: ستخبر طبقة العرض بما إذا كانت قد وصلت إلى الحد الأقصى لعدد\n    المنشورات أم لا.\n- `PostFailure`: ستخبر طبقة العرض بحدوث خطأ أثناء جلب المنشورات.\n\nيمكننا الآن إنشاء `bloc/post_state.dart` وتنفيذه بالشكل التالي.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\nقمنا بتنفيذ `copyWith` حتى نتمكن من نسخ instance من `PostSuccess` وتحديث صفر أو\nأكثر من الخصائص بسهولة (وسيكون ذلك مفيدًا لاحقًا).\n\n:::\n\nالآن بعد تنفيذ `Events` و`States`، يمكننا إنشاء `PostBloc`.\n\n## Post Bloc\n\nللتبسيط، سيعتمد `PostBloc` مباشرة على `http client`. لكن في تطبيق إنتاجي يُفضل\nحقن API client واستخدام نمط المستودع (repository pattern). راجع\n[الوثائق](/ar/architecture).\n\nلننشئ `post_bloc.dart` ونبدأ بـ `PostBloc` فارغ.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\nمن class declaration فقط، يمكننا معرفة أن `PostBloc` يستقبل `PostEvents` كمدخلات\nويخرج `PostStates`.\n\n:::\n\nبعد ذلك، نحتاج لتسجيل event handler لمعالجة أحداث `PostFetched` الواردة.\nاستجابةً لهذا الحدث، سنستدعي `_fetchPosts` لجلب المنشورات من API.\n\n<PostBlocOnPostFetchedSnippet />\n\nسيقوم `PostBloc` بإصدار (`emit`) حالات جديدة عبر `Emitter<PostState>` الممرر إلى\nevent handler. راجع [المفاهيم الأساسية](/ar/bloc-concepts/#التدفقات-streams)\nلمزيد من المعلومات.\n\nالآن، في كل مرة يُضاف فيها `PostEvent` وكان الحدث هو `PostFetched` وما زالت هناك\nمنشورات متاحة، سيقوم `PostBloc` بجلب 20 منشورًا إضافيًا.\n\nستعيد API مصفوفة فارغة إذا حاولنا الجلب بعد الحد الأقصى (100 منشور). لذلك إذا\nحصلنا على مصفوفة فارغة، فسيقوم bloc بإصدار (`emit`) الحالة الحالية مع تعيين\n`hasReachedMax` إلى `true`.\n\nإذا لم نتمكن من استرداد المنشورات، فإننا نصدر `PostStatus.failure`.\n\nإذا تمكنا من استرداد المنشورات، فإننا نصدر `PostStatus.success` والقائمة الكاملة\nللمنشورات.\n\nأحد التحسينات الممكنة هو عمل **throttle** لحدث `PostFetched` لتجنب إرسال طلبات\nغير ضرورية إلى API. ويمكن تنفيذ ذلك باستخدام معامل `transform` عند تسجيل معالج\nالحدث `_onFetched`.\n\n:::note\n\nيسمح لنا تمرير `transformer` إلى `on<PostFetched>` بتخصيص كيفية معالجة الأحداث.\n\n:::\n\n:::note\n\nتأكد من استيراد\n[`package:stream_transform`](https://pub.dev/packages/stream_transform) لاستخدام\nAPI الخاصة بـ `throttle`.\n\n:::\n\n<PostBlocTransformerSnippet />\n\nيجب أن يبدو `PostBloc` النهائي لدينا الآن كما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\nممتاز! بعد الانتهاء من منطق الأعمال، المتبقي هو تنفيذ طبقة العرض.\n\n## طبقة العرض (Presentation Layer)\n\nفي `main.dart`، نبدأ بتنفيذ الدالة الرئيسية واستدعاء `runApp` لعرض Widget الجذر.\nوهنا يمكننا أيضًا تضمين `bloc observer` لتسجيل الانتقالات وأي أخطاء.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nفي ويدجت `App`، وهو جذر مشروعنا، يمكننا بعد ذلك تعيين الصفحة الرئيسية إلى\n`PostsPage`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nفي Widget `PostsPage`، نستخدم `BlocProvider` لإنشاء وتوفير instance من\n`PostBloc` للشجرة الفرعية. كما نضيف حدث `PostFetched` بحيث يطلب التطبيق الدفعة\nالأولية من المنشورات عند التحميل.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\nبعد ذلك، نحتاج إلى تنفيذ `PostsList` التي ستعرض المنشورات وتتصل بـ `PostBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList` هي `StatefulWidget` لأنها تحتاج للاحتفاظ بـ `ScrollController`. في\n`initState`، نضيف listener إلى `ScrollController` حتى نستجيب لأحداث التمرير. كما\nنصل إلى instance من `PostBloc` عبر `context.read<PostBloc>()`.\n\n:::\n\nفي خطوة البناء، تعيد `build method` لدينا `BlocBuilder`. و`BlocBuilder` هي\nWidget من Flutter ضمن [حزمة flutter_bloc](https://pub.dev/packages/flutter_bloc)\nتتولى بناء ويدجت استجابةً لحالات bloc الجديدة. وعند تغيّر حالة `PostBloc`، سيتم\nاستدعاء `builder` بالحالة الجديدة `PostState`.\n\n:::caution\n\nيجب أن نتذكر تنظيف الموارد والتخلّص من `ScrollController` عند التخلص من\n`StatefulWidget`.\n\n:::\n\nعند تمرير المستخدم، نحسب المسافة التي وصل إليها داخل الصفحة. وإذا بلغت المسافة\n`>= 90%` من `maxScrollExtent` نضيف حدث `PostFetched` لتحميل مزيد من المنشورات.\n\nبعد ذلك، نحتاج لتنفيذ Widget `BottomLoader` التي توضح للمستخدم أننا نحمّل المزيد\nمن المنشورات.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\nأخيرًا، نحتاج إلى تنفيذ `PostListItem` التي تعرض عنصر `Post` واحدًا.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\nفي هذه المرحلة يفترض أن يعمل التطبيق بشكل صحيح، لكن ما زال هناك تحسين إضافي.\n\nمن مزايا مكتبة bloc أننا نستطيع الوصول إلى جميع الانتقالات (`Transitions`) في\nمكان واحد.\n\nالتغيير من حالة إلى أخرى يسمى انتقالًا (`Transition`).\n\n:::note\n\nيتكون الانتقال (`Transition`) من الحالة الحالية، والحدث، والحالة التالية.\n\n:::\n\nعلى الرغم من أن لدينا في هذا التطبيق كتلة واحدة فقط، فمن الشائع جدًا في\nالتطبيقات الأكبر أن يكون لدينا العديد من الكتل التي تدير أجزاء مختلفة من حالة\nالتطبيق.\n\nإذا أردنا تنفيذ منطق معيّن استجابةً لكل الانتقالات (`Transitions`)، يمكننا\nببساطة إنشاء `BlocObserver` خاص بنا.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\nكل ما نحتاجه هو التوسيع (extend) من `BlocObserver` وعمل override للدالة\n`onTransition`.\n\n:::\n\nالآن، في كل مرة يحدث فيها انتقال (`Transition`) داخل Bloc، يمكننا رؤية تفاصيله\nمطبوعة في terminal.\n\n:::note\n\nعمليًا، يمكنك إنشاء `BlocObservers` مختلفة. وبما أن كل تغيّر في الحالة يُسجّل،\nيمكننا بسهولة فحص التطبيق وتتبع تفاعلات المستخدم وتغييرات الحالة في مكان واحد.\n\n:::\n\nهذا كل ما في الأمر! نجحنا الآن في تنفيذ قائمة لانهائية في Flutter باستخدام حزمتي\n[bloc](https://pub.dev/packages/bloc) و\n[flutter_bloc](https://pub.dev/packages/flutter_bloc)، مع فصل طبقة العرض عن منطق\nالأعمال.\n\nلا تعرف `PostsPage` من أين تأتي `Posts` أو كيف يتم جلبها. وفي المقابل، لا يعرف\n`PostBloc` كيف تُعرض `State`؛ هو فقط يحوّل الأحداث إلى حالات.\n\nيمكن العثور على المصدر الكامل لهذا المثال\n[هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-login.mdx",
    "content": "---\ntitle: تسجيل الدخول في Flutter\ndescription: دليل متعمّق لبناء مسار تسجيل الدخول في Flutter باستخدام مكتبة Bloc.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nفي هذا الدليل، سنقوم ببناء مسار تسجيل دخول في Flutter باستخدام مكتبة Bloc.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## المواضيع الرئيسية\n\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter\n  توفّر bloc للأبناء (subtree).\n- إضافة الأحداث باستخدام [context.read](/ar/flutter-bloc-concepts#contextread).\n- تجنب إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي Widget\n  في Flutter توفّر repository للأبناء.\n- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي Widget في Flutter\n  تنفّذ كود المستمع استجابةً لتغيّرات حالة الـ bloc.\n- تحديث واجهة المستخدم بناءً على جزء من حالة الـ bloc باستخدام\n  [context.select](/ar/flutter-bloc-concepts#contextselect).\n\n## إعداد المشروع\n\nسنبدأ بإنشاء مشروع Flutter جديد بالكامل.\n\n<FlutterCreateSnippet />\n\nبعد ذلك، يمكننا تثبيت جميع التبعيات (dependencies).\n\n<FlutterPubGetSnippet />\n\n## مستودع المصادقة (Authentication Repository)\n\nأول شيء سنقوم به هو إنشاء حزمة `authentication_repository` والتي ستكون مسؤولة عن\nإدارة مجال المصادقة.\n\nسنبدأ بإنشاء مجلد `packages/authentication_repository` في جذر المشروع والذي\nسيحتوي على جميع الحزم الداخلية.\n\nعلى مستوى عالٍ، يجب أن يبدو هيكل المجلدات بالشكل التالي:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\nبعد ذلك، يمكننا إنشاء ملف `pubspec.yaml` لحزمة `authentication_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\nحزمة `package:authentication_repository` ستكون حزمة Dart نقية بدون أي تبعيات\nخارجية.\n\n:::\n\nبعد ذلك، نحتاج إلى تنفيذ الفئة `AuthenticationRepository` نفسها والتي ستكون في\n`packages/authentication_repository/lib/src/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nتوفّر `AuthenticationRepository` تدفق `Stream` من تحديثات\n`AuthenticationStatus`، ويُستخدم هذا التدفق لإبلاغ التطبيق عند تسجيل المستخدم\nالدخول أو الخروج.\n\nبالإضافة إلى ذلك، هناك دالتا `logIn` و`logOut` مبسّطتان للشرح، لكن يمكن بسهولة\nتوسيعها للمصادقة باستخدام `FirebaseAuth` مثلاً أو أي مزود مصادقة آخر.\n\n:::note\n\nبما أننا ندير `StreamController` داخليًا، تم توفير دالة `dispose` لإغلاق\ncontroller عندما لا يعود مطلوبًا.\n\n:::\n\nأخيرًا، نحتاج إلى إنشاء الملف\n`packages/authentication_repository/lib/authentication_repository.dart` والذي\nسيحتوي على الصادرات العامة (public exports):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\nهذا كل شيء بالنسبة لـ `AuthenticationRepository`، في الخطوة التالية سنعمل على\n`UserRepository`.\n\n## مستودع المستخدم\n\nتمامًا كما فعلنا مع `AuthenticationRepository`، سنقوم بإنشاء حزمة\n`user_repository` داخل مجلد `packages`.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\nبعد ذلك، سنقوم بإنشاء ملف `pubspec.yaml` الخاص بـ `user_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\nحزمة `user_repository` مسؤولة عن نطاق المستخدم، وتوفّر APIs للتفاعل مع المستخدم\nالحالي.\n\nأول شيء سنحدده هو نموذج المستخدم في الملف  \n`packages/user_repository/lib/src/models/user.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\nلأجل البساطة، يحتوي المستخدم على خاصية `id` فقط، لكن في التطبيق العملي قد تكون\nهناك خصائص إضافية مثل `firstName`، `lastName`، `avatarUrl` وغيرها...\n\n:::note\n\nيتم استخدام [`package:equatable`](https://pub.dev/packages/equatable) لتمكين\nمقارنة القيم داخل كائن `User`.\n\n:::\n\nبعد ذلك، يمكننا إنشاء ملف `models.dart` داخل\n`packages/user_repository/lib/src/models` ليقوم بتصدير كل النماذج، بحيث يمكننا\nاستخدام استيراد واحد لاستدعاء نماذج متعددة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\nالآن بعد تعريف النماذج، يمكننا تنفيذ فئة `UserRepository` في\n`packages/user_repository/lib/src/user_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\nفي هذا المثال البسيط، توفّر `UserRepository` دالة واحدة فقط هي `getUser` والتي\nتسترجع المستخدم الحالي. نحن هنا نستخدم stubbing، لكن في التطبيق الفعلي ستكون هذه\nالدالة هي التي تستعلم المستخدم الحالي من الخادم (backend).\n\nلقد اقتربنا من الانتهاء من حزمة `user_repository`، والشيء الوحيد المتبقي هو\nإنشاء ملف `user_repository.dart` في المسار `packages/user_repository/lib` والذي\nيعرّف الصادرات العامة (public exports):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\nالآن بعد أن أتممنا حزمتي `authentication_repository` و `user_repository`، يمكننا\nالانتقال للتركيز على تطبيق Flutter.\n\n## تثبيت التبعيات\n\nلنبدأ بتحديث ملف `pubspec.yaml` المُولد في جذر مشروعنا:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nيمكننا تثبيت التبعيات عن طريق تشغيل الأمر:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\nسيتولى `AuthenticationBloc` مسؤولية الاستجابة لتغيرات حالة المصادقة (التي يعرضها\n`AuthenticationRepository`) وسيصدر حالات يمكننا التفاعل معها في طبقة العرض.\n\nتم تنفيذ `AuthenticationBloc` داخل مجلد `lib/authentication` لأننا نعتبر\nالمصادقة كميزة في طبقة التطبيق الخاصة بنا.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\nاستخدم\n[امتداد VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nأو [إضافة IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) لإنشاء\nblocs تلقائيًا.\n\n:::\n\n### authentication_event.dart\n\nتمثل مثيلات `AuthenticationEvent` المدخلات إلى `AuthenticationBloc`، وسيتم\nمعالجتها لاستخدامها في إصدار مثيلات جديدة من `AuthenticationState`.\n\nفي هذا التطبيق، سيستجيب `AuthenticationBloc` لحدثين مختلفين:\n\n- `AuthenticationSubscriptionRequested`: الحدث الأولي الذي يُبلغ الـ bloc\n  بالاشتراك في تدفق `AuthenticationStatus`.\n- `AuthenticationLogoutPressed`: يُعلم الـ bloc بحدوث تسجيل خروج من قِبل\n  المستخدم.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\nلننتقل الآن للنظر في `AuthenticationState`.\n\n### authentication_state.dart\n\nتمثل مثيلات `AuthenticationState` نواتج `AuthenticationBloc` وسيتم استهلاكها من\nقبل طبقة العرض.\n\nلفئة `AuthenticationState` ثلاث مُنشئين مسمّين:\n\n- `AuthenticationState.unknown()`: الحالة الافتراضية التي تدل على أن الـ bloc لا\n  يعرف بعد ما إذا كان المستخدم الحالي مصدقًا أم لا.\n\n- `AuthenticationState.authenticated()`: الحالة التي تشير إلى أن المستخدم حالياً\n  مصدق عليه.\n\n- `AuthenticationState.unauthenticated()`: الحالة التي تدل على أن المستخدم\n  حالياً غير مصدق عليه.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\nبعد أن اطلعنا على تنفيذ `AuthenticationEvent` و`AuthenticationState`، دعونا نلقي\nنظرة على `AuthenticationBloc`.\n\n### authentication_bloc.dart\n\nيدير `AuthenticationBloc` حالة المصادقة في التطبيق، والتي تُستخدم لاتخاذ قرارات\nمثل بدء المستخدم في صفحة تسجيل الدخول أو الصفحة الرئيسية.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\nيعتمد `AuthenticationBloc` على كل من `AuthenticationRepository`\nو`UserRepository`، ويحدد الحالة الابتدائية كـ `AuthenticationState.unknown()`.\n\nفي مُنشئ الـ bloc، يتم ربط فئات الأحداث المشتقة من `AuthenticationEvent`\nبمعالجيها المناسبين.\n\nفي معالج الحدث `_onSubscriptionRequested`، يستخدم `AuthenticationBloc`\n`emit.onEach` للاشتراك في تدفق `status` الخاص بـ `AuthenticationRepository`\nوإصدار حالة استجابةً لكل حالة من `AuthenticationStatus`.\n\n`emit.onEach` يقوم بإنشاء اشتراك داخلي في التدفق ويتولى إلغاءه تلقائيًا عند\nإغلاق `AuthenticationBloc` أو تدفق `status`.\n\nإذا أصدر تدفق `status` خطأً، فإن `addError` يمرّر الخطأ مع `stackTrace` لأي\n`BlocObserver` يستمع.\n\n:::caution\n\nإذا تم حذف `onError`، تُعتبر أي أخطاء في تدفق `status` غير معالجة، وسيتم رميها\nبواسطة `onEach`، مما يؤدي إلى إلغاء الاشتراك في التدفق.\n\n:::\n\n:::tip\n\nيُعد [`BlocObserver`](/ar/bloc-concepts/#blocobserver) أداة ممتازة لتسجيل أحداث\nالـ Bloc والأخطاء وتغيرات الحالة، خصوصًا في سياق التحليلات وتقرير الأعطال.\n\n:::\n\nعندما يصدر تدفق `status` الحالة `AuthenticationStatus.unknown` أو\n`unauthenticated`، يتم إصدار الحالة المطابقة في `AuthenticationState`.\n\nعندما يُصدر التدفق `AuthenticationStatus.authenticated`، يقوم\n`AuthenticationBloc` باستعلام بيانات المستخدم عبر `UserRepository`.\n\n## main.dart\n\nبعد ذلك، يمكننا استبدال ملف `main.dart` الافتراضي بالنص التالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## التطبيق\n\n`app.dart` يحتوي على ويدجت الجذر `App` الخاص بالتطبيق بأكمله.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nتم تقسيم `app.dart` إلى جزأين: `App` و`AppView`. يتحمل `App` مسؤولية إنشاء/توفير\n`AuthenticationBloc` الذي سيتم استهلاكه من قبل `AppView`. هذا الفصل (decoupling)\nيسمح لنا باختبار كل من Widget `App` و`AppView` بسهولة لاحقًا.\n\n:::\n\n:::note\n\nيُستخدم `RepositoryProvider` لتوفير instance واحدة من `AuthenticationRepository`\nلكامل التطبيق، وهو ما سيكون مفيدًا لاحقًا.\n\n:::\n\nافتراضيًا، `BlocProvider` يكون lazy ولا يستدعي `create` إلا عند أول وصول إلى الـ\nBloc. وبما أن `AuthenticationBloc` يجب أن يشترك دائمًا في stream\n`AuthenticationStatus` فورًا (عبر الحدث `AuthenticationSubscriptionRequested`)،\nيمكننا تجاوز هذا السلوك صراحةً عن طريق ضبط `lazy: false`.\n\n`AppView` هو `StatefulWidget` لأنه يحتفظ بـ `GlobalKey` الذي يُستخدم للوصول إلى\nحالة الـ `Navigator`. بشكل افتراضي، يقوم `AppView` بعرض `SplashPage` (التي\nسنراها لاحقًا) ويستخدم `BlocListener` للتنقل بين الصفحات المختلفة بناءً على\nالتغيرات في حالة `AuthenticationState`.\n\n## شاشة البداية\n\nميزة شاشة البداية ستتكون من عرض بسيط يُعرض فور إطلاق التطبيق بينما يحدد التطبيق\nما إذا كان المستخدم مصادقًا عليه.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage` توفّر مسارًا (`Route`) ثابتًا، مما يجعل التنقل إليها سهلًا باستخدام\n`Navigator.of(context).push(SplashPage.route())`;\n\n:::\n\n## تسجيل الدخول\n\nيحتوي مسار تسجيل الدخول على `LoginPage` و `LoginForm` و `LoginBloc`، ويسمح\nللمستخدمين بإدخال اسم المستخدم وكلمة المرور لتسجيل الدخول إلى التطبيق.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### نماذج تسجيل الدخول\n\nنستخدم [`package:formz`](https://pub.dev/packages/formz) لإنشاء نماذج قابلة\nلإعادة الاستخدام وموحدة لـ `username` و`password`.\n\n#### اسم المستخدم\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\nلأجل البساطة، نحن نتحقق فقط من أن اسم المستخدم ليس فارغًا، ولكن في التطبيق\nالعملي يمكنك فرض قواعد استخدام الأحرف الخاصة، الطول، وغيرها...\n\n#### كلمة المرور\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\nمرة أخرى، نحن نُجري فحصًا بسيطًا للتأكد من أن كلمة المرور ليست فارغة.\n\n#### ملف التجميع للنماذج (Models Barrel)\n\nكما في السابق، هناك ملف `models.dart` لتسهيل استيراد نماذج `Username` و\n`Password` عبر استيراد واحد فقط.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\nيقوم `LoginBloc` بإدارة حالة `LoginForm` ويتولى التحقق من صحة إدخالات اسم\nالمستخدم وكلمة المرور بالإضافة إلى حالة النموذج.\n\n#### login_event.dart\n\nفي هذا التطبيق، هناك ثلاثة أنواع مختلفة من `LoginEvent`:\n\n- `LoginUsernameChanged`: يخطر الـ bloc بأنه تم تعديل اسم المستخدم.\n- `LoginPasswordChanged`: يخطر الـ bloc بأنه تم تعديل كلمة المرور.\n- `LoginSubmitted`: يخطر الـ bloc بأنه تم تقديم النموذج.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\nيحتوي الـ `LoginState` على حالة النموذج بالإضافة إلى حالات إدخال اسم المستخدم\nوكلمة المرور.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\nنماذج `Username` و `Password` تُستخدم كجزء من `LoginState`، والحالة (status) هي\nأيضًا جزء من [package:formz](https://pub.dev/packages/formz).\n\n:::\n\n#### login_bloc.dart\n\nيتولى `LoginBloc` التفاعل مع تفاعلات المستخدم داخل الـ `LoginForm` والتعامل مع\nالتحقق من صحة النموذج وتقديمه.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\nيعتمد `LoginBloc` على `AuthenticationRepository` لأنه عند تقديم النموذج يستدعي\n`logIn`. الحالة الابتدائية للـ bloc هي `pure`، ما يعني أن الحقول والنموذج لم يتم\nالتفاعل معهما بعد.\n\nعندما يتغير اسم المستخدم أو كلمة المرور، يقوم الـ bloc بإنشاء نسخة \"متسخة\"\n(dirty) من نموذج `Username` أو `Password` ويُحدّث حالة النموذج عبر واجهة برمجة\nالتطبيقات `Formz.validate`.\n\nعند إضافة حدث `LoginSubmitted`، إذا كانت حالة النموذج الحالية صالحة، يقوم الـ\nbloc باستدعاء `logIn` ويُحدّث الحالة بناءً على نتيجة الطلب.\n\nبعد ذلك، سنلقي نظرة على `LoginPage` و `LoginForm`.\n\n### صفحة تسجيل الدخول\n\nتتولى `LoginPage` مسؤولية توفير الـ `Route` بالإضافة إلى إنشاء وتوفير\n`LoginBloc` لـ `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\nيُستخدم `context.read<AuthenticationRepository>()` للعثور على instance من\n`AuthenticationRepository` عبر `BuildContext`.\n\n:::\n\n### نموذج تسجيل الدخول\n\nيتولى `LoginForm` إخطار `LoginBloc` بأحداث المستخدم ويستجيب أيضًا للتغيرات في\nالحالة باستخدام `BlocBuilder` و `BlocListener`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\nيُستخدم `BlocListener` لعرض `SnackBar` في حال فشل تقديم بيانات تسجيل الدخول.\nبالإضافة إلى ذلك، يُستخدم `context.select` للوصول بكفاءة إلى أجزاء محددة من\n`LoginState` لكل Widget، مما يمنع عمليات البناء غير الضرورية. تُستخدم دالة\n`onChanged` لإخطار `LoginBloc` بالتغييرات التي تطرأ على اسم المستخدم أو كلمة\nالمرور.\n\nيتم تفعيل Widget `_LoginButton` فقط إذا كانت حالة النموذج صالحة، ويُعرض مؤشر\nتحميل دائري `CircularProgressIndicator` مكانه أثناء تقديم النموذج.\n\n## الصفحة الرئيسية\n\nعند نجاح طلب `logIn`، ستتغير حالة `AuthenticationBloc` إلى `authenticated` وسيتم\nتوجيه المستخدم إلى صفحة `HomePage` حيث نعرض معرف المستخدم (`id`) بالإضافة إلى زر\nلتسجيل الخروج.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### الصفحة الرئيسية\n\nيمكن لصفحة `HomePage` الوصول إلى معرف المستخدم الحالي عبر  \n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` وعرضه باستخدام\nWidget `Text`. بالإضافة إلى ذلك، عند الضغط على زر تسجيل الخروج يتم إضافة حدث\n`AuthenticationLogoutPressed` إلى الـ `AuthenticationBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` يُحدث تحديثات\nتلقائية في حال تغيّر معرف المستخدم.\n\n:::\n\nفي هذه المرحلة لدينا تنفيذ قوي لمسار تسجيل الدخول وقد قمنا بفصل طبقة العرض عن\nطبقة منطق الأعمال باستخدام Bloc.\n\nيمكن العثور على الأكواد البرمجية المصدرية الكاملة لهذا المثال (بما في ذلك\nاختبارات الوحدة واختبارات Widgets)\n[هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_login).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: مؤقت Flutter (Flutter Timer)\ndescription:\n  دليل متعمّق لبناء تطبيق مؤقّت (Timer) في Flutter باستخدام مكتبة Bloc.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nفي هذا الدليل، سنتعلّم كيفية بناء تطبيق مؤقّت باستخدام مكتبة Bloc. يفترض أن يبدو\nالتطبيق النهائي بهذا الشكل:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## المواضيع الرئيسية\n\n- مراقبة تغييرات الحالة باستخدام [BlocObserver](/ar/bloc-concepts#blocobserver).\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter\n  توفّر Bloc للأبناء.\n- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter\n  تتولّى إعادة البناء استجابةً للحالات الجديدة.\n- منع إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- تعلم استخدام `StreamSubscription` داخل Bloc.\n- منع إعادة البناء غير الضرورية باستخدام `buildWhen`.\n\n## الإعداد\n\nسنبدأ بإنشاء مشروع Flutter جديد بالكامل:\n\n<FlutterCreateSnippet />\n\nبعد ذلك، يمكننا استبدال محتويات `pubspec.yaml` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nسنستخدم حزمتي [flutter_bloc](https://pub.dev/packages/flutter_bloc) و\n[equatable](https://pub.dev/packages/equatable) في هذا التطبيق.\n\n:::\n\nثم شغّل `flutter pub get` لتثبيت جميع التبعيات (dependencies).\n\n## هيكل المشروع\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## المؤقت (Ticker)\n\nسيكون `Ticker` هو مصدر البيانات لتطبيق المؤقّت. إذ يوفّر stream من النبضات\n(`ticks`) يمكننا الاشتراك فيه والتفاعل معه.\n\nابدأ بإنشاء الملف `ticker.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\nكل ما يفعله class `Ticker` هو توفير الدالة `tick` التي تستقبل عدد النبضات\n(الثواني) المطلوب، ثم ترجع stream يصدر الثواني المتبقية كل ثانية.\n\nبعد ذلك، نحتاج إلى إنشاء `TimerBloc` الذي سيستهلك `Ticker`.\n\n## Timer Bloc\n\n### حالة المؤقت (TimerState)\n\nسنبدأ بتعريف `TimerStates` التي يمكن أن تكون عليها `TimerBloc`.\n\nيمكن أن تكون حالة `TimerBloc` الخاصة بنا واحدة مما يلي:\n\n- `TimerInitial`: جاهز لبدء العد التنازلي من المدة المحددة.\n- `TimerRunInProgress`: يعد تنازليًا بنشاط من المدة المحددة.\n- `TimerRunPause`: متوقف مؤقتًا عند مدة متبقية معينة.\n- `TimerRunComplete`: اكتمل بمدة متبقية 0.\n\nكل حالة من هذه الحالات تؤثر على واجهة المستخدم والإجراءات المتاحة للمستخدم. على\nسبيل المثال:\n\n- إذا كانت الحالة هي `TimerInitial`، فسيتمكن المستخدم من بدء المؤقت.\n- إذا كانت الحالة هي `TimerRunInProgress`، فسيتمكن المستخدم من إيقاف المؤقت\n  مؤقتًا وإعادة تعيينه، بالإضافة إلى رؤية المدة المتبقية.\n- إذا كانت الحالة هي `TimerRunPause`، فسيتمكن المستخدم من استئناف المؤقت وإعادة\n  تعيينه.\n- إذا كانت الحالة هي `TimerRunComplete`، فسيتمكن المستخدم من إعادة تعيين المؤقت.\n\nللحفاظ على جميع ملفات bloc معًا، لننشئ مجلد `bloc` ونضيف\n`bloc/timer_state.dart`.\n\n:::tip\n\nيمكنك استخدام إضافات\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) أو\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nلإنشاء ملفات bloc التالية تلقائيًا.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\nلاحظ أن جميع `TimerStates` ترث من abstract base class `TimerState` الذي يحتوي\nعلى الخاصية `duration`. السبب هو أننا نريد معرفة الوقت المتبقي مهما كانت حالة\n`TimerBloc`. بالإضافة إلى ذلك، يرث `TimerState` من `Equatable` لتحسين الأكواد\nالبرمجية ومنع إعادة البناء عندما تتكرر الحالة نفسها.\n\nبعد ذلك، لنحدّد وننفّذ `TimerEvents` التي سيعالجها `TimerBloc`.\n\n### حدث المؤقت (TimerEvent)\n\nيحتاج `TimerBloc` إلى معرفة كيفية معالجة الأحداث التالية:\n\n- `TimerStarted`: يُعلم `TimerBloc` بضرورة بدء المؤقت.\n- `TimerPaused`: يُعلم `TimerBloc` بضرورة إيقاف المؤقت مؤقتًا.\n- `TimerResumed`: يُعلم `TimerBloc` بضرورة استئناف المؤقت.\n- `TimerReset`: يُعلم `TimerBloc` بضرورة إعادة تعيين المؤقت إلى حالته الأصلية.\n- `_TimerTicked`: يُعلم `TimerBloc` بحدوث نبضة (`tick`) وبضرورة تحديث حالته\n  وفقًا لذلك.\n\nإذا لم تستخدم إضافات\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) أو\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)،\nفأنشئ `bloc/timer_event.dart` ونفّذ هذه الأحداث.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\nوالآن لننفّذ `TimerBloc`.\n\n### TimerBloc\n\nإذا لم تكن قد فعلت ذلك بعد، فأنشئ `bloc/timer_bloc.dart` وأنشئ `TimerBloc`\nفارغًا.\n\n<TimerBlocEmptySnippet />\n\nأول خطوة هي تحديد الحالة الأولية لـ `TimerBloc`. هنا نريد أن يبدأ `TimerBloc` في\nحالة `TimerInitial` بمدة مضبوطة مسبقًا تبلغ دقيقة واحدة (60 ثانية).\n\n<TimerBlocInitialStateSnippet />\n\nبعد ذلك، نحتاج إلى تحديد dependency على `Ticker`.\n\n<TimerBlocTickerSnippet />\n\nكما نعرّف `StreamSubscription` لـ `Ticker` وسنعود إليها بعد قليل.\n\nفي هذه المرحلة، المتبقي هو تنفيذ event handlers. ولتحسين قابلية القراءة، نفصل كل\nمعالج في helper function مستقلة. سنبدأ بحدث `TimerStarted`.\n\n<TimerBlocOnStartedSnippet />\n\nإذا استقبل `TimerBloc` حدث `TimerStarted`، فإنه يصدر حالة `TimerRunInProgress`\nبمدة البداية. وإذا كانت `_tickerSubscription` مفتوحة مسبقًا، فنحتاج إلى إلغائها\nلتحرير الذاكرة. كما نحتاج إلى عمل override للدالة `close` في `TimerBloc` حتى\nنلغي `_tickerSubscription` عند إغلاق الـ bloc. أخيرًا، نستمع إلى stream\n`_ticker.tick`، ومع كل نبضة نضيف حدث `_TimerTicked` بالمدة المتبقية.\n\nبعد ذلك، دعنا ننفذ معالج حدث `_TimerTicked`.\n\n<TimerBlocOnTickedSnippet />\n\nفي كل مرة نستقبل فيها حدث `_TimerTicked`، إذا كانت مدة النبضة أكبر من 0 فنحن\nبحاجة إلى إصدار حالة `TimerRunInProgress` محدثة بالمدة الجديدة. أما إذا كانت مدة\nالنبضة تساوي 0 فقد انتهى المؤقّت ونحتاج إلى إصدار حالة `TimerRunComplete`.\n\nالآن دعنا ننفذ معالج حدث `TimerPaused`.\n\n<TimerBlocOnPausedSnippet />\n\nفي `_onPaused`، إذا كانت `state` الخاصة بـ `TimerBloc` هي `TimerRunInProgress`\nفيمكننا إيقاف `_tickerSubscription` مؤقتًا وإصدار حالة `TimerRunPause` بالمدة\nالحالية.\n\nبعد ذلك، لننفذ معالج حدث `TimerResumed` حتى نستأنف المؤقّت.\n\n<TimerBlocOnResumedSnippet />\n\nمعالج حدث `TimerResumed` مشابه جدًا لمعالج حدث `TimerPaused`. إذا كانت حالة\n`TimerBloc` من نوع `TimerRunPause` ووصل حدث `TimerResumed`، فإنه يستأنف\n`_tickerSubscription` ويصدر حالة `TimerRunInProgress` بالمدة الحالية.\n\nأخيرًا، نحتاج إلى تنفيذ معالج حدث `TimerReset`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\nإذا استقبل `TimerBloc` حدث `TimerReset`، فإنه يحتاج إلى إلغاء\n`_tickerSubscription` الحالية حتى لا يتلقى أي نبضات إضافية، ثم يصدر حالة\n`TimerInitial` بالمدة الأصلية.\n\nهذا كل ما يخص `TimerBloc`. والمتبقي الآن هو تنفيذ واجهة المستخدم (UI) للتطبيق.\n\n## واجهة مستخدم التطبيق (Application UI)\n\n### MyApp\n\nيمكننا البدء بحذف محتويات `main.dart` واستبدالها بما يلي.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nبعد ذلك، لننشئ Widget التطبيق في `app.dart`، والتي ستكون جذر التطبيق.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nبعد ذلك، نحتاج إلى تنفيذ Widget `Timer`.\n\n### المؤقت (Timer)\n\nWidget `Timer` في (`lib/timer/view/timer_page.dart`) مسؤولة عن عرض الوقت المتبقي\nمع الأزرار المناسبة التي تمكّن المستخدم من بدء المؤقّت وإيقافه مؤقتًا وإعادة\nتعيينه.\n\n<TimerPageSnippet />\n\nحتى الآن، نستخدم `BlocProvider` فقط للوصول إلى instance من `TimerBloc`.\n\nبعد ذلك، سننفّذ Widget `Actions` والتي ستحتوي على الإجراءات المناسبة (بدء، إيقاف\nمؤقت، وإعادة تعيين).\n\n### ملف التجميع (Barrel)\n\nلتنظيم عمليات الاستيراد من قسم `Timer`، نحتاج إلى إنشاء ملف تجميعي\n(`barrel file`) باسم `timer/timer.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### الإجراءات (Actions)\n\n<ActionsSnippet />\n\nWidget `Actions` هي `StatelessWidget` تستخدم `BlocBuilder` لإعادة بناء واجهة\nالمستخدم كلما حصلنا على `TimerState` جديدة. تستخدم `Actions` الدالة\n`context.read<TimerBloc>()` للوصول إلى instance من `TimerBloc`، وتُرجع أزرار\n`FloatingActionButton` مختلفة حسب الحالة الحالية لـ `TimerBloc`. كل زر من أزرار\n`FloatingActionButton` يضيف event داخل callback `onPressed` لإخطار `TimerBloc`.\n\nإذا أردت تحكمًا أدق في توقيت استدعاء `builder`، يمكنك تمرير `buildWhen`\nاختياريًا إلى `BlocBuilder`. تستقبل `buildWhen` الحالة السابقة والحالة الحالية\nللـ bloc وتعيد قيمة منطقية (`boolean`). إذا أعادت `true` فسيُستدعى `builder`\nبالحالة وتحدث إعادة البناء. وإذا أعادت `false` فلن يُستدعى `builder` ولن تحدث\nإعادة بناء.\n\nفي هذه الحالة، لا نريد إعادة بناء Widget `Actions` في كل نبضة لأن ذلك غير فعّال.\nبدلًا من ذلك، نريد إعادة بناء `Actions` فقط إذا تغيّر `runtimeType` لـ\n`TimerState` (على سبيل المثال: TimerInitial => TimerRunInProgress،\nTimerRunInProgress => TimerRunPause، إلخ...).\n\nنتيجةً لذلك، إذا قمنا بتلوين الـ Widgets عشوائيًا عند كل إعادة بناء، فسيبدو\nالأمر كما يلي:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\nعلى الرغم من أن Widget `Text` تُعاد بناؤها في كل نبضة، فإننا نعيد بناء `Actions`\nفقط إذا كانت بحاجة إلى إعادة بناء.\n\n:::\n\n### الخلفية (Background)\n\nأخيرًا، أضف Widget الخلفية كما يلي:\n\n<BackgroundSnippet />\n\n### تجميع كل شيء معًا\n\nهذا كل ما في الأمر! في هذه المرحلة أصبح لدينا تطبيق مؤقّت جيد يعيد بناء Widgets\nالتي تحتاج فقط إلى إعادة البناء بكفاءة.\n\nيمكن العثور على المصدر الكامل لهذا المثال\n[هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Flutter Todos\ndescription:\n  دليل متعمّق لبناء تطبيق مهام (Todos) في Flutter باستخدام مكتبة Bloc.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nفي هذا الدليل التعليمي، سنبني تطبيق مهام (Todos) في Flutter باستخدام مكتبة Bloc.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## المواضيع الرئيسية\n\n- [Bloc و Cubit](/ar/bloc-concepts/#cubit-مقابل-bloc) لإدارة حالات الميزات\n  المختلفة.\n- [الهيكلية الطبقية](/ar/architecture) لفصل الاهتمامات/المسؤوليات وتسهيل إعادة\n  الاستخدام.\n- [BlocObserver](/ar/bloc-concepts#blocobserver) لمراقبة تغييرات الحالة.\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter\n  توفر Bloc للأبناء.\n- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter\n  تتولى إعادة البناء استجابةً للحالات الجديدة.\n- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي Widget في Flutter\n  تنفذ تأثيرات جانبية استجابةً لتغيرات الحالة.\n- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي Widget\n  في Flutter توفر مستودع (repository) للأبناء.\n- [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable) لمنع عمليات إعادة البناء غير\n  الضرورية.\n- [MultiBlocListener](/ar/flutter-bloc-concepts#multibloclistener)، وهي Widget\n  في Flutter تقلل التعشيش عند استخدام عدة BlocListeners.\n\n## الإعداد\n\nسنبدأ بإنشاء مشروع Flutter جديد كليًا باستخدام  \n[very_good_cli](https://pub.dev/packages/very_good_cli).\n\n<FlutterCreateSnippet />\n\n:::note\n\nقم بتثبيت `very_good_cli` باستخدام الأمر التالي:\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\nبعد ذلك، سنقوم بإنشاء الحزم `todos_api`، `local_storage_todos_api`، و  \n`todos_repository` باستخدام `very_good_cli`:\n\n<FlutterCreatePackagesSnippet />\n\nبإمكاننا بعد ذلك استبدال محتويات ملف `pubspec.yaml` بـ:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nوأخيرًا، ثبّت جميع التبعيات (dependencies):\n\n<VeryGoodPackagesGetSnippet />\n\n## هيكلية المشروع\n\nيجب أن تبدو هيكلية مشروع التطبيق لدينا على النحو التالي:\n\n<ProjectStructureSnippet />\n\nنقسم المشروع إلى عدة حزم للحفاظ على تبعيات واضحة وصريحة لكل حزمة، مع حدود واضحة\nتفرض\n[مبدأ المسؤولية الواحدة](https://en.wikipedia.org/wiki/Single-responsibility_principle).\nتؤدي هذه الطريقة في تنظيم المشروع إلى فوائد عديدة تشمل، ولكن لا تقتصر على:\n\n- سهولة إعادة استخدام الحزم عبر مشاريع متعددة\n- تحسينات في الـ CI/CD من حيث الكفاءة (تشغيل الفحوصات فقط على الكود الذي تم\n  تغييره)\n- سهولة صيانة الحزم بمعزل عن بعضها مع مجموعات اختبار مخصصة، وإصدار نسخ دلالية،\n  ودورة/إيقاع إصدار واضحة\n\n## البنية المعمارية\n\n![مخطط بنية تطبيق المهام](~/assets/tutorials/todos-architecture.png)\n\nتنظيم الكود على شكل طبقات أمر بالغ الأهمية ويساعدنا على تطوير التطبيق بسرعة\nوثقة. كل طبقة تتحمل مسؤولية واحدة فقط، ويمكن استخدامها واختبارها بشكل مستقل. هذا\nيسمح لنا بحصر التعديلات داخل طبقة معينة لتقليل تأثيرها على كامل التطبيق.\nبالإضافة إلى ذلك، فصل الطبقات في التطبيق يسمح لنا بإعادة استخدام المكتبات بسهولة\nعبر مشاريع متعددة، خاصة فيما يتعلق بطبقة البيانات.\n\nيتكون تطبيقنا من ثلاث طبقات رئيسية:\n\n- طبقة البيانات\n- طبقة المجال (Domain)\n- طبقة المميزات\n  - طبقة العرض/UI (widgets)\n  - منطق الأعمال (blocs/cubits)\n\n**طبقة البيانات**\n\nهذه الطبقة هي الأدنى في الهيكل، وهي مسؤولة عن جلب البيانات الخام من مصادر خارجية\nمثل قواعد البيانات، واجهات برمجة التطبيقات (APIs)، وغيرها. الحزم (packages) في\nطبقة البيانات عادة لا تعتمد على أي جزء من طبقة العرض، ويمكن إعادة استخدامها أو\nحتى نشرها على [pub.dev](https://pub.dev) كحزمة مستقلة. في هذا المثال، تتضمن طبقة\nالبيانات لدينا حزمتَي `todos_api` و `local_storage_todos_api`.\n\n**طبقة المجال**\n\nتجمع هذه الطبقة بين مزود بيانات أو أكثر وتطبق قواعد الأعمال على البيانات. كل\nمكون في هذه الطبقة يسمى repository وتدير كل repository مجالًا واحدًا عادةً.\nالحزم في طبقة الـ repository ينبغي أن تتعامل فقط مع طبقة البيانات. في هذا\nالمثال، تتكون طبقة الـ repository لدينا من حزمة `todos_repository`.\n\n**طبقة المميزات**\n\nتحتوي هذه الطبقة على جميع الميزات وحالات الاستخدام الخاصة بالتطبيق. تتضمن كل\nميزة عادةً بعض عناصر الواجهة (UI) ومنطق الأعمال. يجب أن تكون الميزات مستقلة عن\nبعضها البعض لتسهيل إضافتها أو إزالتها دون التأثير على بقية قاعدة الكود. ضمن كل\nميزة، يتم إدارة حالة الميزة ومنطق الأعمال بواسطة blocs. تتفاعل الـ blocs مع صفر\nأو أكثر من المستودعات (repositories). تستجيب الـ blocs للأحداث (events) وتصدر\nالحالات (states) التي تؤدي إلى تغيير واجهة المستخدم. الأدوات (widgets) في كل\nميزة تعتمد عادةً على الـ bloc المناظر وتقوم بعرض واجهة المستخدم بناءً على الحالة\nالحالية. يمكن لواجهة المستخدم إعلام الـ bloc بإدخالات المستخدم عبر الأحداث. في\nهذا المثال، يتكون تطبيقنا من الميزات التالية: `home`، `todos_overview`، `stats`،\nو `edit_todos`.\n\nالآن بعد أن استعرضنا الطبقات على مستوى عالٍ، لنبدأ ببناء تطبيقنا بدءًا من طبقة\nالبيانات!\n\n## طبقة البيانات\n\nطبقة البيانات هي الطبقة الأدنى في تطبيقنا وتتكون من موفري البيانات الخام. الحزم\nالموجودة في هذه الطبقة تُعنى أساساً بمصدر البيانات وكيفية الحصول عليها. في\nحالتنا، ستتكون طبقة البيانات من `TodosApi`، وهو واجهة (interface)،\nو`LocalStorageTodosApi`، وهو تنفيذ لـ `TodosApi` يعتمد على `shared_preferences`.\n\n### TodosApi\n\nحزمة `todos_api` ستصدر واجهة عامة للتفاعل مع وإدارة المهام (todos). لاحقًا سنقوم\nبتنفيذ `TodosApi` باستخدام `shared_preferences`. وجود تجريد (abstraction) يجعل\nمن السهل دعم تنفيذات أخرى دون الحاجة لتغيير أي جزء آخر من التطبيق. على سبيل\nالمثال، يمكننا لاحقًا إضافة `FirestoreTodosApi` التي تستخدم `cloud_firestore`\nبدلاً من `shared_preferences` مع تغييرات برمجية طفيفة في باقي التطبيق.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### نموذج Todo\n\nبعد ذلك سنُعرّف نموذج `Todo`.\n\nأول ما يجب ملاحظته هو أن نموذج `Todo` لا يعيش داخل تطبيقنا مباشرة — بل هو جزء من\nحزمة `todos_api`. السبب في ذلك هو أن `TodosApi` تُعرّف واجهات برمجية تُرجع أو\nتستقبل كائنات `Todo`. النموذج هو تمثيل دُرتي (Dart) للكائن الخام `Todo` الذي\nسيتم تخزينه واسترجاعه.\n\nيستخدم نموذج `Todo` مكتبة  \n[json_serializable](https://pub.dev/packages/json_serializable) لمعالجة\nالسيريالايزيشن (serialization) وفكه (deserialization) من وإلى JSON. إذا كنت تتبع\nهذا الشرح، سيتوجب عليك تشغيل  \n[خطوة توليد الأكواد](https://pub.dev/packages/json_serializable#running-the-code-generator) لحل\nأخطاء المترجم.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\nيوفر `json_map.dart` تعريف نوع (typedef) للمساعدة في مراجعة الأكواد والتنقيح\n(linting).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\nنموذج `Todo` مُعرّف في `todos_api/models/todo.dart` ويتم تصديره عبر\n`package:todos_api/todos_api.dart`.\n\n#### تحديث التصديرات\n\nنموذج `Todo` و `TodosApi` يتم تصديرهما عبر ملفات البرميل (barrel files). لاحظ\nكيف أننا لا نستورد النموذج مباشرة، بل نستوردها في `lib/src/todos_api.dart` مع\nإشارة إلى ملف البرميل الخاص بالحزمة:\n`import 'package:todos_api/todos_api.dart';`. حدّث ملفات البرميل هذه لحل أي\nأخطاء استيراد متبقية:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Streams مقابل Futures\n\nفي نسخة سابقة من هذا الدرس، كانت `TodosApi` تعتمد على `Future` وليست على\n`Stream`.\n\nكمثال على API مبني على `Future`، راجع  \n[تنفيذ براين إيجان في عينات عمارة التطبيقات](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core).\n\nيمكن أن يتكون التنفيذ القائم على `Future` من طريقين: `loadTodos` و`saveTodos`\n(لاحظ الجمع). هذا يعني أنه يجب توفير قائمة كاملة من المهام في كل مرة يتم فيها\nاستدعاء الطريقة.\n\n- إحدى القيود في هذا الأسلوب هي أن عمليات CRUD القياسية (إنشاء، قراءة، تحديث،\n  حذف) تتطلب إرسال القائمة الكاملة للمهام مع كل استدعاء. على سبيل المثال، في\n  شاشة إضافة مهمة، لا يمكننا فقط إرسال المهمة المضافة، بل يجب تتبع القائمة\n  الكاملة وإرسال القائمة الجديدة كاملة عند حفظ التغييرات.\n- قيد آخر هو أن `loadTodos` تقدم البيانات لمرة واحدة فقط. لذلك يجب أن يحتوي\n  التطبيق على منطق لطلب التحديثات بشكل دوري.\n\nفي التنفيذ الحالي، تتيح `TodosApi` دفق `Stream<List<Todo>>` عبر `getTodos()`\nوالذي يقوم بإبلاغ جميع المشتركين بالتحديثات في الوقت الحقيقي عند حدوث تغييرات في\nقائمة المهام.\n\nبالإضافة إلى ذلك، يمكن إنشاء المهام وحذفها وتحديثها بشكل منفرد. على سبيل المثال،\nيتم حذف المهمة أو حفظها بتمرير المهمة وحدها فقط كوسيط. لا يلزم توفير القائمة\nالكاملة المحدثة في كل مرة.\n\n### LocalStorageTodosApi\n\nتقوم هذه الحزمة بتنفيذ `todos_api` باستخدام  \nحزمة [`shared_preferences`](https://pub.dev/packages/shared_preferences).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## طبقة المستودع (Repository Layer)\n\nيُعدّ [المستودع](/ar/architecture/#المستودع-repository) جزءًا من طبقة منطق\nالعمل/الأعمال. يعتمد المستودع على واحد أو أكثر من مزودي البيانات الذين لا\nيمتلكون قيمة منطقية للأعمال، ويجمع واجهات برمجة التطبيقات (APIs) الخاصة بهم في\nواجهات توفر قيمة أعمال. بالإضافة إلى ذلك، يساعد وجود طبقة المستودع في تجريد\nعملية الحصول على البيانات عن بقية التطبيق، مما يتيح لنا تغيير مكان أو طريقة\nتخزين البيانات دون التأثير على باقي أجزاء التطبيق.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\nيتطلب إنشاء نسخة من المستودع تحديد `TodosApi`، الذي ناقشناه سابقًا في هذا\nالدليل، لذا قمنا بإضافته كاعتماد في ملف `pubspec.yaml` الخاص بنا:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### تصدير المكتبة\n\nبالإضافة إلى تصدير فصل `TodosRepository`، نقوم أيضًا بتصدير نموذج `Todo` من حزمة\n`todos_api`. تساهم هذه الخطوة في منع الربط الوثيق بين التطبيق ومزودي البيانات.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\nقررنا إعادة تصدير نفس نموذج `Todo` من `todos_api` بدلاً من تعريف نموذج منفصل في\n`todos_repository`، لأننا في هذه الحالة نمتلك السيطرة الكاملة على نموذج\nالبيانات. في العديد من الحالات، لن يكون مزود البيانات شيئًا يمكنك التحكم فيه. في\nهذه الحالات، يصبح من الضروري بشكل متزايد الحفاظ على تعريفات النماذج الخاصة بك\nداخل طبقة المستودع للحفاظ على سيطرة كاملة على الواجهة وعقد الـ API.\n\n## طبقة الخاصية (Feature Layer)\n\n### نقطة الدخول (Entrypoint)\n\nنقطة الدخول لتطبيقنا هي `main.dart`. في هذه الحالة، هناك ثلاث نسخ:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\nأبرز ما يميز هذه النسخ هو أن التنفيذ الملموس لـ `local_storage_todos_api` يتم\nإنشاؤه داخل كل نقطة دخول.\n\n### البوتستراب (Bootstrapping)\n\n`bootstrap.dart` يقوم بتحميل مراقب الـ `BlocObserver` الخاص بنا وينشئ نسخة من\n`TodosRepository`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### التطبيق (App)\n\n`App` يغلف ويدير Widget `RepositoryProvider` الذي يوفر الـ repository لجميع\nالأبناء. بما أن شجرتي الـ widgets الخاصة بصفحة `EditTodoPage` وصفحة `HomePage`\nهي منحدرات لـ `App`، فيمكن لجميع الـ blocs والـ cubits الوصول إلى الـ\nrepository.\n\n`AppView` يقوم بإنشاء تطبيق `MaterialApp` ويضبط الثيم والتعريب (الترجمة).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### الثيم (Theme)\n\nيوفر هذا تعريف الثيم للوضع الفاتح والغامق.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### الصفحة الرئيسية (Home)\n\nالميزة الرئيسية مسؤولة عن إدارة حالة التبويب المختار حالياً وعرض الشجرة الفرعية\nالمناسبة.\n\n#### HomeState\n\nهناك حالتان فقط مرتبطتان بالشاشتين: `todos` و `stats`.\n\n:::note\n\n`EditTodo` هي مسار منفصل لذلك ليست جزءًا من `HomeState`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### HomeCubit\n\nالـ cubit مناسب هنا بسبب بساطة منطق الأعمال. لدينا دالة واحدة `setTab` لتغيير\nالتبويب.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### HomeView\n\nالملف `view.dart` هو ملف تجميعي (barrel file) يصدر كل مكونات واجهة المستخدم\nالمتعلقة بميزة الصفحة الرئيسية.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\nالملف `home_page.dart` يحتوي على واجهة المستخدم للصفحة الجذرية التي يراها\nالمستخدم عند إطلاق التطبيق.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\nتمثيل مبسط لشجرة Widget الخاصة بـ `HomePage` هو:\n\n<HomePageTreeSnippet />\n\nتقدم `HomePage` نسخة من `HomeCubit` إلى `HomeView`. يستخدم `HomeView` الدالة\n`context.select` لإعادة البناء بشكل انتقائي كلما تغير التبويب. هذا يسمح لنا\nباختبار Widget `HomeView` بسهولة عن طريق توفير نسخة Mock من `HomeCubit` وتمثيل\nالحالة (stubbing state).\n\nشريط التطبيق السفلي (`BottomAppBar`) يحتوي على أزرار `HomeTabButton` التي تستدعي\n`setTab` على `HomeCubit`. يتم الحصول على نسخة الـ cubit عبر `context.read` ويتم\nاستدعاء الدالة المناسبة على هذه النسخة.\n\n:::caution\n\n`context.read` لا يستمع للتغييرات، يستخدم فقط للوصول إلى `HomeCubit` واستدعاء\n`setTab`.\n\n:::\n\n### ملخص المهام (TodosOverview)\n\nميزة ملخص المهام تتيح للمستخدمين إدارة مهامهم من خلال الإنشاء، التعديل، الحذف،\nوالتصفية.\n\n#### أحداث TodosOverviewEvent\n\nلننشئ الملف `todos_overview/bloc/todos_overview_event.dart` ونعرف فيه الأحداث.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: هذا هو الحدث الذي يُطلق عند بدء التشغيل.\n  عند استقباله، يقوم الـ bloc بالاشتراك في تيار المهام من `TodosRepository`.\n- `TodosOverviewTodoDeleted`: هذا الحدث يحذف مهمة.\n- `TodosOverviewTodoCompletionToggled`: يقوم بتبديل حالة إكمال المهمة.\n- `TodosOverviewToggleAllRequested`: يبدل حالة إكمال كل المهام.\n- `TodosOverviewClearCompletedRequested`: يحذف كل المهام المكتملة.\n- `TodosOverviewUndoDeletionRequested`: يتراجع عن حذف مهمة، على سبيل المثال في\n  حالة الحذف بالخطأ.\n- `TodosOverviewFilterChanged`: يأخذ مرشح العرض `TodosViewFilter` كوسيط ويغير\n  العرض بتطبيق الفلتر.\n\n#### الحالة TodosOverviewState\n\nلننشئ الملف `todos_overview/bloc/todos_overview_state.dart` ونعرف فيه الحالة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState` يحتفظ بقائمة المهام، المرشح النشط، المهمة المحذوفة الأخيرة\n(`lastDeletedTodo`)، والحالة العامة.\n\n:::note\n\nإلى جانب الـ getters والـ setters الافتراضية، لدينا getter مخصص يسمى\n`filteredTodos`. يستخدم واجهة المستخدم `BlocBuilder` للوصول إما إلى\n`state.filteredTodos` أو `state.todos`.\n\n:::\n\n#### الـ Bloc - TodosOverviewBloc\n\nلننشئ الملف `todos_overview/bloc/todos_overview_bloc.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nالـ bloc لا ينشئ نسخة من `TodosRepository` داخليًا. بدلاً من ذلك، يعتمد على حقن\nنسخة الـ repository عبر المُنشئ (constructor).\n\n:::\n\n##### عند طلب الاشتراك (onSubscriptionRequested)\n\nعند إضافة حدث `TodosOverviewSubscriptionRequested`، يبدأ الـ bloc بإصدار حالة\n`loading`. في المقابل، يمكن لواجهة المستخدم عرض مؤشر تحميل.\n\nبعد ذلك، نستخدم `emit.forEach<List<Todo>>( ... )` التي تنشئ اشتراكًا على تيار\nالمهام من `TodosRepository`.\n\n:::caution\n\n`emit.forEach()` ليست نفسها `forEach()` المستخدمة في القوائم. هذه الدالة تسمح\nللـ bloc بالاشتراك في `Stream` وإصدار حالة جديدة مع كل تحديث.\n\n:::\n\n:::note\n\n`stream.listen` لا يتم استدعاؤه مباشرةً في هذا الدليل. استخدام\n`await emit.forEach()` هو نمط أحدث للاشتراك في تيارات يسمح للـ bloc بإدارة\nالاشتراك داخليًا.\n\n:::\n\nبمجرد التعامل مع الاشتراك، سيتم التعامل مع الأحداث الأخرى مثل إضافة، تعديل، وحذف\nالمهام.\n\n##### عند حفظ المهمة (onTodoSaved)\n\n`_onTodoSaved` ببساطة يستدعي `_todosRepository.saveTodo(event.todo)`.\n\n:::note\n\n`emit` لا يتم استدعاؤه من داخل `onTodoSaved` والعديد من معالجات الأحداث الأخرى.\nبدلاً من ذلك، تقوم هذه المعالجات بإعلام الـ repository الذي يصدر قائمة محدثة عبر\nتيار المهام. راجع قسم [تدفق البيانات](#تدفق-البيانات-data-flow) لمزيد من\nالتفاصيل.\n\n:::\n\n##### التراجع (Undo)\n\nميزة التراجع تتيح للمستخدمين استعادة آخر عنصر تم حذفه.\n\n`_onTodoDeleted` يقوم بشيئين: أولاً، يصدر حالة جديدة مع المهمة التي سيتم حذفها.\nثم يحذف المهمة عن طريق استدعاء الـ repository.\n\n`_onUndoDeletionRequested` يُنفذ عند استقبال حدث طلب التراجع من واجهة المستخدم.\n\nيقوم `_onUndoDeletionRequested` بما يلي:\n\n- يحفظ مؤقتًا نسخة من المهمة المحذوفة الأخيرة.\n- يحدث الحالة بإزالة `lastDeletedTodo`.\n- يعكس عملية الحذف.\n\n##### التصفية (Filtering)\n\n`_onFilterChanged` يصدر حالة جديدة مع الفلتر الجديد.\n\n#### النماذج (Models)\n\nيوجد ملف نموذج واحد يتعامل مع تصفية العرض.\n\n`todos_view_filter.dart` هو enum يمثل مرشحات العرض الثلاثة بالإضافة إلى الدوال\nلتطبيق الفلتر.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart` هو ملف تجميعي لإعادة التصدير.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\nبعد ذلك، لنلقي نظرة على `TodosOverviewPage`.\n\n#### صفحة ملخص المهام TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\nتمثيل مبسط لشجرة Widget الخاصة بـ `TodosOverviewPage` هو:\n\n<TodosOverviewPageTreeSnippet />\n\nتمامًا مثل ميزة الصفحة الرئيسية، تقوم `TodosOverviewPage` بتوفير نسخة من\n`TodosOverviewBloc` للشجرة الفرعية عبر `BlocProvider<TodosOverviewBloc>`، مما\nيقتصر نطاق هذا الـ bloc على Widgets الموجودة تحت `TodosOverviewPage` فقط.\n\nهناك ثلاث ويدجتات تستمع للتغييرات في `TodosOverviewBloc`.\n\n1. الأولى هي `BlocListener` يستمع للأخطاء. الدالة `listener` تُستدعى فقط حين\n   يعيد `listenWhen` القيمة `true`. إذا كانت الحالة\n   `TodosOverviewStatus.failure`، يتم عرض `SnackBar`.\n\n2. لدينا أيضًا `BlocListener` آخر يستمع لعمليات الحذف. عند حذف مهمة، يعرض\n   `SnackBar` مع زر التراجع. إذا ضغط المستخدم على التراجع، يتم إضافة حدث\n   `TodosOverviewUndoDeletionRequested` إلى الـ bloc.\n\n3. وأخيرًا نستخدم `BlocBuilder` لبناء قائمة تظهر المهام.\n\nشريط التطبيق (`AppBar`) يحتوي على عمليتين كرُفع قوائم (dropdowns) للتصفية\nوالتعديل على المهام.\n\n:::note\n\nالأحداث `TodosOverviewTodoCompletionToggled` و `TodosOverviewTodoDeleted` تمت\nإضافتها إلى الـ bloc باستخدام `context.read`.\n\n:::\n\nالملف `view.dart` هو ملف تجميعي يصدر `todos_overview_page.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Widgets\n\n`widgets.dart` هو ملف تجميعي آخر يصدر جميع المكونات المستخدمة ضمن ميزة\n`todos_overview`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart` هو `ListTile` لكل عنصر مهمة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart` يعرض خيارين للتحكم بالمهام:\n\n- `toggleAll`\n- `clearCompleted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart` يعرض ثلاثة خيارات للتصفية:\n\n- `all`\n- `activeOnly`\n- `completedOnly`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### الإحصائيات (Stats)\n\nميزة الإحصائيات تعرض معلومات عن المهام النشطة والمكتملة.\n\n#### StatsState\n\n`StatsState` يحتفظ بمعلومات ملخصة وحالة `StatsStatus` الحالية.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### StatsEvent\n\nلدى `StatsEvent` حدث وحيد هو `StatsSubscriptionRequested`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### StatsBloc\n\n`StatsBloc` يعتمد على `TodosRepository` كما هو الحال مع `TodosOverviewBloc`.\nيشترك في تيار المهام عبر `_todosRepository.getTodos`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### واجهة الإحصائيات (Stats View)\n\n`view.dart` هو الملف التجميعي الخاص بصفحة الإحصائيات `stats_page`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart` يحتوي على واجهة المستخدم للصفحة التي تعرض إحصاءات المهام.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\nتمثيل مبسط لشجرة Widget الخاصة بـ `StatsPage` هو:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\nكل من `TodosOverviewBloc` و `StatsBloc` يتواصلان مع `TodosRepository`, لكن من\nالمهم ملاحظة أنه لا يوجد تواصل مباشر بين الـ blocs. راجع قسم\n[تدفق البيانات](#تدفق-البيانات-data-flow) لمزيد من التفاصيل.\n\n:::\n\n### تعديل المهمة (EditTodo)\n\nميزة `EditTodo` تتيح للمستخدمين تعديل مهمة موجودة وحفظ التغييرات.\n\n#### EditTodoState\n\n`EditTodoState` يحتفظ بالمعلومات اللازمة أثناء تعديل مهمة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### أحداث EditTodoEvent\n\nالأحداث التي يستجيب لها الـ bloc هي:\n\n- `EditTodoTitleChanged`\n- `EditTodoDescriptionChanged`\n- `EditTodoSubmitted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc` يعتمد على `TodosRepository` مثل `TodosOverviewBloc` و\n`StatsBloc`.\n\n:::caution\n\nعلى عكس الـ blocs الأخرى، `EditTodoBloc` لا يشترك في\n`_todosRepository.getTodos`. إنه bloc \"للكتابة فقط\" يعني أنه لا يحتاج لقراءة\nالمعلومات من الـ repository.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### تدفق البيانات (Data Flow)\n\nرغم وجود العديد من الميزات التي تعتمد على نفس قائمة المهام، لا يوجد تواصل مباشر\nبين الـ blocs. بدلاً من ذلك، كل ميزة مستقلة وتعتمد على `TodosRepository`\nللاستماع للتغييرات في قائمة المهام، بالإضافة لأداء التحديثات عليها.\n\nعلى سبيل المثال، ميزة `EditTodos` لا تعرف شيئًا عن ميزات `TodosOverview` أو\n`Stats`.\n\nعند إرسال واجهة المستخدم حدث `EditTodoSubmitted`:\n\n- `EditTodoBloc` يتولى منطق الأعمال بتحديث الـ `TodosRepository`.\n- `TodosRepository` يُبلغ كل من `TodosOverviewBloc` و `StatsBloc`.\n- `TodosOverviewBloc` و `StatsBloc` يُبلغان الواجهة التي تقوم بالتحديث وفقًا\n  للحالة الجديدة.\n\n#### صفحة تعديل المهمة (EditTodoPage)\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\nتمامًا مثل الميزات السابقة، توفر `EditTodosPage` نسخة من `EditTodosBloc` عبر\n`BlocProvider`. على عكس الميزات الأخرى، صفحة `EditTodosPage` هي مسار منفصل ولهذا\nتوفر طريقة `static` اسمها `route`. هذا يسهل دفع الصفحة على مكدس التنقل عبر\n`Navigator.of(context).push(...)`.\n\nتمثيل مبسط لشجرة Widget الخاصة بـ `EditTodosPage` هو:\n\n<EditTodosPageTreeSnippet />\n\n## الملخص\n\nهذا كل شيء، لقد أكملنا الدرس التعليمي! 🎉\n\nيمكنك العثور على الأكواد البرمجية المصدرية الكاملة لهذا المثال، بما في ذلك\nاختبارات الوحدة واختبارات Widgets،\n[هنا](https://github.com/felangel/bloc/tree/master/examples/flutter_todos).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: تطبيق الطقس Flutter\ndescription: دليل متعمّق لبناء تطبيق طقس في Flutter باستخدام مكتبة Bloc.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nفي هذا الدرس، سنبني تطبيق طقس في Flutter يوضح كيفية إدارة عدة cubits لتنفيذ\nالتخصيص الديناميكي للثيمات، والسحب للتحديث، والعديد من الميزات الأخرى. سيعتمد\nتطبيق الطقس على جلب بيانات الطقس الحية من واجهة برمجة التطبيقات العامة\nOpenMeteo، وسنشرح كيفية فصل التطبيق إلى طبقات (البيانات، المستودع، منطق الأعمال،\nوطبقة العرض).\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## متطلبات المشروع\n\nيجب أن يتيح تطبيقنا للمستخدمين ما يلي:\n\n- البحث عن مدينة من خلال صفحة مخصصة للبحث\n- عرض تمثيل بصري مريح لبيانات الطقس التي يتم جلبها من  \n  [Open Meteo API](https://open-meteo.com)\n- تغيير وحدات القياس المعروضة (متري مقابل إمبيريال)\n\nبالإضافة إلى ذلك،\n\n- يجب أن يعكس موضوع التطبيق حالة الطقس للمدينة المختارة\n- يجب أن تستمر حالة التطبيق عبر الجلسات: أي يجب أن يتذكر التطبيق حالته بعد\n  الإغلاق وإعادة الفتح (باستخدام  \n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc))\n\n## المفاهيم الرئيسية\n\n- مراقبة تغييرات الحالة باستخدام [BlocObserver](/ar/bloc-concepts#blocobserver).\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter\n  توفر bloc لأبنائها.\n- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter\n  تتولى إعادة البناء استجابةً للحالات الجديدة.\n- تجنب عمليات إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي Widget\n  في Flutter توفر repository لأبنائها.\n- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي Widget في Flutter\n  تستدعي كود المستمع استجابةً لتغيرات الحالة في الـ bloc.\n- [MultiBlocProvider](/ar/flutter-bloc-concepts#multiblocprovider)، وهي Widget\n  في Flutter التي تجمع عدة `BlocProvider` في واحد.\n- [BlocConsumer](/ar/flutter-bloc-concepts#blocconsumer)، وهي Widget في Flutter\n  توفر builder و listener للاشتراك في حالات جديدة.\n- [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  لإدارة الحالة وحفظها بشكل مستمر.\n\n## الإعداد\n\nلبداية العمل، أنشئ مشروع Flutter جديدًا.\n\n<FlutterCreateSnippet />\n\n### هيكل المشروع\n\nسيتكون تطبيقنا من ميزات منفصلة في مجلدات مخصصة لها. هذا يسمح لنا بالتوسع مع\nزيادة عدد الميزات ويسمح للمطورين بالعمل على ميزات مختلفة بشكل متوازي.\n\nيمكن تقسيم تطبيقنا إلى أربع ميزات رئيسية: **search, settings, theme, weather**.\nدعنا ننشئ هذه المجلدات.\n\n<FeatureTreeSnippet />\n\n### البنية المعمارية\n\nوفقًا لإرشادات [bloc architecture](/ar/architecture)، سيتكون تطبيقنا من عدة\nطبقات.\n\nفي هذا الدرس، تقوم كل طبقة بالمهام التالية:\n\n- **Data**: جلب بيانات الطقس الخام من واجهة برمجة التطبيقات (API)\n- **Repository**: تجريد طبقة البيانات وتوفير نماذج نطاق العمل (domain models)\n  ليتم استهلاكها داخل التطبيق\n- **Business Logic**: إدارة حالة كل ميزة (معلومات الوحدة، تفاصيل المدينة،\n  السمات، إلخ)\n- **Presentation**: عرض معلومات الطقس وجمع مدخلات المستخدمين (صفحة الإعدادات،\n  صفحة البحث، إلخ)\n\n## طبقة البيانات\n\nفي هذا التطبيق، سنقوم بالوصول إلى  \n[Open Meteo API](https://open-meteo.com).\n\nسنركز على نقطتي نهاية (Endpoints) الأساسية:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` للحصول\n  على  \n  موقع جغرافي لاسم مدينة معين\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true`  \n  للحصول\n  على حالة الطقس لموقع محدد\n\nافتح  \n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)  \nفي متصفحك للاطلاع على الاستجابة لمدينة شيكاغو. سنستخدم قيمتي  \n`latitude` و `longitude` في الاستجابة للوصول إلى نقطة نهاية الطقس.\n\nقيم `latitude`/`longitude` لشيكاغو هي `41.85003`/`-87.65005`. يمكنك التوجه إلى  \n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)  \nفي متصفحك لترى الاستجابة الخاصة بحالة الطقس في شيكاغو والتي تحتوي على جميع\nالبيانات التي سنحتاجها لتطبيقنا.\n\n### عميل واجهة برمجة التطبيقات OpenMeteo API\n\nعميل واجهة برمجة التطبيقات OpenMeteo مستقل عن تطبيقنا. لذلك، سنقوم بإنشائه كحزمة\nداخلية (ويمكننا حتى نشره على  \n[pub.dev](https://pub.dev)). بعد ذلك، نستطيع استخدام الحزمة بإضافتها إلى\n`pubspec.yaml` في طبقة repository، التي ستتولى طلبات البيانات لتطبيق الطقس\nالرئيسي لدينا.\n\nأنشئ مجلدًا جديدًا على مستوى المشروع باسم `packages`. هذا المجلد سيحتوي جميع\nحزمنا الداخلية.\n\nداخل هذا المجلد، نفذ أمر `flutter create` المدمج لإنشاء حزمة جديدة باسم\n`open_meteo_api` لعميل واجهة برمجة التطبيقات.\n\n<FlutterCreateApiClientSnippet />\n\n### نموذج بيانات الطقس\n\nبعد ذلك، لنقم بإنشاء ملفي `location.dart` و `weather.dart` الذي سيحتويان النماذج\n(Models) لاستجابات نقاط نهاية الـ API الخاصة بالموقع والطقس.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### نموذج الموقع\n\nيجب أن يخزن نموذج `location.dart` البيانات التي تعود من API الموقع، والتي تبدو\nكما يلي:\n\n<LocationJsonSnippet />\n\nوهذا ملف `location.dart` قيد التقدم والذي يخزن الاستجابة أعلاه:\n\n<LocationDartSnippet />\n\n#### نموذج الطقس\n\nبعدها، ننتقل إلى العمل على `weather.dart`. يجب أن يخزن نموذج الطقس البيانات التي\nتعود من API الطقس، الواردة كما يلي:\n\n<WeatherJsonSnippet />\n\nوهذا ملف `weather.dart` قيد التقدم الذي يخزن الاستجابة أعلاه:\n\n<WeatherDartSnippet />\n\n### ملفات البرميل (Barrel Files)\n\nوأثناء تواجدنا هنا، دعونا نُنشئ بسرعة  \n[barrel file](https://adrianfaciu.dev/posts/barrel-files/)  \nلتنظيم وتقليل تعقيد الاستيرادات لدينا مستقبلاً.\n\nأنشئ ملف `models.dart` كملف برميل (barrel) وصدّر فيه النموذجان:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\nلنقم أيضاً بإنشاء ملف برميل على مستوى الحزمة باسم `open_meteo_api.dart`\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\nفي الملف الأعلى مستوى `open_meteo_api.dart`، قمنا بتصدير النماذج:\n\n<OpenMeteoLibrarySnippet />\n\n### الإعداد\n\nنحتاج لأن نتمكن من  \n[التسلسل والتفكيك التسلسلي (serialization/deserialization)](https://en.wikipedia.org/wiki/Serialization)  \nلنماذجنا لكي نتمكن من التعامل مع بيانات الـ API. للقيام بذلك، سنضيف طرق `toJson`\nو `fromJson` لنماذجنا.\n\nوبالإضافة لذلك، نحتاج طريقة لإجراء  \n[طلبات الشبكة عبر HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)  \nلجلب البيانات من API. لحسن الحظ، هناك العديد من الحزم الشعبية للقيام بهذه\nالمهمة.\n\nسنستخدم حزم:  \n[json_annotation](https://pub.dev/packages/json_annotation)،  \n[json_serializable](https://pub.dev/packages/json_serializable)، و  \n[build_runner](https://pub.dev/packages/build_runner)  \nلتوليد طرق `toJson` و`fromJson` تلقائيًا.\n\nوفي خطوة مستقبلية، سنستخدم أيضاً حزمة  \n[http](https://pub.dev/packages/http)  \nلإرسال طلبات الشبكة لواجهة الطقس ليتمكن تطبيقنا من عرض بيانات الطقس الحالية.\n\nلنُضِف هذه التبعيات إلى `pubspec.yaml`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\nتذكر تشغيل الأمر `flutter pub get` بعد إضافة التبعيات.\n\n:::\n\n### (إعادة)التسلسل Serialization/Deserialization\n\nلكي يعمل توليد الأكواد، نحتاج إلى تمييز الكود لدينا باستخدام ما يلي:\n\n- `@JsonSerializable` لتعليم الكلاسات القابلة للتسلسل\n- `@JsonKey` لتوفير تمثيلات نصية لأسماء الحقول\n- `@JsonValue` لتوفير تمثيلات نصية لقيم الحقول\n- تنفيذ `JSONConverter` لتحويل تمثيلات الكائن إلى تمثيلات JSON\n\nلكل ملف نحتاج أيضاً إلى:\n\n- استيراد `json_annotation`\n- تضمين الكود المُولد باستخدام كلمة المفتاح  \n  [part](https://dart.dev/tools/pub/create-packages#organizing-a-package)\n- تضمين طرق `fromJson` لعملية التفكيك التسلسلي (deserialization)\n\n#### نموذج الموقع\n\nهذا هو ملف نموذج `location.dart` كاملاً:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### نموذج الطقس\n\nوهذا هو ملف نموذج `weather.dart` كاملاً:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### إنشاء ملف البناء (Build File)\n\nفي مجلد `open_meteo_api`، أنشئ ملفًا باسم `build.yaml`. هدف هذا الملف هو التعامل\nمع الفروقات في تسميات الحقول داخل  \n`json_serializable`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### توليد الأكواد (Code Generation)\n\nلنستخدم `build_runner` لتوليد الأكواد المطلوبة.\n\n<BuildRunnerBuildSnippet />\n\nمن المفترض أن يولّد `build_runner` الملفات `location.g.dart` و `weather.g.dart`.\n\n### عميل OpenMeteo API\n\nدعونا ننشئ عميل الـ API الخاص بنا في ملف `open_meteo_api_client.dart` داخل مجلد\n`src`. يجب أن تبدو بنية مشروعنا كما يلي:\n\n<OpenMeteoApiClientTreeSnippet />\n\nالآن يمكننا استخدام حزمة  \n[http](https://pub.dev/packages/http)  \nالتي أضفناها سابقًا إلى ملف `pubspec.yaml` لإجراء طلبات HTTP إلى واجهة الطقس\nواستخدام هذه المعلومات في تطبيقنا.\n\nسيُوفر عميل API الخاص بنا طريقتين:\n\n- `locationSearch` التي تعيد `Future<Location>`\n- `getWeather` التي تعيد `Future<Weather>`\n\n#### البحث عن الموقع\n\nتصل طريقة `locationSearch` إلى API الموقع وتطرح أخطاء من النوع  \n`LocationRequestFailure` عند الحاجة. الطرح النهائي للطريقة كما يلي:\n\n<LocationSearchMethodSnippet />\n\n#### الحصول على الطقس\n\nبنفس الطريقة، تصل طريقة `getWeather` إلى API الطقس وتطرح أخطاء من النوع  \n`WeatherRequestFailure`. الشكل النهائي للطريقة كالتالي:\n\n<GetWeatherMethodSnippet />\n\nالملف المكتمل يبدو بالشكل التالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### تحديثات ملفات البرميل\n\nلنُنهي هذه الحزمة بإضافة عميل واجهة برمجة التطبيقات إلى ملف البرميل.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### اختبارات الوحدة (Unit Tests)\n\nمن المهم بشكل خاص كتابة اختبارات وحدة لطبقة البيانات لأنها أساس تطبيقنا.\nاختبارات الوحدة ستمنحنا الثقة أن الحزمة تعمل كما هو متوقع.\n\n#### الإعداد\n\nفي السابق، أضفنا حزمة  \n[test](https://pub.dev/packages/test)  \nإلى ملف pubspec.yaml والتي تتيح كتابة اختبارات الوحدة بسهولة.\n\nسوف نقوم بإنشاء ملف اختبار لعميل API بالإضافة إلى ملفات اختبار لكل نموذج من\nالنموذجين.\n\n#### اختبارات الموقع\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### اختبارات الطقس\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### اختبارات عميل API\n\nبعدها، لنختبر عميل API الخاص بنا. يجب أن نتحقق من أن عميل API يتعامل بشكل صحيح\nمع كل طلبات الـ API، بما في ذلك السيناريوهات الحدية (Edge Cases).\n\n:::note\n\nلا نرغب في أن تستخدم اختباراتنا طلبات API حقيقية لأن هدفنا هو اختبار منطق عميل\nالـ API (بما في ذلك جميع السيناريوهات الحدية) وليس الـ API نفسه. ولضمان بيئة\nاختبار متسقة ومتحكم بها، سنستخدم  \n[mocktail](https://github.com/felangel/mocktail)  \n(والتي أضفناها مسبقًا في `pubspec.yaml`) لمحاكاة عميل الـ `http`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### تغطية الاختبارات (Test Coverage)\n\nوأخيرًا، دعونا نجمع تغطية الاختبارات لنتأكد من أننا غطينا كل سطر من الكود على\nالأقل بحالة اختبار واحدة.\n\n<FlutterTestCoverageSnippet />\n\n## طبقة المستودع (Repository Layer)\n\nالهدف من طبقة المستودع هو تجريد طبقة البيانات وتسهيل الاتصال مع طبقة الـ bloc.\nعند القيام بذلك، يعتمد بقية الكود الأساسي لدينا فقط على الدوال المعروضة من خلال\nطبقة المستودع بدلًا من الاعتماد على تنفيذات مزود البيانات المحددة. هذا يسمح لنا\nبتغيير مزودي البيانات دون التأثير على أي كود في مستوى التطبيق. على سبيل المثال،\nإذا قررنا الانتقال بعيدًا عن واجهة برمجة التطبيقات الخاصة بالطقس التي نستخدمها\nحالياً، يجب أن نتمكن من إنشاء عميل API جديد واستبداله دون الحاجة لتعديل واجهة\nAPI العامة لطبقة المستودع أو طبقات التطبيق.\n\n### الإعداد\n\nداخل مجلد الحزم `packages`، نفذ الأمر التالي:\n\n<FlutterCreateRepositorySnippet />\n\nسنستخدم نفس الحزم الموجودة في حزمة `open_meteo_api` بما في ذلك حزمة\n`open_meteo_api` من الخطوة السابقة. حدّث ملف `pubspec.yaml` ثم نفذ الأمر\n`flutter pub get`.\n\n:::note\n\nنستخدم مسار `path` لتحديد موقع `open_meteo_api` مما يسمح لنا بالتعامل معها كما\nلو كانت حزمة خارجية من `pub.dev`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### نماذج Weather Repository\n\nسنقوم بإنشاء ملف جديد `weather.dart` لتعريف نموذج الطقس الخاص بالمجال\n(domain-specific). هذا النموذج سيحتوي فقط على البيانات ذات الصلة بحالات العمل\nلدينا — بمعنى آخر، يجب أن يكون مفصولًا تمامًا عن عميل الـ API وصيغة البيانات\nالخام. كما هو معتاد، سننشئ أيضًا ملف برميل `models.dart` لتجميع النماذج.\n\n<RepositoryModelsBarrelTreeSnippet />\n\nهذه المرة، سيحتوي نموذج الطقس لدينا فقط على الخصائص\n`location, temperature, condition`. سنستمر أيضًا في ترميز كودنا للسماح بعمليات\nالتسلسل والتسلسل العكسي (serialization & deserialization).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\nقم بتحديث ملف البرميل الذي أنشأناه سابقًا ليشمل النماذج.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### إنشاء ملف Build\n\nكما في السابق، نحتاج إلى إنشاء ملف `build.yaml` بالمحتويات التالية:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### توليد الكود\n\nكما فعلنا في السابق، نفذ الأمر التالي لتوليد تنفيذ عمليات (de)serialization.\n\n<BuildRunnerBuildSnippet />\n\n#### ملف البرميل (Barrel File)\n\nلننشئ أيضًا ملف برميل على مستوى الحزمة باسم  \n`packages/weather_repository/lib/weather_repository.dart` لتصدير النماذج:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\nالهدف الأساسي من `WeatherRepository` هو توفير واجهة تجريدية لمزود البيانات. في\nهذه الحالة، ستعتمد `WeatherRepository` على `WeatherApiClient` وستعرض دالة عامة\nواحدة فقط، وهي `getWeather(String city)`.\n\n:::note\n\nمستهلكو `WeatherRepository` غير مطلعين على تفاصيل التنفيذ الداخلية مثل حقيقة\nإجراء طلبين شبكيين إلى واجهة الطقس. الهدف من `WeatherRepository` هو فصل الـ\n\"ماذا\" عن الـ \"كيف\" — بمعنى أننا نريد طريقة لجلب الطقس لمدينة معينة، لكننا لا\nنهتم بكيفية أو من أين تأتي البيانات.\n\n:::\n\n#### الإعداد\n\nلنقم بإنشاء ملف `weather_repository.dart` داخل مجلد `src` في الحزمة، ونعمل على\nتنفيذ الريبو.\n\nالدالة الرئيسية التي سنركز عليها هي `getWeather(String city)`. يمكننا تنفيذها\nباستخدام مكالمتين إلى عميل الـ API كما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### ملف البرميل (Barrel File)\n\nقم بتحديث ملف البرميل الذي أنشأناه سابقًا.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### اختبارات الوحدة\n\nتمامًا كما هو الحال مع طبقة البيانات، من الضروري اختبار طبقة المستودع لضمان صحة\nمنطق المجال. لاختبار `WeatherRepository`، سنستخدم مكتبة\n[mocktail](https://github.com/felangel/mocktail). سنقوم بمحاكاة عميل الـ API\nالأساسي لاختبار منطق `WeatherRepository` ضمن بيئة مختبرية ومعزولة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## طبقة منطق الأعمال\n\nفي طبقة منطق الأعمال، سنستخدم نموذج مجال الطقس من  \n`WeatherRepository`  \nوسنعرض نموذجًا خاصًا بالمزايا على مستوى الميزة ليتم عرضه للمستخدم عبر واجهة\nالمستخدم.\n\n:::note\n\nهذا هو النوع الثالث المختلف من نموذج الطقس الذي نطبقه. في عميل API، كان نموذج\nالطقس لدينا يحتوي على كل المعلومات التي ترجعها الـ API. في طبقة المستودع، كان\nنموذج الطقس يحتوي فقط على النموذج المجرد بناءً على حالة العمل/الأعمال لدينا. في\nهذه الطبقة، سيكون نموذج الطقس لدينا يحتوي فقط على المعلومات ذات الصلة اللازمة\nخصيصًا لمجموعة الميزات الحالية.\n\n:::\n\n### الإعداد\n\nبما أن طبقة منطق الأعمال تقع داخل التطبيق الرئيسي، نحتاج إلى تعديل ملف  \n`pubspec.yaml`  \nلمشروع `flutter_weather` بأكمله وإضافة جميع الحزم التي سنستخدمها.\n\n- استخدام [equatable](https://pub.dev/packages/equatable) يتيح لمثيلات حالة\n  التطبيق إمكانية المقارنة باستخدام معامل المساواة `==`. في الخلفية، يقوم bloc\n  بمقارنة الحالات ليرى إذا ما كانت متساوية، وإذا لم تكن كذلك، سيؤدي ذلك إلى\n  إعادة بناء. هذا يضمن أن شجرة Widget ستُعاد بناؤها فقط عند الضرورة للحفاظ على\n  الأداء سريعًا ومتجاوبًا.\n- يمكننا تحسين واجهة المستخدم باستخدام حزمة  \n  [google_fonts](https://pub.dev/packages/google_fonts).\n- توفر [HydratedBloc](https://pub.dev/packages/hydrated_bloc) إمكانية حفظ حالة\n  التطبيق عند إغلاقه وإعادة فتحه.\n- سنقوم بإضافة حزمة `weather_repository` التي أنشأناها للتو لتمكيننا من جلب\n  بيانات الطقس الحالية!\n\nلأغراض الاختبار، سنضيف الحزم المعتادة `test`، بالإضافة إلى `mocktail` لتقليد\nالتبعيات، و  \n[bloc_test](https://pub.dev/packages/bloc_test)  \nلتسهيل اختبار وحدات منطق الأعمال أو الـ blocs!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nبعدها، سنعمل على طبقة التطبيق ضمن دليل ميزة `weather`.\n\n### نموذج الطقس\n\nالهدف من نموذج الطقس هو متابعة بيانات الطقس التي يعرضها تطبيقنا، بالإضافة إلى\nإعدادات درجة الحرارة (سلسيوس أو فهرنهايت).\n\nأنشئ الملف `flutter_weather/lib/weather/models/weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### إنشاء ملف البناء\n\nأنشئ ملف `build.yaml` لطبقة منطق الأعمال.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### توليد الكود\n\nشغّل الأمر `build_runner` لتوليد تطبيقات التسلسل العكسي (de)serialization.\n\n<BuildRunnerBuildSnippet />\n\n### ملف البرميل (Barrel File)\n\nدعنا نصدر نماذجنا من ملف البرميل  \n(`flutter_weather/lib/weather/models/models.dart`):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\nثم لننشئ ملف برميل رئيسي لميزة الطقس  \n(`flutter_weather/lib/weather/weather.dart`);\n\n<WeatherBarrelDartSnippet />\n\n### الطقس\n\nسنستخدم `HydratedCubit` لتمكين تطبيقنا من حفظ حالة التطبيق واستعادتها، حتى بعد\nإغلاق التطبيق وإعادة فتحه.\n\n:::note\n\n`HydratedCubit` هو امتداد لـ `Cubit` يتولى حفظ واستعادة الحالة عبر الجلسات.\n\n:::\n\n#### حالة الطقس\n\nباستخدام  \n[Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)  \nأو  \n[Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)  \nافتح القائمة السياقية على مجلد `weather` وأنشئ cubit جديد اسمه `Weather`. يجب أن\nيكون هيكل المشروع كما يلي:\n\n<WeatherCubitTreeSnippet />\n\nهناك أربع حالات يمكن أن يكون عليها تطبيق الطقس:\n\n- `initial` قبل تحميل أي شيء\n- `loading` أثناء طلب البيانات من الـ API\n- `success` إذا نجح طلب الـ API\n- `failure` إذا فشل طلب الـ API\n\nسيُمثل هذا الـ enum المسمى `WeatherStatus` الحالات السابقة.\n\nيجب أن تبدو حالة الطقس الكاملة كما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\nبعد تعريف `WeatherState`، فلنكتب `WeatherCubit` الذي يعرض الطرق التالية:\n\n- `fetchWeather(String? city)` يستخدم مستودع الطقس لمحاولة جلب كائن الطقس\n  للمدينة المعطاة\n- `refreshWeather()` يجلب كائن طقس جديد باستخدام مستودع الطقس اعتمادًا على\n  الحالة الحالية\n- `toggleUnits()` يبدّل الوحدات بين سلسيوس وفهرنهايت\n- `fromJson(Map<String, dynamic> json)`، `toJson(WeatherState state)` تُستخدم\n  للحفظ والاستعادة\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\nلا تنسَ توليد كود (de)serialization عبر:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### اختبارات الوحدة\n\nمثل طبقات البيانات والمستودع، من الضروري اختبار وحدة طبقة منطق الأعمال لضمان أن\nمنطق الميزة يعمل كما هو متوقع. سنعتمد على  \n[bloc_test](https://pub.dev/packages/bloc_test)  \nإلى جانب `mocktail` و`test`.\n\nدعونا نضيف الحزم `test`، `bloc_test`، و`mocktail` إلى قسم  \n`dev_dependencies`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nتتيح حزمة [bloc_test](https://pub.dev/packages/bloc_test) تجهيز الـ blocs بسهولة\nللاختبار، مع التعامل مع تغيرات الحالة والتحقق من النتائج بطريقة موحدة.\n\n:::\n\n#### اختبارات Weather Cubit\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## طبقة العرض\n\n### صفحة الطقس\n\nسنبدأ بـ `WeatherPage` التي تستخدم `BlocProvider` لتوفير instance من\n`WeatherCubit` لشجرة Widget.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\nستلاحظ أن هذه الصفحة تعتمد على Widget `SettingsPage` و`SearchPage`، اللتين\nسننشئهما بعد ذلك.\n\n### صفحة الإعدادات\n\nتتيح صفحة الإعدادات للمستخدمين تحديث تفضيلاتهم لوحدات درجة الحرارة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### صفحة البحث\n\nتمكن صفحة البحث المستخدمين من إدخال اسم المدينة المرغوبة وتعيد نتيجة البحث إلى\nالمسار السابق عبر `Navigator.of(context).pop`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Widgets الطقس\n\nسيعرض التطبيق شاشات مختلفة اعتمادًا على الحالات الأربع المحتملة لـ\n`WeatherCubit`.\n\n#### حالة WeatherEmpty\n\nتعرض هذه الشاشة عندما لا تكون هناك بيانات للعرض لأن المستخدم لم يختَر مدينة بعد.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### حالة WeatherError\n\nتعرض هذه الشاشة في حال حدوث خطأ.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### حالة WeatherLoading\n\nتعرض هذه الشاشة أثناء جلب التطبيق للبيانات.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### حالة WeatherPopulated\n\nتعرض هذه الشاشة بعد أن يختار المستخدم مدينة ويتم استرجاع البيانات.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### ملف البرميل (Barrel File)\n\nلنقم بإضافة هذه الحالات إلى ملف برميل لتنظيم عمليات الاستيراد وجعلها أنظف.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### نقطة الدخول\n\nينبغي أن يقوم ملف `main.dart` بتهيئة تطبيق `WeatherApp` و`BlocObserver` (لأغراض\nالتصحيح)، بالإضافة إلى إعداد `HydratedStorage` للحفاظ على الحالة عبر الجلسات.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nWidget `app.dart` تتولى بناء عرض `WeatherPage` الذي أنشأناه سابقًا وتستخدم\n`BlocProvider` لحقن الـ `WeatherCubit`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### اختبارات Widgets\n\nتوفّر مكتبة [`bloc_test`](https://pub.dev/packages/bloc_test) أيضًا `MockBlocs`\nو `MockCubits` التي تجعل من السهل اختبار واجهة المستخدم. يمكننا محاكاة حالات الـ\ncubits المختلفة والتأكد من استجابة واجهة المستخدم بشكل صحيح.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\nنستخدم `MockWeatherCubit` مع واجهة برمجة التطبيقات `when` من مكتبة `mocktail` من\nأجل محاكاة الحالة الخاصة بـ cubit في كل حالة اختبار. هذا يسمح لنا بمحاكاة جميع\nالحالات والتحقق من أن واجهة المستخدم تتصرف بشكل صحيح تحت كل الظروف.\n\n:::\n\n## الملخص\n\nهذا كل شيء، لقد أنهينا الدرس التعليمي! 🎉\n\nيمكننا تشغيل التطبيق النهائي باستخدام الأمر `flutter run`.\n\nيمكن العثور على الأكواد البرمجية المصدرية الكاملة لهذا المثال، بما في ذلك\nاختبارات الوحدة واختبارات Widgets،\n[هنا](https://github.com/felangel/bloc/tree/master/examples/flutter_weather).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/github-search.mdx",
    "content": "---\ntitle: بحث GitHub\ndescription:\n  دليل متعمق لبناء تطبيق بحث GitHub باستخدام Flutter وAngularDart مع bloc.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nفي هذا الدليل، سنبني تطبيق بحث GitHub باستخدام Flutter وAngularDart لشرح كيف\nيمكننا مشاركة طبقة البيانات (data layer) وطبقة منطق الأعمال (business logic\nlayer) بين المشروعين.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## المواضيع الرئيسية\n\n- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهو `widget` في\n  Flutter يوفّر bloc للأبناء.\n- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهو `widget` في Flutter\n  يتولى بناء الواجهة استجابةً للحالات الجديدة.\n- استخدام Cubit بدلًا من Bloc. [ما الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc)\n- تجنب إعادة البناء غير الضرورية باستخدام\n  [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable).\n- استخدام `EventTransformer` مخصص مع\n  [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency).\n- تنفيذ طلبات الشبكة عبر حزمة `http`.\n\n## مكتبة GitHub Search المشتركة\n\nستتضمن مكتبة GitHub Search المشتركة النماذج (models)، ومزوّد البيانات (data\nprovider)، وRepository، بالإضافة إلى bloc الذي سنعيد استخدامه بين AngularDart\nوFlutter.\n\n### الإعداد\n\nسنبدأ بإنشاء مجلد جديد لتطبيقنا.\n\n<SetupSnippet />\n\n:::note\n\nسيحتوي مجلد `common_github_search` على المكتبة المشتركة.\n\n:::\n\nنحتاج إلى إنشاء `pubspec.yaml` يتضمن dependencies المطلوبة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\nأخيرًا، نحتاج إلى تثبيت dependencies.\n\n<DartPubGetSnippet />\n\nانتهى إعداد المشروع. الآن يمكننا البدء في بناء حزمة `common_github_search`.\n\n### Github Client\n\n`GithubClient` سيكون مسؤولًا عن توفير البيانات الخام من\n[GitHub API](https://developer.github.com/v3/).\n\n:::note\n\nيمكنك رؤية مثال لشكل البيانات العائدة من API\n[هنا](https://api.github.com/search/repositories?q=dartlang).\n\n:::\n\nلننشئ `github_client.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\n`GithubClient` يرسل طلب شبكة إلى Repository Search API في GitHub، ثم يحوّل\nالنتيجة إلى `SearchResult` أو `SearchResultError` ضمن `Future`.\n\n:::\n\n:::note\n\nتنفيذ `GithubClient` يعتمد على `SearchResult.fromJson`، ولم نطبّقه بعد.\n\n:::\n\nالخطوة التالية هي تعريف نموذجي `SearchResult` و`SearchResultError`.\n\n#### نموذج نتيجة البحث\n\nأنشئ `search_result.dart`، وهو يمثل قائمة `SearchResultItems` بناءً على استعلام\nالمستخدم:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\nتنفيذ `SearchResult` يعتمد على `SearchResultItem.fromJson`، ولم نطبّقه بعد.\n\n:::\n\n:::note\n\nلا نضيف الخصائص التي لن نستخدمها داخل النموذج.\n\n:::\n\n#### نموذج عنصر نتيجة البحث\n\nالآن سننشئ `search_result_item.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\nمرة أخرى، تنفيذ `SearchResultItem` يعتمد على `GithubUser.fromJson`، ولم نطبّقه\nبعد.\n\n:::\n\n#### نموذج مستخدم GitHub\n\nالآن سننشئ `github_user.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\nبهذا نكون انتهينا من تنفيذ `SearchResult` واعتمادياته، وننتقل الآن إلى\n`SearchResultError`.\n\n#### نموذج خطأ نتيجة البحث\n\nأنشئ `search_result_error.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\nانتهينا من `GithubClient`، والخطوة التالية هي `GithubCache`، والذي سيكون مسؤولًا\nعن [memoization](https://en.wikipedia.org/wiki/Memoization) كتحسين للأداء.\n\n### GitHub Cache\n\nسيكون `GithubCache` مسؤولًا عن تذكّر جميع الاستعلامات السابقة لتجنب تنفيذ طلبات\nشبكة غير ضرورية إلى GitHub API. هذا يساعد أيضًا في تحسين أداء التطبيق.\n\nأنشئ `github_cache.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\nالآن أصبحنا جاهزين لإنشاء `GithubRepository`.\n\n### GitHub Repository\n\n`GithubRepository` مسؤول عن إنشاء طبقة تجريد (abstraction) بين طبقة البيانات\n(`GithubClient`) وطبقة منطق الأعمال (`Bloc`). وهنا أيضًا سنستخدم `GithubCache`.\n\nأنشئ `github_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\n`GithubRepository` يعتمد على `GithubCache` و`GithubClient`، ويخفي تفاصيل التنفيذ\nالداخلية. التطبيق لا يحتاج لمعرفة كيفية جلب البيانات أو مصدرها. يمكننا تغيير\nطريقة عمل الـ repository في أي وقت، وما دمنا لم نغيّر الواجهة (interface)، فلن\nنحتاج لتعديل كود العملاء (client code).\n\n:::\n\nبهذا نكون أكملنا طبقة مزوّد البيانات وطبقة الـ repository، وأصبحنا جاهزين\nللانتقال إلى طبقة منطق الأعمال.\n\n### حدث GitHub Search\n\nسيتم إشعار Bloc عندما يكتب المستخدم اسم repository، وسنمثل ذلك عبر حدث\n`TextChanged` من نوع `GithubSearchEvent`.\n\nأنشئ `github_search_event.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\nنرث من [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة نسخ\n`GithubSearchEvent`. افتراضيًا، عامل المساواة يعيد `true` فقط إذا كان الكائنان\nنفس النسخة.\n\n:::\n\n### حالة GitHub Search\n\nطبقة العرض تحتاج عدة حالات لتتمكن من رسم الواجهة بشكل صحيح:\n\n- `SearchStateEmpty` لإبلاغ طبقة العرض بعدم وجود إدخال من المستخدم.\n- `SearchStateLoading` لإبلاغ طبقة العرض بضرورة إظهار مؤشر تحميل.\n- `SearchStateSuccess` لإبلاغ طبقة العرض بوجود بيانات جاهزة للعرض.\n\n  - `items` ستكون `List<SearchResultItem>` التي ستُعرض.\n\n- `SearchStateError` لإبلاغ طبقة العرض بحدوث خطأ أثناء جلب repositories.\n\n  - `error` يمثل الخطأ الفعلي الذي حدث.\n\nيمكننا الآن إنشاء `github_search_state.dart` وتنفيذه كالتالي.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\nنرث من [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة نسخ\n`GithubSearchState`. افتراضيًا، عامل المساواة يعيد `true` فقط إذا كان الكائنان\nنفس النسخة.\n\n:::\n\nبعد تنفيذ Events وStates، يمكننا إنشاء `GithubSearchBloc`.\n\n### GitHub Search Bloc\n\nأنشئ `github_search_bloc.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\n`GithubSearchBloc` يحوّل `GithubSearchEvent` إلى `GithubSearchState`، ويعتمد على\n`GithubRepository`.\n\n:::\n\n:::note\n\nننشئ `EventTransformer` مخصصًا لتنفيذ\n[debounce](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)\nعلى `GithubSearchEvents`. وأحد أسباب اختيار Bloc بدل Cubit هو الاستفادة من\nstream transformers.\n\n:::\n\nممتاز. انتهينا من حزمة `common_github_search`. الناتج النهائي يجب أن يبدو مثل\n[هذا](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search).\n\nالخطوة التالية: تنفيذ Flutter.\n\n## بحث GitHub باستخدام Flutter\n\nFlutter GitHub Search سيكون تطبيق Flutter يعيد استخدام النماذج، ومزوّدي\nالبيانات، والـ repositories، والـ blocs من `common_github_search` لتنفيذ ميزة\nالبحث في GitHub.\n\n### الإعداد\n\nنبدأ بإنشاء مشروع Flutter جديد داخل مجلد `github_search` في نفس مستوى\n`common_github_search`.\n\n<FlutterCreateSnippet />\n\nبعد ذلك نحدّث `pubspec.yaml` ليتضمن كل dependencies المطلوبة.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\nنضيف مكتبة `common_github_search` التي أنشأناها كاعتمادية.\n\n:::\n\nالآن نثبت dependencies.\n\n<FlutterPubGetSnippet />\n\nانتهى إعداد المشروع. بما أن `common_github_search` تحتوي طبقة البيانات وطبقة\nمنطق الأعمال، فكل ما نحتاج لبنائه هو طبقة العرض.\n\n### نموذج البحث\n\nسنحتاج إلى إنشاء نموذج يحتوي `widget` باسم `_SearchBar` و`widget` باسم\n`_SearchBody`.\n\n- `_SearchBar` سيكون مسؤولًا عن استقبال إدخال المستخدم.\n- `_SearchBody` سيكون مسؤولًا عن عرض نتائج البحث ومؤشرات التحميل والأخطاء.\n\nلننشئ `search_form.dart`.\n\n`SearchForm` سيكون `StatelessWidget` يعرض `_SearchBar` و`_SearchBody`.\n\n`_SearchBar` سيكون أيضًا `StatefulWidget` لأنه يحتاج لإدارة\n`TextEditingController` خاص به حتى نتتبع مدخلات المستخدم.\n\n`_SearchBody` هو `StatelessWidget` مسؤول عن عرض نتائج البحث والأخطاء ومؤشرات\nالتحميل. وهو المستهلك لـ `GithubSearchBloc`.\n\nإذا كانت الحالة `SearchStateSuccess`، سنعرض `_SearchResults` الذي سننفذه لاحقًا.\n\n`_SearchResults` هو `StatelessWidget` يستقبل `List<SearchResultItem>` ويعرضها\nكقائمة من `_SearchResultItems`.\n\n`_SearchResultItem` هو `StatelessWidget` مسؤول عن عرض بيانات نتيجة بحث واحدة.\nوهو أيضًا مسؤول عن التعامل مع تفاعل المستخدم والتنقل إلى رابط repository عند\nالنقر.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar` يصل إلى `GithubSearchBloc` عبر `context.read<GithubSearchBloc>()`\nويُشعِر bloc بأحداث `TextChanged`.\n\n:::\n\n:::note\n\n`_SearchBody` يستخدم `BlocBuilder` لإعادة البناء استجابةً لتغيّر الحالة. وبما أن\nوسيط bloc في `BlocBuilder` تم حذفه، سيقوم `BlocBuilder` تلقائيًا بالبحث عن\ninstance مناسبة عبر `BlocProvider` و`BuildContext` الحالي. اقرأ المزيد\n[هنا.](/ar/flutter-bloc-concepts#blocbuilder)\n\n:::\n\n:::note\n\nنستخدم `ListView.builder` لبناء قائمة قابلة للتمرير من `_SearchResultItem`.\n\n:::\n\n:::note\n\nنستخدم حزمة [url_launcher](https://pub.dev/packages/url_launcher) لفتح الروابط\nالخارجية.\n\n:::\n\n### تجميع كل شيء\n\nكل ما تبقى هو تنفيذ التطبيق الرئيسي في `main.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\nيتم إنشاء `GithubRepository` في `main` وحقنه داخل `App`. ثم يتم تغليف\n`SearchForm` داخل `BlocProvider` المسؤول عن إنشاء instance من `GithubSearchBloc`\nوإغلاقها وإتاحتها لـ `SearchForm` والأبناء.\n\n:::\n\nبهذا نكون نفذنا تطبيق بحث GitHub في Flutter بنجاح باستخدام حزمتَي\n[bloc](https://pub.dev/packages/bloc) و\n[flutter_bloc](https://pub.dev/packages/flutter_bloc)، وتمكنا من فصل طبقة العرض\nعن طبقة منطق الأعمال.\n\nيمكنك العثور على المصدر الكامل\n[هنا](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search).\n\nالآن سننتقل إلى بناء تطبيق GitHub Search باستخدام AngularDart.\n\n## بحث GitHub باستخدام AngularDart\n\nAngularDart GitHub Search سيكون تطبيق AngularDart يعيد استخدام النماذج، ومزوّدي\nالبيانات، والـ repositories، والـ blocs من `common_github_search` لتنفيذ ميزة\nالبحث في GitHub.\n\n### الإعداد\n\nنبدأ بإنشاء مشروع AngularDart جديد داخل مجلد `github_search` في نفس مستوى\n`common_github_search`.\n\n<StagehandSnippet />\n\n:::note\n\nيمكنك تثبيت `stagehand` عبر:\n\n<ActivateStagehandSnippet />\n\n:::\n\nبعدها يمكننا استبدال محتوى `pubspec.yaml` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### نموذج البحث\n\nكما في تطبيق Flutter، نحتاج إلى `SearchForm` يحتوي على مكوّني `SearchBar` و\n`SearchBody`.\n\nمكوّن `SearchForm` سيطبّق `OnInit` و`OnDestroy` لأنه يحتاج لإنشاء\n`GithubSearchBloc` ثم إغلاقه.\n\n- `SearchBar` مسؤول عن استقبال إدخال المستخدم.\n- `SearchBody` مسؤول عن عرض نتائج البحث ومؤشرات التحميل والأخطاء.\n\nلننشئ `search_form_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\nيتم حقن `GithubRepository` داخل `SearchFormComponent`.\n\n:::\n\n:::note\n\n`SearchFormComponent` هو المسؤول عن إنشاء `GithubSearchBloc` وإغلاقه.\n\n:::\n\nوسيكون القالب (`search_form_component.html`) كالتالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\nالآن سننفذ مكوّن `SearchBar`.\n\n### Search Bar\n\n`SearchBar` هو مكوّن مسؤول عن استقبال إدخال المستخدم، وإشعار `GithubSearchBloc`\nبتغيّر النص.\n\nأنشئ `search_bar_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent` يعتمد على `GithubSearchBloc` لأنه مسؤول عن إرسال أحداث\n`TextChanged` إلى bloc.\n\n:::\n\nبعدها ننشئ `search_bar_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\nانتهينا من `SearchBar`، والآن ننتقل إلى `SearchBody`.\n\n### Search Body\n\n`SearchBody` هو مكوّن مسؤول عن عرض نتائج البحث والأخطاء ومؤشرات التحميل. وهو\nالمستهلك لـ `GithubSearchBloc`.\n\nأنشئ `search_body_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent` يعتمد على `GithubSearchState` التي يتم توفيرها من\n`GithubSearchBloc` باستخدام bloc pipe الخاصة بـ `angular_bloc`.\n\n:::\n\nأنشئ `search_body_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\nإذا كانت الحالة `isSuccess`، سنعرض `SearchResults`، وسننفيذه الآن.\n\n### Search Results\n\n`SearchResults` هو مكوّن يستقبل `List<SearchResultItem>` ويعرضها كقائمة من\n`SearchResultItems`.\n\nأنشئ `search_results_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\nبعد ذلك ننشئ `search_results_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\nنستخدم `ngFor` لبناء قائمة من مكونات `SearchResultItem`.\n\n:::\n\nحان الوقت لتنفيذ `SearchResultItem`.\n\n### Search Result Item\n\n`SearchResultItem` هو مكوّن مسؤول عن عرض معلومات نتيجة بحث واحدة، كما يتعامل مع\nتفاعل المستخدم وينتقل إلى رابط repository عند النقر.\n\nأنشئ `search_result_item_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\nثم أنشئ القالب المقابل `search_result_item_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### تجميع كل شيء\n\nلدينا الآن جميع المكونات، وحان وقت تجميعها داخل `app_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\nننشئ `GithubRepository` داخل `AppComponent` ثم نحقنه في مكوّن `SearchForm`.\n\n:::\n\nبهذا نكون نفذنا تطبيق بحث GitHub في AngularDart بنجاح باستخدام حزمتَي `bloc` و\n`angular_bloc`، وتمكّنا من فصل طبقة العرض عن طبقة منطق الأعمال.\n\nيمكنك العثور على المصدر الكامل\n[هنا](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search).\n\n## الملخص\n\nفي هذا الدليل، أنشأنا تطبيق Flutter وتطبيق AngularDart مع مشاركة جميع النماذج،\nومزوّدي البيانات، والـ blocs بين المشروعين.\n\nالجزء الوحيد الذي احتجنا لكتابته مرتين فعليًا هو طبقة العرض (UI)، وهذا ممتاز من\nناحية الكفاءة وسرعة التطوير. وبما أن تطبيقات الويب وتطبيقات الجوال غالبًا ما\nتملك تجارب استخدام وأنماط تصميم مختلفة، فهذا النهج يوضح مدى سهولة بناء تطبيقين\nبواجهتين مختلفتين تمامًا مع مشاركة نفس طبقة البيانات وطبقة منطق الأعمال.\n\nيمكنك العثور على المصدر الكامل\n[هنا](https://github.com/felangel/bloc/tree/master/examples/github_search).\n"
  },
  {
    "path": "docs/src/content/docs/ar/tutorials/ngdart-counter.mdx",
    "content": "---\ntitle: AngularDart Counter\ndescription:\n  دليل متعمق لبناء تطبيق عدّاد (Counter) باستخدام AngularDart ومكتبة Bloc.\nsidebar:\n  order: 8\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/ngdart-counter/StagehandSnippet.astro';\nimport InstallDependenciesSnippet from '~/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nفي هذا الدليل، سنبني تطبيق عدّاد (Counter) باستخدام AngularDart ومكتبة Bloc.\n\n![demo](~/assets/tutorials/ngdart-counter.gif)\n\n## الإعداد (Setup)\n\nسنبدأ بإنشاء مشروع AngularDart جديد باستخدام\n[`stagehand`](https://github.com/dart-lang/stagehand).\n\nإذا لم يكن `stagehand` مثبتًا لديك، فعِّله عبر:\n\n<ActivateStagehandSnippet />\n\nثم أنشئ مشروعًا جديدًا عبر:\n\n<StagehandSnippet />\n\nبعد ذلك، استبدل محتوى ملف `pubspec.yaml` بما يلي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nثم ثبّت جميع dependencies:\n\n<InstallDependenciesSnippet />\n\nسيحتوي تطبيق العداد على زرين لزيادة/إنقاص قيمة العداد، وعنصر لعرض القيمة\nالحالية. لنبدأ بتصميم `CounterEvents`.\n\n## Counter Bloc\n\nبما أن حالة العداد يمكن تمثيلها بعدد صحيح (`integer`)، فلا حاجة لإنشاء class\nمخصصة، ويمكننا وضع events وBloc في نفس المكان.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_bloc.dart\"\n\ttitle=\"lib/src/counter_page/counter_bloc.dart\"\n/>\n\n:::note\n\nمن تعريف class فقط، نعرف أن `CounterBloc` يستقبل `CounterEvents` كمدخلات، ويُنتج\nأعدادًا صحيحة (`integers`).\n\n:::\n\n## تطبيق العداد (Counter App)\n\nبعد اكتمال تنفيذ `CounterBloc`، يمكننا البدء في إنشاء مكوّن تطبيق AngularDart\n(`App Component`).\n\nيجب أن يكون `app.component.dart` كالتالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.dart\"\n\ttitle=\"lib/app_component.dart\"\n/>\n\nويجب أن يكون `app.component.html` كالتالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.html\"\n\ttitle=\"lib/app_component.html\"\n/>\n\n## صفحة العداد (Counter Page)\n\nأخيرًا، يتبقى بناء مكوّن صفحة العداد (`Counter Page Component`).\n\nيجب أن يكون `counter_page_component.dart` كالتالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.dart\"\n\ttitle=\"lib/src/counter_page/counter_page_component.dart\"\n/>\n\n:::note\n\nيمكن الوصول إلى نسخة `CounterBloc` عبر نظام حقن الاعتماديات\n(`dependency injection system`) في AngularDart. وبما أننا سجلناه كـ `Provider`،\nيستطيع AngularDart إجراء `resolve` لـ `CounterBloc` بشكل صحيح.\n\n:::\n\n:::note\n\nنغلق `CounterBloc` داخل `ngOnDestroy`.\n\n:::\n\n:::note\n\nنستورد `BlocPipe` حتى نتمكن من استخدامه في القالب (`template`).\n\n:::\n\nأخيرًا، يجب أن يكون `counter_page_component.html` كالتالي:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.html\"\n\ttitle=\"lib/src/counter_page/counter_page_component.html\"\n/>\n\n:::note\n\nنستخدم `BlocPipe` لعرض حالة `CounterBloc` مع كل تحديث.\n\n:::\n\nهذا كل شيء. قمنا بفصل طبقة العرض (`presentation layer`) عن طبقة منطق الأعمال\n(`business logic layer`). لا يعرف `CounterPageComponent` ماذا يحدث عند ضغط\nالمستخدم على الزر؛ هو فقط يضيف حدثًا لإخطار `CounterBloc`. وبالمثل، لا يعرف\n`CounterBloc` تفاصيل العرض الخاصة بالحالة (قيمة العداد)؛ بل يحوّل\n`CounterEvents` إلى أعداد صحيحة.\n\nيمكن تشغيل التطبيق باستخدام `webdev serve` ثم عرضه محليًا.\n\nيمكنك العثور على المصدر الكامل لهذا المثال\n[هنا](https://github.com/felangel/bloc/tree/master/examples/angular_counter).\n"
  },
  {
    "path": "docs/src/content/docs/ar/why-bloc.mdx",
    "content": "---\ntitle: لماذا Bloc؟\ndescription:\n  نظرة عامة على ما يجعل Bloc حلاً متينًا لإدارة الحالة (State Management).\nsidebar:\n  order: 1\n---\n\nيسهّل Bloc فصل طبقة العرض (Presentation) عن منطق العمل (Business Logic)، مما\nيجعل كودك سريعًا، سهل الاختبار، وقابلًا لإعادة الاستخدام.\n\nعند بناء تطبيقات بجودة إنتاجية، تصبح إدارة الحالة أمرًا بالغ الأهمية.\n\nكمطورين، نرغب في:\n\n- معرفة الحالة التي يكون عليها تطبيقنا في أي لحظة.\n- اختبار كل سيناريو بسهولة للتأكد من أن التطبيق يستجيب بالشكل الصحيح.\n- تسجيل كل تفاعل يقوم به المستخدم داخل التطبيق لاتخاذ قرارات مبنية على البيانات.\n- العمل بأعلى قدر ممكن من الكفاءة وإعادة استخدام المكونات داخل التطبيق نفسه أو\n  عبر تطبيقات أخرى.\n- تمكين عدة مطورين من العمل بسلاسة ضمن قاعدة كود واحدة باتباع نفس الأنماط\n  والاتفاقيات.\n- تطوير تطبيقات سريعة وتفاعلية.\n\nتم تصميم Bloc لتلبية جميع هذه الاحتياجات وأكثر من ذلك.\n\nهناك العديد من حلول إدارة الحالة، وقد يكون اختيار الحل المناسب مهمة صعبة. لا\nيوجد حل مثالي واحد! المهم هو اختيار الحل الذي يناسب فريقك ومشروعك بالشكل الأفضل.\n\nتم تصميم Bloc مع التركيز على ثلاث قيم أساسية:\n\n- **بسيط (Simple):** سهل الفهم ويمكن استخدامه من قبل مطورين بمستويات مهارية\n  مختلفة.\n- **قوي (Powerful):** يساعد على بناء تطبيقات مذهلة ومعقدة من خلال تكوينها من\n  مكونات أصغر.\n- **قابل للاختبار (Testable):** يسهّل اختبار جميع جوانب التطبيق لنتمكن من\n  التطوير بثقة.\n\nبشكل عام، يسعى Bloc إلى جعل تغييرات الحالة قابلة للتنبؤ (Predictable) من خلال\nتنظيم توقيت حدوث التغيير وفرض أسلوب موحد لتغيير الحالة في كامل التطبيق.\n"
  },
  {
    "path": "docs/src/content/docs/architecture.mdx",
    "content": "---\ntitle: Architecture\ndescription: Overview of the recommended architecture patterns when using bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nUsing the bloc library allows us to separate our application into three layers:\n\n- Presentation\n- Business Logic\n- Data\n  - Repository\n  - Data Provider\n\nWe're going to start at the lowest level layer (farthest from the user\ninterface) and work our way up to the presentation layer.\n\n## Data Layer\n\nThe data layer's responsibility is to retrieve/manipulate data from one or more\nsources.\n\nThe data layer can be split into two parts:\n\n- Repository\n- Data Provider\n\nThis layer is the lowest level of the application and interacts with databases,\nnetwork requests, and other asynchronous data sources.\n\n### Data Provider\n\nThe data provider's responsibility is to provide raw data. The data provider\nshould be generic and versatile.\n\nThe data provider will usually expose simple APIs to perform\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)\noperations. We might have a `createData`, `readData`, `updateData`, and\n`deleteData` method as part of our data layer.\n\n<DataProviderSnippet />\n\n### Repository\n\nThe repository layer is a wrapper around one or more data providers with which\nthe Bloc Layer communicates.\n\n<RepositorySnippet />\n\nAs you can see, our repository layer can interact with multiple data providers\nand perform transformations on the data before handing the result to the\nbusiness logic layer.\n\n## Business Logic Layer\n\nThe business logic layer's responsibility is to respond to input from the\npresentation layer with new states. This layer can depend on one or more\nrepositories to retrieve data needed to build up the application state.\n\nThink of the business logic layer as the bridge between the user interface\n(presentation layer) and the data layer. The business logic layer is notified of\nevents/actions from the presentation layer and then communicates with repository\nin order to build a new state for the presentation layer to consume.\n\n<BusinessLogicComponentSnippet />\n\n### Bloc-to-Bloc Communication\n\nBecause blocs expose streams, it may be tempting to make a bloc which listens to\nanother bloc. You should **not** do this. There are better alternatives than\nresorting to the code below:\n\n<BlocTightCouplingSnippet />\n\nWhile the code above is error free (and even cleans up after itself), it has a\nbigger problem: it creates a dependency between two blocs.\n\nGenerally, sibling dependencies between two entities in the same architectural\nlayer should be avoided at all costs, as it creates tight-coupling which is hard\nto maintain. Since blocs reside in the business logic architectural layer, no\nbloc should know about any other bloc.\n\n![Application Architecture Layers](~/assets/architecture/architecture.png)\n\nA bloc should only receive information through events and from injected\nrepositories (i.e., repositories given to the bloc in its constructor).\n\nIf you're in a situation where a bloc needs to respond to another bloc, you have\ntwo other options. You can push the problem up a layer (into the presentation\nlayer), or down a layer (into the domain layer).\n\n#### Connecting Blocs through Presentation\n\nYou can use a `BlocListener` to listen to one bloc and add an event to another\nbloc whenever the first bloc changes.\n\n<BlocLooseCouplingPresentationSnippet />\n\nThe code above prevents `SecondBloc` from needing to know about `FirstBloc`,\nencouraging loose-coupling. The [flutter_weather](/tutorials/flutter-weather)\napplication\n[uses this technique](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nto change the app's theme based on the weather information that is received.\n\nIn some situations, you may not want to couple two blocs in the presentation\nlayer. Instead, it can often make sense for two blocs to share the same source\nof data and update whenever the data changes.\n\n#### Connecting Blocs through Domain\n\nTwo blocs can listen to a stream from a repository and update their states\nindependent of each other whenever the repository data changes. Using reactive\nrepositories to keep state synchronized is common in large-scale enterprise\napplications.\n\nFirst, create or use a repository which provides a data `Stream`. For example,\nthe following repository exposes a never-ending stream of the same few app\nideas:\n\n<AppIdeasRepositorySnippet />\n\nThe same repository can be injected into each bloc that needs to react to new\napp ideas. Below is an `AppIdeaRankingBloc` which yields a state out for each\nincoming app idea from the repository above:\n\n<AppIdeaRankingBlocSnippet />\n\nFor more about using streams with Bloc, see\n[How to use Bloc with streams and concurrency](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Presentation Layer\n\nThe presentation layer's responsibility is to figure out how to render itself\nbased on one or more bloc states. In addition, it should handle user input and\napplication lifecycle events.\n\nMost applications flows will start with a `AppStart` event which triggers the\napplication to fetch some data to present to the user.\n\nIn this scenario, the presentation layer would add an `AppStart` event.\n\nIn addition, the presentation layer will have to figure out what to render on\nthe screen based on the state from the bloc layer.\n\n<PresentationComponentSnippet />\n\nSo far, even though we've had some code snippets, all of this has been fairly\nhigh level. In the tutorial section we're going to put all this together as we\nbuild several different example apps.\n"
  },
  {
    "path": "docs/src/content/docs/bloc-concepts.mdx",
    "content": "---\ntitle: Bloc Concepts\ndescription: An overview of the core concepts for package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nPlease make sure to carefully read the following sections before working with\n[`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nThere are several core concepts that are critical to understanding how to use\nthe bloc package.\n\nIn the upcoming sections, we're going to discuss each of them in detail as well\nas work through how they would apply to a counter app.\n\n## Streams\n\n:::note\n\nCheck out the official\n[Dart Documentation](https://dart.dev/tutorials/language/streams) for more\ninformation about `Streams`.\n\n:::\n\nA stream is a sequence of asynchronous data.\n\nIn order to use the bloc library, it is critical to have a basic understanding\nof `Streams` and how they work.\n\nIf you're unfamiliar with `Streams` just think of a pipe with water flowing\nthrough it. The pipe is the `Stream` and the water is the asynchronous data.\n\nWe can create a `Stream` in Dart by writing an `async*` (async generator)\nfunction.\n\n<CountStreamSnippet />\n\nBy marking a function as `async*` we are able to use the `yield` keyword and\nreturn a `Stream` of data. In the above example, we are returning a `Stream` of\nintegers up to the `max` integer parameter.\n\nEvery time we `yield` in an `async*` function we are pushing that piece of data\nthrough the `Stream`.\n\nWe can consume the above `Stream` in several ways. If we wanted to write a\nfunction to return the sum of a `Stream` of integers it could look something\nlike:\n\n<SumStreamSnippet />\n\nBy marking the above function as `async` we are able to use the `await` keyword\nand return a `Future` of integers. In this example, we are awaiting each value\nin the stream and returning the sum of all integers in the stream.\n\nWe can put it all together like so:\n\n<StreamsMainSnippet />\n\nNow that we have a basic understanding of how `Streams` work in Dart we're ready\nto learn about the core component of the bloc package: a `Cubit`.\n\n## Cubit\n\nA `Cubit` is a class which extends `BlocBase` and can be extended to manage any\ntype of state.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nA `Cubit` can expose functions which can be invoked to trigger state changes.\n\nStates are the output of a `Cubit` and represent a part of your application's\nstate. UI components can be notified of states and redraw portions of themselves\nbased on the current state.\n\n:::note\n\nFor more information about the origins of `Cubit` checkout\n[the following issue](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Creating a Cubit\n\nWe can create a `CounterCubit` like:\n\n<CounterCubitSnippet />\n\nWhen creating a `Cubit`, we need to define the type of state which the `Cubit`\nwill be managing. In the case of the `CounterCubit` above, the state can be\nrepresented via an `int` but in more complex cases it might be necessary to use\na `class` instead of a primitive type.\n\nThe second thing we need to do when creating a `Cubit` is specify the initial\nstate. We can do this by calling `super` with the value of the initial state. In\nthe snippet above, we are setting the initial state to `0` internally but we can\nalso allow the `Cubit` to be more flexible by accepting an external value:\n\n<CounterCubitInitialStateSnippet />\n\nThis would allow us to instantiate `CounterCubit` instances with different\ninitial states like:\n\n<CounterCubitInstantiationSnippet />\n\n### Cubit State Changes\n\nEach `Cubit` has the ability to output a new state via `emit`.\n\n<CounterCubitIncrementSnippet />\n\nIn the above snippet, the `CounterCubit` is exposing a public method called\n`increment` which can be called externally to notify the `CounterCubit` to\nincrement its state. When `increment` is called, we can access the current state\nof the `Cubit` via the `state` getter and `emit` a new state by adding 1 to the\ncurrent state.\n\n:::caution\n\nThe `emit` method is protected, meaning it should only be used inside of a\n`Cubit`.\n\n:::\n\n### Using a Cubit\n\nWe can now take the `CounterCubit` we've implemented and put it to use!\n\n#### Basic Usage\n\n<CounterCubitBasicUsageSnippet />\n\nIn the above snippet, we start by creating an instance of the `CounterCubit`. We\nthen print the current state of the cubit which is the initial state (since no\nnew states have been emitted yet). Next, we call the `increment` function to\ntrigger a state change. Finally, we print the state of the `Cubit` again which\nwent from `0` to `1` and call `close` on the `Cubit` to close the internal state\nstream.\n\n#### Stream Usage\n\n`Cubit` exposes a `Stream` which allows us to receive real-time state updates:\n\n<CounterCubitStreamUsageSnippet />\n\nIn the above snippet, we are subscribing to the `CounterCubit` and calling print\non each state change. We are then invoking the `increment` function which will\nemit a new state. Lastly, we are calling `cancel` on the `subscription` when we\nno longer want to receive updates and closing the `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` is added for this example to avoid\ncanceling the subscription immediately.\n\n:::\n\n:::caution\n\nOnly subsequent state changes will be received when calling `listen` on a\n`Cubit`.\n\n:::\n\n### Observing a Cubit\n\nWhen a `Cubit` emits a new state, a `Change` occurs. We can observe all changes\nfor a given `Cubit` by overriding `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nWe can then interact with the `Cubit` and observe all changes output to the\nconsole.\n\n<CounterCubitOnChangeUsageSnippet />\n\nThe above example would output:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nA `Change` occurs just before the state of the `Cubit` is updated. A `Change`\nconsists of the `currentState` and the `nextState`.\n\n:::\n\n#### BlocObserver\n\nOne added bonus of using the bloc library is that we can have access to all\n`Changes` in one place. Even though in this application we only have one\n`Cubit`, it's fairly common in larger applications to have many `Cubits`\nmanaging different parts of the application's state.\n\nIf we want to be able to do something in response to all `Changes` we can simply\ncreate our own `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nAll we need to do is extend `BlocObserver` and override the `onChange` method.\n\n:::\n\nIn order to use the `SimpleBlocObserver`, we just need to tweak the `main`\nfunction:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nThe above snippet would then output:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nThe internal `onChange` override is called first, which calls `super.onChange`\nnotifying the `onChange` in the `BlocObserver`.\n\n:::\n\n:::tip\n\nIn `BlocObserver` we have access to the `Cubit` instance in addition to the\n`Change` itself.\n\n:::\n\n### Cubit Error Handling\n\nEvery `Cubit` has an `addError` method which can be used to indicate that an\nerror has occurred.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` can be overridden within the `Cubit` to handle all errors for a\nspecific `Cubit`.\n\n:::\n\n`onError` can also be overridden in `BlocObserver` to handle all reported errors\nglobally.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nIf we run the same program again we should see the following output:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nA `Bloc` is a more advanced class which relies on `events` to trigger `state`\nchanges rather than functions. `Bloc` also extends `BlocBase` which means it has\na similar public API as `Cubit`. However, rather than calling a `function` on a\n`Bloc` and directly emitting a new `state`, `Blocs` receive `events` and convert\nthe incoming `events` into outgoing `states`.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Creating a Bloc\n\nCreating a `Bloc` is similar to creating a `Cubit` except in addition to\ndefining the state that we'll be managing, we must also define the event that\nthe `Bloc` will be able to process.\n\nEvents are the input to a Bloc. They are commonly added in response to user\ninteractions such as button presses or lifecycle events like page loads.\n\n<CounterBlocSnippet />\n\nJust like when creating the `CounterCubit`, we must specify an initial state by\npassing it to the superclass via `super`.\n\n### Bloc State Changes\n\n`Bloc` requires us to register event handlers via the `on<Event>` API, as\nopposed to functions in `Cubit`. An event handler is responsible for converting\nany incoming events into zero or more outgoing states.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nAn `EventHandler` has access to the added event as well as an `Emitter` which\ncan be used to emit zero or more states in response to the incoming event.\n\n:::\n\nWe can then update the `EventHandler` to handle the `CounterIncrementPressed`\nevent:\n\n<CounterBlocIncrementSnippet />\n\nIn the above snippet, we have registered an `EventHandler` to manage all\n`CounterIncrementPressed` events. For each incoming `CounterIncrementPressed`\nevent we can access the current state of the bloc via the `state` getter and\n`emit(state + 1)`.\n\n:::note\n\nSince the `Bloc` class extends `BlocBase`, we have access to the current state\nof the bloc at any point in time via the `state` getter just like in `Cubit`.\n\n:::\n\n:::caution\n\nBlocs should never directly `emit` new states. Instead every state change must\nbe output in response to an incoming event within an `EventHandler`.\n\n:::\n\n:::caution\n\nBoth blocs and cubits will ignore duplicate states. If we emit `State nextState`\nwhere `state == nextState`, then no state change will occur.\n\n:::\n\n### Using a Bloc\n\nAt this point, we can create an instance of our `CounterBloc` and put it to use!\n\n#### Basic Usage\n\n<CounterBlocUsageSnippet />\n\nIn the above snippet, we start by creating an instance of the `CounterBloc`. We\nthen print the current state of the `Bloc` which is the initial state (since no\nnew states have been emitted yet). Next, we add the `CounterIncrementPressed`\nevent to trigger a state change. Finally, we print the state of the `Bloc` again\nwhich went from `0` to `1` and call `close` on the `Bloc` to close the internal\nstate stream.\n\n:::note\n\n`await Future.delayed(Duration.zero)` is added to ensure we wait for the next\nevent-loop iteration (allowing the `EventHandler` to process the event).\n\n:::\n\n#### Stream Usage\n\nJust like with `Cubit`, a `Bloc` is a special type of `Stream`, which means we\ncan also subscribe to a `Bloc` for real-time updates to its state:\n\n<CounterBlocStreamUsageSnippet />\n\nIn the above snippet, we are subscribing to the `CounterBloc` and calling print\non each state change. We are then adding the `CounterIncrementPressed` event\nwhich triggers the `on<CounterIncrementPressed>` `EventHandler` and emits a new\nstate. Lastly, we are calling `cancel` on the subscription when we no longer\nwant to receive updates and closing the `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` is added for this example to avoid\ncanceling the subscription immediately.\n\n:::\n\n### Observing a Bloc\n\nSince `Bloc` extends `BlocBase`, we can observe all state changes for a `Bloc`\nusing `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nWe can then update `main.dart` to:\n\n<CounterBlocOnChangeUsageSnippet />\n\nNow if we run the above snippet, the output will be:\n\n<CounterBlocOnChangeOutputSnippet />\n\nOne key differentiating factor between `Bloc` and `Cubit` is that because `Bloc`\nis event-driven, we are also able to capture information about what triggered\nthe state change.\n\nWe can do this by overriding `onTransition`.\n\nThe change from one state to another is called a `Transition`. A `Transition`\nconsists of the current state, the event, and the next state.\n\n<CounterBlocOnTransitionSnippet />\n\nIf we then rerun the same `main.dart` snippet from before, we should see the\nfollowing output:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` is invoked before `onChange` and contains the event which\ntriggered the change from `currentState` to `nextState`.\n\n:::\n\n#### BlocObserver\n\nJust as before, we can override `onTransition` in a custom `BlocObserver` to\nobserve all transitions that occur from a single place.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nWe can initialize the `SimpleBlocObserver` just like before:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nNow if we run the above snippet, the output should look like:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` is invoked first (local before global) followed by `onChange`.\n\n:::\n\nAnother unique feature of `Bloc` instances is that they allow us to override\n`onEvent` which is called whenever a new event is added to the `Bloc`. Just like\nwith `onChange` and `onTransition`, `onEvent` can be overridden locally as well\nas globally.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nWe can run the same `main.dart` as before and should see the following output:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` is called as soon as the event is added. The local `onEvent` is\ninvoked before the global `onEvent` in `BlocObserver`.\n\n:::\n\n### Bloc Error Handling\n\nJust like with `Cubit`, each `Bloc` has an `addError` and `onError` method. We\ncan indicate that an error has occurred by calling `addError` from anywhere\ninside our `Bloc`. We can then react to all errors by overriding `onError` just\nas with `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nIf we rerun the same `main.dart` as before, we can see what it looks like when\nan error is reported:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nThe local `onError` is invoked first followed by the global `onError` in\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` and `onChange` work the exact same way for both `Bloc` and `Cubit`\ninstances.\n\n:::\n\n:::caution\n\nAny unhandled exceptions that occur within an `EventHandler` are also reported\nto `onError`.\n\n:::\n\n## Cubit vs. Bloc\n\nNow that we've covered the basics of the `Cubit` and `Bloc` classes, you might\nbe wondering when you should use `Cubit` and when you should use `Bloc`.\n\n### Cubit Advantages\n\n#### Simplicity\n\nOne of the biggest advantages of using `Cubit` is simplicity. When creating a\n`Cubit`, we only have to define the state as well as the functions which we want\nto expose to change the state. In comparison, when creating a `Bloc`, we have to\ndefine the states, events, and the `EventHandler` implementation. This makes\n`Cubit` easier to understand and there is less code involved.\n\nNow let's take a look at the two counter implementations:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nThe `Cubit` implementation is more concise and instead of defining events\nseparately, the functions act like events. In addition, when using a `Cubit`, we\ncan simply call `emit` from anywhere in order to trigger a state change.\n\n### Bloc Advantages\n\n#### Traceability\n\nOne of the biggest advantages of using `Bloc` is knowing the sequence of state\nchanges as well as exactly what triggered those changes. For state that is\ncritical to the functionality of an application, it might be very beneficial to\nuse a more event-driven approach in order to capture all events in addition to\nstate changes.\n\nA common use case might be managing `AuthenticationState`. For simplicity, let's\nsay we can represent `AuthenticationState` via an `enum`:\n\n<AuthenticationStateSnippet />\n\nThere could be many reasons as to why the application's state could change from\n`authenticated` to `unauthenticated`. For example, the user might have tapped a\nlogout button and requested to be signed out of the application. On the other\nhand, maybe the user's access token was revoked and they were forcefully logged\nout. When using `Bloc` we can clearly trace how the application state got to a\ncertain state.\n\n<AuthenticationTransitionSnippet />\n\nThe above `Transition` gives us all the information we need to understand why\nthe state changed. If we had used a `Cubit` to manage the `AuthenticationState`,\nour logs would look like:\n\n<AuthenticationChangeSnippet />\n\nThis tells us that the user was logged out but it doesn't explain why which\nmight be critical to debugging and understanding how the state of the\napplication is changing over time.\n\n#### Advanced Event Transformations\n\nAnother area in which `Bloc` excels over `Cubit` is when we need to take\nadvantage of reactive operators such as `buffer`, `debounceTime`, `throttle`,\netc.\n\n:::tip\n\nSee [`package:stream_transform`](https://pub.dev/packages/stream_transform) and\n[`package:rxdart`](https://pub.dev/packages/rxdart) for stream transformers.\n\n:::\n\n`Bloc` has an event sink that allows us to control and transform the incoming\nflow of events.\n\nFor example, if we were building a real-time search, we would probably want to\ndebounce the requests to the backend in order to avoid getting rate-limited as\nwell as to cut down on cost/load on the backend.\n\nWith `Bloc` we can provide a custom `EventTransformer` to change the way\nincoming events are processed by the `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nWith the above code, we can easily debounce the incoming events with very little\nadditional code.\n\n:::tip\n\nCheck out\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) for an\nopinionated set of event transformers.\n\n:::\n\nIf you are unsure about which to use, start with `Cubit` and you can later\nrefactor or scale-up to a `Bloc` as needed.\n"
  },
  {
    "path": "docs/src/content/docs/bn/architecture.mdx",
    "content": "---\ntitle: আর্কিটেকচার\ndescription:\n  Bloc ব্যবহার করার সময় সুপারিশকৃত আর্কিটেকচার প্যাটার্নগুলোর একটি ওভারভিউ।\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nBloc লাইব্রেরি ব্যবহার করলে আমরা আমাদের অ্যাপ্লিকেশনকে তিনটি স্তরে ভাগ করতে\nপারি:\n\n- প্রেজেন্টেশন\n- বিজনেস লজিক\n- ডেটা\n  - রিপোজিটরি\n  - ডেটা প্রোভাইডার\n\nআমরা ব্যবহারকারীর ইন্টারফেস থেকে সবচেয়ে দূরের (সবচেয়ে নিচের) স্তর থেকে শুরু\nকরব এবং প্রেজেন্টেশন লেয়ার পর্যন্ত উপরের দিকে এগোব।\n\n## ডেটা লেয়ার\n\nডেটা লেয়ারের দায়িত্ব হলো এক বা একাধিক সোর্স থেকে ডেটা উদ্ধার/ম্যানিপুলেট করা।\n\nডেটা লেয়ার দুইটি অংশে বিভক্ত হতে পারে:\n\n- রিপোজিটরি\n- ডেটা প্রোভাইডার\n\nএই লেয়ারটি অ্যাপ্লিকেশনের সবচেয়ে নিচের স্তর এবং এটি ডাটাবেস, নেটওয়ার্ক\nরিকোয়েস্ট এবং অন্যান্য অ্যাসিঙ্ক্রোনাস ডেটা সোর্সের সাথে ইন্টারঅ্যাক্ট করে।\n\n### ডেটা প্রোভাইডার\n\nডেটা প্রোভাইডারের দায়িত্ব হলো র’ ডেটা সরবরাহ করা। ডেটা প্রোভাইডারটি জেনেরিক এবং\nবহুমুখী হওয়া উচিত।\n\nডেটা প্রোভাইডার সাধারণত\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) অপারেশন\nসম্পাদনের জন্য সরল API প্রদান করবে। উদাহরণস্বরূপ, আমাদের `createData`,\n`readData`, `updateData`, এবং `deleteData` মেথড থাকতে পারে।\n\n<DataProviderSnippet />\n\n### রিপোজিটরি\n\nরিপোজিটরি লেয়ার হলো এক বা একাধিক ডেটা প্রোভাইডারের একটি র‍্যাপার, যার সাথে Bloc\nলেয়ার যোগাযোগ করে।\n\n<RepositorySnippet />\n\nযেমনটি দেখানো হয়েছে, আমাদের রিপোজিটরি লেয়ার একাধিক ডেটা প্রোভাইডারের সাথে\nইন্টারঅ্যাক্ট করতে পারে এবং বিজনেস লজিক লেয়ারকে ফলাফল দেওয়ার আগে ডেটা\nট্রান্সফর্ম করতে পারে।\n\n## বিজনেস লজিক লেয়ার\n\nবিজনেস লজিক লেয়ারের দায়িত্ব হলো প্রেজেন্টেশন লেয়ার থেকে ইনপুট নিয়ে নতুন\nস্টেট তৈরি করা। এই লেয়ার এক বা একাধিক রিপোজিটরির উপর নির্ভর করতে পারে\nপ্রয়োজনীয় ডেটা উদ্ধারের জন্য।\n\nবিজনেস লজিক লেয়ারকে প্রেজেন্টেশন লেয়ার এবং ডেটা লেয়ারের মধ্যে একটি ব্রিজ\nহিসেবে ভাবুন। এটি প্রেজেন্টেশন লেয়ার থেকে ইভেন্ট/অ্যাকশন নোটিফাই হয় এবং নতুন\nস্টেট তৈরি করতে রিপোজিটরির সাথে যোগাযোগ করে যা প্রেজেন্টেশন লেয়ার ব্যবহার করবে।\n\n<BusinessLogicComponentSnippet />\n\n### Bloc-to-Bloc কমিউনিকেশন\n\nকারণ ব্লকগুলি স্ট্রিম প্রকাশ করে, তাই একটি ব্লক অন্য ব্লককে লিসেন করে তৈরি করা\nপ্রলোভন হতে পারে। তবে এটি **করবেন না**। নিচের কোডের চেয়ে ভালো বিকল্প রয়েছে:\n\n<BlocTightCouplingSnippet />\n\nউপরের কোডটি ত্রুটিমুক্ত হলেও, এতে একটি বড় সমস্যা রয়েছে: এটি দুইটি ব্লকের মধ্যে\nনির্ভরতা তৈরি করে।\n\nসাধারণভাবে, একই আর্কিটেকচার স্তরের দুটি সিবলিং এন্টিটির মধ্যে নির্ভরতা এড়ানো\nউচিত, কারণ এটি কঠিন এবং রক্ষণাবেক্ষণে অসুবিধাজনক। যেহেতু ব্লকগুলি বিজনেস লজিক\nআর্কিটেকচার লেয়ারে থাকে, কোন ব্লকই অন্য ব্লকের সম্পর্কে জানবে না।\n\nএকটি ব্লককে অন্য ব্লকের পরিবর্তনের উপর রেসপন্ড করতে হলে, আপনার দুটি বিকল্প\nরয়েছে। আপনি সমস্যা উপরের স্তরে (প্রেজেন্টেশন লেয়ারে) বা নিচের স্তরে (ডোমেইন\nলেয়ারে) ঠেলতে পারেন।\n\n#### প্রেজেন্টেশন লেয়ারের মাধ্যমে ব্লক সংযোগ\n\n`BlocListener` ব্যবহার করে একটি ব্লককে লিসেন করতে এবং প্রথম ব্লক পরিবর্তিত হলে\nঅন্য ব্লকে ইভেন্ট যোগ করতে পারেন।\n\n<BlocLooseCouplingPresentationSnippet />\n\nউপরের কোডটি `SecondBloc`-কে `FirstBloc` সম্পর্কে জানার প্রয়োজন থেকে মুক্ত রাখে,\nএবং লুজ-কাপলিংকে উৎসাহিত করে। [flutter_weather](/bn/tutorials/flutter-weather)\nঅ্যাপ্লিকেশনটি এই কৌশল ব্যবহার করে\n[এখানে](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nআবহাওয়ার উপর ভিত্তি করে অ্যাপের থিম পরিবর্তন করে।\n\nকিছু পরিস্থিতিতে, প্রেজেন্টেশন লেয়ারে দুইটি ব্লক কাপল করতে চাইতে নাও পারেন।\nবরং, দুটি ব্লক একই ডেটা সোর্স ভাগ করতে পারে এবং ডেটা পরিবর্তনের সাথে সাথে স্টেট\nআপডেট করতে পারে।\n\n#### ডোমেইন লেয়ারের মাধ্যমে ব্লক সংযোগ\n\nদুটি ব্লক রিপোজিটরির স্ট্রিম শুনতে পারে এবং রিপোজিটরি ডেটা পরিবর্তিত হলে\nস্বাধীনভাবে তাদের স্টেট আপডেট করে। বড় স্কেলের এন্টারপ্রাইজ অ্যাপ্লিকেশনে স্টেট\nসিঙ্ক্রোনাইজ করার জন্য রিয়েক্টিভ রিপোজিটরি ব্যবহার করা সাধারণ।\n\nপ্রথমে একটি রিপোজিটরি তৈরি করুন বা ব্যবহার করুন যা একটি ডেটা `Stream` প্রদান\nকরে। উদাহরণস্বরূপ, নিচের রিপোজিটরিটি কিছু অ্যাপ আইডিয়ার একটি অবিরাম স্ট্রিম\nপ্রকাশ করে:\n\n<AppIdeasRepositorySnippet />\n\nএকই রিপোজিটরিটি প্রতিটি ব্লকে ইঞ্জেক্ট করা যেতে পারে যা নতুন অ্যাপ আইডিয়ার জন্য\nরেসপন্ড করতে চায়। নিচে একটি `AppIdeaRankingBloc` দেখানো হলো যা রিপোজিটরি থেকে\nপ্রতিটি আসা আইডিয়ার জন্য স্টেট প্রকাশ করে:\n\n<AppIdeaRankingBlocSnippet />\n\nব্লকের স্ট্রিম এবং কনকারেন্সি ব্যবহার করার বিষয়ে আরও জানার জন্য দেখুন\n[How to use Bloc with streams and concurrency](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency)।\n\n## প্রেজেন্টেশন লেয়ার\n\nপ্রেজেন্টেশন লেয়ারের দায়িত্ব হলো এক বা একাধিক ব্লক স্টেটের উপর ভিত্তি করে\nনিজেকে রেন্ডার করা। এছাড়াও এটি ইউজার ইনপুট এবং অ্যাপ্লিকেশন লাইফসাইকেল ইভেন্ট\nহ্যান্ডেল করে।\n\nঅধিকাংশ অ্যাপ্লিকেশন ফ্লো শুরু হয় একটি `AppStart` ইভেন্ট দিয়ে, যা ব্যবহারকারীর\nজন্য প্রদর্শনের জন্য কিছু ডেটা ফেচ করতে ট্রিগার করে।\n\nএই পরিস্থিতিতে, প্রেজেন্টেশন লেয়ার `AppStart` ইভেন্ট যোগ করবে।\n\nএছাড়াও, প্রেজেন্টেশন লেয়ারকে ব্লক লেয়ারের স্টেট অনুযায়ী স্ক্রিনে কী রেন্ডার\nকরতে হবে তা নির্ধারণ করতে হবে।\n\n<PresentationComponentSnippet />\n\nএখন পর্যন্ত, যদিও কিছু কোড স্নিপেট দেখানো হয়েছে, সবকিছু এখনও উচ্চ-স্তরের।\nটিউটোরিয়াল সেকশনে আমরা একাধিক উদাহরণ অ্যাপ বানিয়ে সবকিছু একত্রে দেখাবো।\n"
  },
  {
    "path": "docs/src/content/docs/bn/bloc-concepts.mdx",
    "content": "---\ntitle: ব্লক কনসেপ্টস\ndescription: package:bloc এর প্রধান ধারণাগুলোর একটি ওভারভিউ\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nঅনুগ্রহ করে package:bloc সম্পর্কিত নীচের অংশগুলো মনোযোগ দিয়ে পড়ুন।\n\n:::\n\nকয়েকটি মূল ধারণা আছে যেগুলো bloc প্যাকেজ ব্যবহার করার সময় বোঝা খুবই জরুরি।\n\nনিচের সেকশনে আমরা এগুলো বিস্তারিতভাবে আলোচনা করব এবং একটি কাউন্টার অ্যাপ দিয়ে\nউদাহরণ দেখাব।\n\n## স্ট্রিমস (Streams)\n\n:::note\n\nআরও তথ্যের জন্য অফিসিয়াল Dart ডকুমেন্টেশন দেখুন:\nhttps://dart.dev/tutorials/language/streams\n\n:::\n\nস্ট্রিম হল অ্যাসিঙ্ক্রোনাস ডেটার একটি ক্রম।\n\nbloc লাইব্রেরি ব্যবহার করার জন্য স্ট্রিমগুলোর একটি বেসিক ধারণা থাকা জরুরি।\n\nযদি আপনি স্ট্রিম সম্পর্কে অপরিচিত হন, তাহলে একটি পাইপে পানি প্রবাহের কল্পনা করুন\n— পাইপ হচ্ছে স্ট্রিম এবং পানি হচ্ছে অ্যাসিঙ্ক্রোনাস ডেটা।\n\nআমরা Dart-এ একটি স্ট্রিম তৈরি করতে পারি একটি `async*` (async generator) ফাংশন\nলিখে।\n\n<CountStreamSnippet />\n\n`async*` ফাংশনে `yield` ব্যবহার করে আমরা স্ট্রিমে ডেটা পুশ করতে পারি। উপরের\nউদাহরণে আমরা `max` পর্যন্ত পূর্ণসংখ্যার একটি স্ট্রিম রিটার্ন করছি।\n\nস্ট্রিমের প্রতিটি `yield` কল একটি নতুন ডেটা আইটেম পাঠায়।\n\nউপরের স্ট্রিমটি আমরা বিভিন্নভাবে কনজিউম করতে পারি। যদি আমরা স্ট্রিমের সব সংখ্যার\nযোগফল ফেরত দিতে চাই, তাহলে একটি ফাংশন হতে পারে:\n\n<SumStreamSnippet />\n\nউপরের ফাংশনটি `async` হওয়ায় আমরা `await` ব্যবহার করে স্ট্রিম থেকে প্রতিটি মান\nসংগ্রহ করে একটি `Future<int>` রিটার্ন করছি।\n\nসবকিছু একসাথে করা যেতে পারে এইভাবে:\n\n<StreamsMainSnippet />\n\nস্ট্রিম সম্পর্কে এই মৌলিক ধারণা হলে আমরা bloc প্যাকেজের মূল উপাদান — Cubit —\nশেখার জন্য প্রস্তুত।\n\n## কিউবিট (Cubit)\n\nCubit হল একটি ক্লাস যা `BlocBase` থেকে এক্সটেন্ড করে এবং যেকোনো ধরনের state\nহ্যান্ডেল করতে ব্যবহৃত হয়।\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nCubit এমন ফাংশন এক্সপোজ করতে পারে যা কল করলে state পরিবর্তন ট্রিগার হয়।\n\nState হল Cubit-র আউটপুট এবং আপনার অ্যাপ্লিকেশনের অংশবিশেষকে প্রতিনিধিত্ব করে। UI\nকম্পোনেন্টগুলো current state অনুযায়ী রিল-রেন্ডার করতে পারে।\n\n:::note\n\nCubit-এর উত্স সম্পর্কে আরও জানতে এই ইস্যুটি পড়তে পারেন:\nhttps://github.com/felangel/cubit/issues/69\n\n:::\n\n### Cubit তৈরি করা\n\nআমরা একটি CounterCubit তৈরি করতে পারি এভাবে:\n\n<CounterCubitSnippet />\n\nCubit তৈরি করার সময় আমাদের state-এর টাইপ নির্ধারণ করতে হবে। CounterCubit-এর\nক্ষেত্রে state একটি `int`, কিন্তু জটিল পরিস্থিতিতে একটি `class` ব্যবহার করা যেতে\nপারে।\n\nআরেকটি গুরুত্বপূর্ণ বিষয় হল initial state নির্ধারণ করা। এটা আমরা `super`-এ\nভ্যালু দিয়ে করতে পারি। উপরের উদাহরণে initial state ভিতর থেকেই `0` সেট করা\nহয়েছে, অথবা Cubit কে বাহ্যিকভাবে আরও ফ্লেক্সিবল করা যায়:\n\n<CounterCubitInitialStateSnippet />\n\nএভাবে আমরা ভিন্ন initial state দিয়ে CounterCubit ইনস্ট্যানশিয়েট করতে পারি:\n\n<CounterCubitInstantiationSnippet />\n\n### Cubit state পরিবর্তন\n\nপ্রতিটি Cubit নতুন state আউটপুট করার জন্য `emit` ব্যবহার করে।\n\n<CounterCubitIncrementSnippet />\n\nউপরের উদাহরণে CounterCubit একটি public method `increment` এক্সপোজ করেছে।\n`increment` কল করলে আমরা current state (`state` getter) পড়ে `emit(state + 1)`\nকরি।\n\n:::caution\n\n`emit` মেথডটি প্রোটেক্টেড — অর্থাৎ এটি শুধুমাত্র Cubit-এর ভিতরে ব্যবহার করবেন।\n\n:::\n\n### Cubit ব্যবহার করা\n\nএখন আমরা CounterCubit তৈরি করে ব্যবহার করব।\n\n#### বেসিক ব্যবহার\n\n<CounterCubitBasicUsageSnippet />\n\nউপরের উদাহরণে আমরা প্রথমে CounterCubit ইনস্ট্যানশিয়েট করি। তারপর বর্তমান state\nপ্রিন্ট করি (এটি initial state)। এরপর `increment` কল করে state পরিবর্তন করি এবং\nআবার state প্রিন্ট করি। শেষে `Cubit.close()` করে স্ট্রিম বন্ধ করি।\n\n#### স্ট্রিম ব্যবহার\n\nCubit একটি স্ট্রিম এক্সপোজ করে যাতে রিয়েল-টাইম আপডেট নেওয়া যায়:\n\n<CounterCubitStreamUsageSnippet />\n\nউপরের উদাহরণে আমরা Cubit-এ subscribe করে প্রতিটি state পরিবর্তনে print করছি,\nতারপর `increment` কল করছি এবং শেষে `subscription.cancel()` করে সাবস্ক্রিপশন বন্ধ\nকরছি। `Cubit.close()` করে Cubit বন্ধ করা হয়।\n\n:::note\n\nউদাহরণে `await Future.delayed(Duration.zero)` দেওয়া আছে যাতে সাবস্ক্রিপশন\nএকেবারেই তাড়াতাড়ি ক্যানসেল না হয়ে পরের ইভেন্ট-লুপ মিলানো যায়।\n\n:::\n\n:::caution\n\nCubit-এ listen করলে শুধুমাত্র পরবর্তী state পরিবর্তনগুলোই পাবেন (past states\nনয়)।\n\n:::\n\n### Cubit পর্যবেক্ষণ (Observing a Cubit)\n\nCubit একটি নতুন state emit করলে সেটা একটি `Change` হিসেবে ধরা হয়। Cubit-এর\n`onChange` override করে আমরা সব চেঞ্জ পর্যবেক্ষণ করতে পারি:\n\n<CounterCubitOnChangeSnippet />\n\nপরে Cubit চালিয়ে আমরা কনসোলে সব চেঞ্জ দেখতে পারি:\n\n<CounterCubitOnChangeUsageSnippet />\n\nউপরের উদাহরণ আউটপুট হবে:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change` ঘটে ঠিক আগে যখন Cubit-এর state আপডেট হয়। একটি `Change`-এ\n`currentState` এবং `nextState` থাকে।\n\n:::\n\n#### BlocObserver\n\nbloc লাইব্রেরির একটি সুবিধা হচ্ছে আমরা সব `Changes` এক জায়গায় দেখতে পারি। বড়\nঅ্যাপে বহু Cubit থাকতে পারে, তাই সব Changes কেন্দ্রিয়ভাবে হ্যান্ডল করতে\nBlocObserver বানানো যেতে পারে।\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nশুধু `BlocObserver` এক্সটেন্ড করে `onChange` override করলেই হয়।\n\n:::\n\nSimpleBlocObserver ব্যবহার করতে main-এ একটু পরিবর্তন করতে হবে:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nতারপর আউটপুট হবে:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nলোকাল (internal) `onChange` আগে কল হয় এবং `super.onChange` ডাকে যাতে global\nBlocObserver-ও নোটিফাই হয়।\n\n:::\n\n:::tip\n\nBlocObserver-এ আপনার কাছে Cubit ইনস্ট্যান্সও অ্যাক্সেস থাকে, change ছাড়াও।\n\n:::\n\n### Cubit ত্রুটি হ্যান্ডলিং\n\nপ্রতি Cubit-এর `addError` মেথড আছে যা কোনো ত্রুটি ইঙ্গিত করতে ব্যবহার করা যায়।\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\nCubit-এর ভিতরে `onError` ওভাররাইড করে নির্দিষ্ট Cubit-এর সব এরর হ্যান্ডল করা\nযায়।\n\n:::\n\nগ্লোবালি সব রিপোর্টেড এরর হ্যান্ডল করতে BlocObserver-এ `onError` ওভাররাইড করা\nযায়।\n\n<SimpleBlocObserverOnErrorSnippet />\n\nউপরের প্রোগ্রাম চালালে আমরা দেখতে পাব:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nBloc একটি উন্নত ক্লাস যা ইভেন্ট-ভিত্তিকভাবে state পরিবর্তন করে; এটি `BlocBase`-ও\nএক্সটেন্ড করে। Cubit-এর তুলনায় Bloc-এ সরাসরি ফাংশন কল করে state পরিবর্তন না\nকরে, পরিবর্তে ইভেন্ট যোগ করে Bloc সেই ইভেন্টগুলোকে state-এ রূপান্তর করে।\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Bloc তৈরি করা\n\nBloc তৈরি করা Cubit-এর মতোই, বরং অতিরিক্তভাবে আপনাকে কোন ইভেন্টগুলি Bloc\nপ্রক্রিয়াকরণ করবে তা নির্ধারণ করতে হবে।\n\nইভেন্ট হল Bloc-এর ইনপুট। সাধারণত ইউজার অ্যাকশন (বাটন ট্যাপ) বা লাইফসাইকেল\nইভেন্টের ফলে ইভেন্ট যোগ করা হয়।\n\n<CounterBlocSnippet />\n\nCubit-এর মতোই Bloc-এও initial state `super`-এ দিয়ে নির্ধারণ করতে হবে।\n\n### Bloc state পরিবর্তন\n\nBloc-এ ইভেন্ট-হ্যান্ডলার অনুজায়ীভাবে `on<Event>` API দিয়ে রেজিস্টার করা হয়।\nইভেন্ট-হ্যান্ডলার ইনকামিং ইভেন্ট থেকে শূন্য বা একাধিক স্টেট ইমিট করে।\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nEventHandler-এ আপনার কাছে event এবং একটি `Emitter` থাকবে, যেটি ব্যবহার করে শূন্য\nবা একাধিক state ইমিট করা যায়।\n\n:::\n\nআমরা এখন CounterIncrementPressed ইভেন্ট হ্যান্ডল করতে পারি:\n\n<CounterBlocIncrementSnippet />\n\nউপরের উদাহরণে `on<CounterIncrementPressed>` রেজিস্টার করা হয়েছে। প্রতিটি\nইভেন্টে current state পড়ে `emit(state + 1)` করা হচ্ছে।\n\n:::note\n\nBloc ক্লাস `BlocBase` এক্সটেন্ড করে, তাই current state যেকোনো সময় `state`\nগেটারের মাধ্যমে অ্যাক্সেস করা যাবে — ঠিক Cubit-এর মতোই।\n\n:::\n\n:::caution\n\nBlocs সরাসরি `emit` করা উচিত নয়; প্রতিটি state change অবশ্যই\nইভেন্ট-হ্যান্ডলারের মধ্যে ইমিট করা উচিত।\n\n:::\n\n:::caution\n\nBlocs এবং Cubits উভয়ই duplicate states উপেক্ষা করে। যদি emit করা nextState\nপূর্ববর্তী state এর সমান হয়, তাহলে কোন স্টেট পরিবর্তন হবে না।\n\n:::\n\n### Bloc ব্যবহার করা\n\nএখন আমরা CounterBloc ইনস্ট্যানশিয়েট করে ব্যবহার করব।\n\n#### বেসিক ব্যবহার\n\n<CounterBlocUsageSnippet />\n\nউপরের উদাহরণে আমরা Bloc তৈরি করে current state প্রিন্ট করেছি, তারপর\n`CounterIncrementPressed` ইভেন্ট add করে state পরিবর্তন করেছি এবং শেষেও\n`close()` করে Bloc বন্ধ করেছি।\n\n:::note\n\nইভেন্ট-হ্যান্ডলারকে প্রক্রিয়া করার জন্য `await Future.delayed(Duration.zero)`\nযোগ করা হয়েছে যাতে পরের ইভেন্ট-লুপের ইটারেশন পর্যন্ত অপেক্ষা করা যায়।\n\n:::\n\n#### স্ট্রিম ব্যবহার\n\nCubit-এর মতোই Bloc-ও স্ট্রিম; আপনি Bloc সাবস্ক্রাইব করে রিয়েল-টাইম state আপডেট\nপেতে পারেন:\n\n<CounterBlocStreamUsageSnippet />\n\nউপরের উদাহরণে আমরা Bloc সাবস্ক্রাইব করে প্রতিটি state পরিবর্তন print করছি, তারপর\nইভেন্ট add করছি এবং শেষে `subscription.cancel()` ও `Bloc.close()` করেছি।\n\n:::note\n\nউদাহরণে `await Future.delayed(Duration.zero)` দেওয়া হয়েছে যাতে সাবস্ক্রিপশন\nসরাসরি ক্যানসেল না হয়।\n\n:::\n\n### Bloc পর্যবেক্ষণ\n\nCubit-এর মতো Bloc-এও `onChange` override করে state পরিবর্তন পর্যবেক্ষণ করা যায়।\n\n<CounterBlocOnChangeSnippet />\n\nmain.dart আপডেট করলে:\n\n<CounterBlocOnChangeUsageSnippet />\n\nএরপর আউটপুট হবে:\n\n<CounterBlocOnChangeOutputSnippet />\n\nBloc-এর একটি গুরুত্বপূর্ণ পার্থক্য হল এটি ইভেন্ট-চৌকশ হওয়ায় আমরা ঠিক কী ইভেন্ট\nট্রিগার করেছে তা ট্র্যাক করতে পারি। এজন্য `onTransition` override করে আমরা\ntransition সম্পর্কিত তথ্য পেতে পারি।\n\nTransition হচ্ছে current state, event এবং next state — এই তিনটির মিশ্রণ।\n\n<CounterBlocOnTransitionSnippet />\n\nউপরের main-টি আবার চালালে আমরা পাব:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` `onChange`-এর আগে invoked হয় এবং এতে কোন ইভেন্টে `currentState`\nথেকে `nextState`-এ পরিবর্তন ঘটল তা থাকে।\n\n:::\n\n#### BlocObserver\n\nওইভাবে BlocObserver-এ `onTransition` override করলে সব Bloc-এ ঘটা transition এক\nজায়গায় দেখা যাবে।\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nSimpleBlocObserver initialization ঠিক আগের মতোই করা যাবে:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nএরপর আউটপুট হবে:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` প্রথমে (লোকাল আগে, গ্লোবাল পরে) কল হয়ে থাকে, এরপর `onChange`\nচলে।\n\n:::\n\nআরেকটি ইউনিক ফিচার হল Bloc-এ `onEvent` override করা যায় — যেটা তখন কল হয় যখন\nনতুন ইভেন্ট যোগ করা হয়। লোকাল `onEvent` প্রথমে, তারপর global\n`BlocObserver.onEvent` কল হয়।\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nতারপর আউটপুট হবে:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` ইভেন্ট যোগ করার সঙ্গে সঙ্গেই কল হয়। লোকাল `onEvent` আগে, গ্লোবাল পরে\nকল হয়।\n\n:::\n\n### Bloc ত্রুটি হ্যান্ডলিং\n\nCubit-ও Bloc-ও `addError` এবং `onError` মেথড প্রদান করে। Bloc-এর ভেতর কোথাও\n`addError` কল করলে that error রিপোর্ট হবে এবং local অথবা global `onError`-এ\nহ্যান্ডল করা যাবে।\n\n<CounterBlocOnErrorSnippet />\n\nউপরের main-টি আবার চালালে error রিপোর্ট কেমন লাগে তা দেখা যাবে:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nলোকাল `onError` আগে, তারপর গ্লোবাল `BlocObserver.onError` কল হয়।\n\n:::\n\n:::note\n\n`onError` এবং `onChange` উভয়ই Bloc ও Cubit-এ একইভাবে কাজ করে।\n\n:::\n\n:::caution\n\nEventHandler-এ কোনো unhandled exception হলে সেটাও `onError`-এ রিপোর্ট করা হয়।\n\n:::\n\n## Cubit vs Bloc\n\nCubit এবং Bloc-এর মৌলিক পার্থক্য এখন covered হয়েছে; আপনি হয়তো চাইবেন কখন Cubit\nব্যবহার করবেন এবং কখন Bloc।\n\n### Cubit সুবিধা\n\n#### সরলতা\n\nCubit-এর প্রধান সুবিধা হল সরলতা। Cubit তৈরি করতে কেবল state এবং সেই state\nপরিবর্তনের জন্য যে ফাংশনগুলো দরকার সেগুলোই লিখতে হয়। Bloc-এ events, states এবং\nEventHandler লিখতে হয় — ফলে Cubit কোড কম এবং বোঝা সহজ।\n\nনিচে দুইটি কাউন্টার ইমপ্লিমেন্টেশন:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nCubit ইমপ্লিমেন্টেশন সংক্ষিপ্ত; এখানে আলাদা ইভেন্ট ডিফাইন করার দরকার পড়ে না —\nফাংশনগুলো ইভেন্টের কাজ করে। Cubit-এ `emit` যেখানেই প্রয়োজন সেখানেই করা যায়।\n\n### Bloc সুবিধা\n\n#### ট্রেসযোগ্যতা (Traceability)\n\nBloc-এর বড় সুবিধা হল state পরিবর্তনের ক্রম এবং ঠিক কী কারণে পরিবর্তন ঘটল সেটা\nট্র্যাক করা যায়। `AuthenticationState` এর মত গুরুত্বপূর্ণ state-এ Bloc-এর\nমাধ্যমে ইভেন্ট-ভিত্তিক লগ রাখা বেশ কার্যকর।\n\nসরলতার জন্য `AuthenticationState` কে enum ধরে নেওয়া যাক:\n\n<AuthenticationStateSnippet />\n\nstate পরিবর্তনের অনেক কারণ থাকতে পারে — উদাহরণ: ইউজার লগআউট ট্যাপ করেছে অথবা\nটোকেন বাতিল হয়েছে। Bloc-এ Transition আপনাকে জানায় কোন ইভেন্টের ফলে state\nবদলেছে:\n\n<AuthenticationTransitionSnippet />\n\nএই Transition আমাদের প্রয়োজনীয় তথ্য দেয়। Cubit ব্যবহার করলে আমরা শুধুমাত্র\nChange দেখতে পারি:\n\n<AuthenticationChangeSnippet />\n\nএই লোগে বলা থাকে যে ব্যবহারকারী লগআউট হয়েছে, কিন্তু কেন তা স্পষ্ট থাকে না — যা\nডিবাগিং-এ অসুবিধা করতে পারে।\n\n#### অ্যাডভান্সড ইভেন্ট ট্রান্সফর্মেশন\n\nআরেকটি জায়গা যেখানে Bloc এগিয়ে তা হলো reactive অপারেটর (`buffer`,\n`debounceTime`, `throttle` ইত্যাদি) ব্যবহার করে ইনকামিং ইভেন্টগুলো কিভাবে\nট্রান্সফর্ম করা হবে তা নিয়ন্ত্রণ করা যায়।\n\n:::tip\n\nstream_transform এবং rxdart প্যাকেজগুলো দেখুন:\nhttps://pub.dev/packages/stream_transform , https://pub.dev/packages/rxdart\n\n:::\n\nBloc-এ ইভেন্ট সিংক থাকে যা ইনকামিং ইভেন্টের ফ্লো কন্ট্রোল ও ট্রান্সফর্ম করতে\nদেয়।\n\nউদাহরণস্বরূপ, রিয়েল-টাইম সার্চে অনুরোধগুলো debounce করলে ব্যাকেন্ড-এ রেট\nলিমিটিং বা অতিরিক্ত লোড কমানো যায়।\n\nBloc-এ কাস্টম `EventTransformer` ব্যবহার করে ইনকামিং ইভেন্ট ডিবাউন্স করা যায়:\n\n<DebounceEventTransformerSnippet />\n\nউপরের কোড দিয়ে ইনকামিং ইভেন্ট সহজে ডিবাউন্স করা যায়।\n\n:::tip\n\nbloc_concurrency প্যাকেজ দেখুন; এটি কিছু প্রচলিত EventTransformer প্রদান করে:\nhttps://pub.dev/packages/bloc_concurrency\n\n:::\n\nনিশ্চয় না হলে Cubit দিয়ে শুরু করুন; পরে প্রয়োজনে Bloc-এ রিফ্যাক্টর করতে\nপারবেন।\n"
  },
  {
    "path": "docs/src/content/docs/bn/faqs.mdx",
    "content": "---\ntitle: FAQs\ndescription: bloc লাইব্রেরি সম্পর্কিত প্রায়শই জিজ্ঞাসিত প্রশ্নগুলোর উত্তর।\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## State Not Updating\n\n❔ **প্রশ্ন**: আমি আমার bloc-এ state emit করছি কিন্তু UI আপডেট হচ্ছে না। আমি কী\nভুল করছি?\n\n💡 **উত্তর**: আপনি যদি Equatable ব্যবহার করেন তাহলে props getter-এ অবশ্যই সমস্ত\nপ্রপার্টি পাস করছেন কিনা নিশ্চিত করুন।\n\n✅ **ভাল**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **খারাপ**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nএছাড়াও, bloc-এর ভিতরে আপনি একটি নতুন state instance emit করছেন তা নিশ্চিত করুন।\n\n✅ **ভাল**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **খারাপ**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\n`Equatable` প্রপার্টিগুলো কখনও modify করা উচিত নয়, বরং সবসময় copy করা উচিত।  \nযদি কোনো `Equatable` ক্লাসে `List` বা `Map` থাকে, তবে রেফারেন্সের উপর ভিত্তি না\nকরে মানের উপর ভিত্তি করে equality নির্ধারণ করতে যথাক্রমে `List.of` বা `Map.of`\nব্যবহার করুন।\n\n:::\n\n## Equatable কবে ব্যবহার করবেন\n\n❔**প্রশ্ন**: কবে আমি Equatable ব্যবহার করব?\n\n💡**উত্তর**:\n\n<EquatableEmitSnippet />\n\nউপরের ক্ষেত্রে যদি `StateA` `Equatable` এক্সটেন্ড করে তবে শুধুমাত্র একটি state\nchange হবে (দ্বিতীয় emit উপেক্ষা করা হবে)। সাধারণভাবে, আপনি যদি rebuild কমিয়ে\nঅপ্টিমাইজ করতে চান তাহলে `Equatable` ব্যবহার করা উচিত। আপনি যদি একই state\nback-to-back হলেও একাধিক transition চান তাহলে `Equatable` ব্যবহার করা উচিত নয়।\n\nএছাড়াও, `Equatable` ব্যবহারে bloc টেস্ট করা সহজ হয় কারণ আমরা নির্দিষ্ট স্টেট\ninstance আশা করতে পারি `Matchers` বা `Predicates` ব্যবহারের পরিবর্তে।\n\n<EquatableBlocTestSnippet />\n\n`Equatable` ছাড়া উপরের টেস্ট ব্যর্থ হবে এবং এভাবে পুনঃলিখন করতে হবে:\n\n<NoEquatableBlocTestSnippet />\n\n## ত্রুটি (Error) হ্যান্ডলিং\n\n❔ **প্রশ্ন**: আগের ডেটা দেখানো অবস্থায় কিভাবে আমি error হ্যান্ডেল করব?\n\n💡 **উত্তর**:\n\nএটি bloc-এর state কিভাবে মডেল করা হয়েছে তার উপর অনেকটাই নির্ভর করে। এমন\nক্ষেত্রে যেখানে error থাকলেও ডেটা রেখে দেওয়া উচিত, একটি single state class\nব্যবহার করার কথা বিবেচনা করুন।\n\n<SingleStateSnippet />\n\nএতে widget গুলো একসাথে `data` এবং `error` পাওয়ার সুবিধা পাবে এবং bloc\n`state.copyWith` ব্যবহার করে error থাকলেও পুরনো ডেটা ধরে রাখতে পারবে।\n\n<SingleStateUsageSnippet />\n\n## Bloc বনাম Redux\n\n❔ **প্রশ্ন**: Bloc এবং Redux-এর মধ্যে পার্থক্য কী?\n\n💡 **উত্তর**:\n\nBLoC একটি ডিজাইন প্যাটার্ন যা নিম্নলিখিত নিয়ম দ্বারা সংজ্ঞায়িত:\n\n1. BLoC-এর Input এবং Output শুধু Streams এবং Sinks।\n2. Dependencies injectable এবং প্ল্যাটফর্ম-অ্যাগনস্টিক হতে হবে।\n3. কোনো প্ল্যাটফর্ম branching অনুমোদিত নয়।\n4. উপরের নিয়ম মেনে চললে যেকোনো implementation ব্যবহার করা যায়।\n\nUI নির্দেশিকা:\n\n1. প্রতিটি “যথেষ্ট জটিল” কম্পোনেন্টের একটি BLoC থাকে।\n2. কম্পোনেন্টগুলো ইনপুট পাঠাবে “as is”।\n3. কম্পোনেন্টগুলো আউটপুট দেখাবে যতটা সম্ভব “as is”।\n4. সকল branching হবে সহজ BLoC boolean আউটপুটের উপর ভিত্তি করে।\n\nBloc লাইব্রেরি BLoC ডিজাইন প্যাটার্ন বাস্তবায়ন করে এবং RxDart abstraction দিয়ে\ndeveloper experience সহজ করে।\n\nRedux-এর তিনটি নীতি:\n\n1. Single source of truth\n2. State read-only\n3. পরিবর্তন pure functions দিয়ে করা হয়\n\nbloc লাইব্রেরি প্রথম নীতি লঙ্ঘন করে; bloc state বহু bloc-এ বিতরণ করা হয়।\nএছাড়াও bloc-এ middleware-এর ধারণা নেই এবং async state পরিবর্তন খুব সহজ, যেখানে\nএকটি event-এর জন্য একাধিক state emit করা যায়।\n\n## Bloc বনাম Provider\n\n❔ **প্রশ্ন**: Bloc আর Provider-এর মধ্যে পার্থক্য কী?\n\n💡 **উত্তর**: `provider` dependency injection-এর জন্য তৈরি (এটি\n`InheritedWidget` কে wrap করে)।  \nআপনাকে এখনও state ম্যানেজমেন্ট বেছে নিতে হবে (`ChangeNotifier`, `Bloc`, `Mobx`,\nইত্যাদি)।  \nBloc লাইব্রেরি অভ্যন্তরীণভাবে `provider` ব্যবহার করে widget tree তে bloc সরবরাহ\nও ব্যবহারের সুবিধার জন্য।\n\n## BlocProvider.of() Bloc খুঁজে পাচ্ছে না\n\n❔ **প্রশ্ন**: `BlocProvider.of(context)` ব্যবহার করলে bloc খুঁজে পায় না। আমি\nকীভাবে এটি ঠিক করব?\n\n💡 **উত্তর**: একই context থেকে bloc অ্যাক্সেস করা যায় না যেখানে এটি provide করা\nহয়েছে। তাই নিশ্চিত করুন `BlocProvider.of()` একটি child `BuildContext` এর ভিতরে\nকল হচ্ছে।\n\n✅ **ভাল**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **খারাপ**\n\n<BlocProviderBad1Snippet />\n\n## Project Structure\n\n❔ **প্রশ্ন**: আমি আমার প্রজেক্ট কীভাবে গঠন করব?\n\n💡 **উত্তর**: এই প্রশ্নের আসলে ঠিক/ভুল বলে কিছু নেই,  \nকিছু সুপারিশকৃত উদাহরণ হলো:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nসবচেয়ে গুরুত্বপূর্ণ হলো **একটি সুনির্দিষ্ট** এবং **উদ্দেশ্যপূর্ণ** project\nstructure বজায় রাখা।\n\n## Bloc-এর ভিতরে Events যোগ করা\n\n❔ **প্রশ্ন**: একটি bloc-এর ভিতরে event যোগ করা কি ঠিক?\n\n💡 **উত্তর**: বেশিরভাগ ক্ষেত্রে event বাইরে থেকে যোগ করা উচিত, তবে কিছু ক্ষেত্রে\nভিতরে যোগ করা যুক্তিসঙ্গত।\n\nসবচেয়ে সাধারণ অভ্যন্তরীণ event ব্যবহারের পরিস্থিতি হলো যখন repository থেকে\nreal-time আপডেটে state পরিবর্তন করতে হয়। এখানে repository হল state পরিবর্তনের\nstimulus, কোনো বাহ্যিক ইভেন্ট (যেমন বাটন ট্যাপ) নয়।\n\nনিচের উদাহরণে, `MyBloc` এর state নির্ভর করে বর্তমান user-এর উপর, যা\n`UserRepository` থেকে `Stream<User>` হিসাবে আসে।  \n`MyBloc` user stream শুনে এবং যখনই একটি user emit হয় তখন অভ্যন্তরীণ\n`_UserChanged` event যোগ করে।\n\n<BlocInternalAddEventSnippet />\n\nঅভ্যন্তরীণ event যোগ করে আমরা event-এর জন্য custom `transformer` নির্ধারণ করতে\nপারি — ডিফল্টভাবে সেগুলো concurrent প্রসেস হয়।\n\nInternal event ব্যক্তিগত (private) রাখা অত্যন্ত সুপারিশ করা হয়।  \nএটি স্পষ্টভাবে বোঝায় যে event শুধুমাত্র bloc-এর ভিতর ব্যবহৃত হয় এবং বাইরের\nকম্পোনেন্ট তা অ্যাক্সেস করতে পারে না।\n\n<BlocInternalEventSnippet />\n\nআমরা বিকল্পভাবে একটি বাহ্যিক `Started` event ব্যবহার করতে পারি এবং\n`emit.forEach` API ব্যবহার করে real-time user update হ্যান্ডেল করতে পারি:\n\n<BlocExternalForEachSnippet />\n\nএর সুবিধাসমূহ:\n\n- আমাদের internal `_UserChanged` event দরকার হয় না\n- `StreamSubscription` ম্যানুয়ালি ম্যানেজ করতে হয় না\n- bloc কখন user stream-এ subscribe করবে তার সম্পূর্ণ নিয়ন্ত্রণ থাকে\n\nঅসুবিধাসমূহ:\n\n- subscription সহজে `pause` বা `resume` করা যায় না\n- একটি public `Started` event প্রকাশ করতে হয় যা বাইরে থেকে add করতে হয়\n- user update প্রসেস করার উপায় কাস্টমাইজ করার জন্য custom `transformer` ব্যবহার\n  করা যায় না\n\n## Public Methods প্রকাশ করা\n\n❔ **প্রশ্ন**: আমার bloc বা cubit instance-এ public method প্রকাশ করা কি ঠিক?\n\n💡 **উত্তর**\n\nCubit তৈরি করার সময় শুধু state পরিবর্তনের জন্য public method রাখা সুপারিশ করা\nহয়।  \nসাধারণত cubit-এর সব public method `void` বা `Future<void>` return করা উচিত।\n\nBloc তৈরি করার সময় custom public method প্রকাশ না করে শুধুমাত্র event যোগ করে\n(`add`) bloc-কে notify করাই সুপারিশ করা হয়।\n"
  },
  {
    "path": "docs/src/content/docs/bn/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Flutter Bloc কনসেপ্টস\ndescription: package:flutter_bloc এর প্রধান ধারণাগুলোর একটি ওভারভিউ\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nঅনুগ্রহ করে package:flutter_bloc সম্পর্কিত নীচের অংশগুলো মনোযোগ দিয়ে পড়ুন।\n\n:::\n\n:::note\n\n`flutter_bloc` প্যাকেজ থেকে এক্সপোর্ট করা সব widgets `Cubit` এবং `Bloc` উভয়\nইনস্ট্যান্সের সাথেই কাজ করে।\n\n:::\n\n## Bloc Widgets\n\n### BlocBuilder\n\n**BlocBuilder** হল একটি Flutter widget যার জন্য একটি `Bloc` এবং একটি `builder`\nফাংশন প্রয়োজন। `BlocBuilder` নতুন states-এর প্রতিক্রিয়ায় widget তৈরি করে।\n`BlocBuilder` `StreamBuilder`-এর সাথে খুবই মিল কিন্তু boilerplate কোড কমাতে আরও\nসহজ API আছে। `builder` ফাংশনটি অনেকবার কল হতে পারে এবং এটি একটি\n[pure function](https://en.wikipedia.org/wiki/Pure_function) হওয়া উচিত যা\nstate-এর প্রতিক্রিয়ায় একটি widget রিটার্ন করে।\n\nState পরিবর্তনের প্রতিক্রিয়ায় কোনো কাজ করতে চাইলে (যেমন navigation, dialog\nদেখানো ইত্যাদি) `BlocListener` দেখুন।\n\n`bloc` প্যারামিটার বাদ দিলে, `BlocBuilder` স্বয়ংক্রিয়ভাবে `BlocProvider` এবং\ncurrent `BuildContext` ব্যবহার করে lookup করবে।\n\n<BlocBuilderSnippet />\n\nশুধুমাত্র তখনই bloc নির্দিষ্ট করুন যখন আপনি একটি bloc প্রদান করতে চান যা একটি\nsingle widget-এ scoped হবে এবং parent `BlocProvider` এবং current `BuildContext`\nদিয়ে অ্যাক্সেসযোগ্য নয়।\n\n<BlocBuilderExplicitBlocSnippet />\n\n`builder` ফাংশন কখন কল হবে তার উপর fine-grained control-এর জন্য একটি optional\n`buildWhen` প্রদান করা যেতে পারে। `buildWhen` previous bloc state এবং current\nbloc state নেয় এবং একটি boolean রিটার্ন করে। যদি `buildWhen` true রিটার্ন করে,\n`builder` `state` সহ কল হবে এবং widget rebuild হবে। যদি `buildWhen` false\nরিটার্ন করে, `builder` `state` সহ কল হবে না এবং rebuild হবে না।\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** হল একটি Flutter widget যা `BlocBuilder`-এর অনুরূপ কিন্তু\nডেভেলপারদের current bloc state-এর উপর ভিত্তি করে একটি নতুন মান নির্বাচন করে\nআপডেট ফিল্টার করতে দেয়। নির্বাচিত মান পরিবর্তন না হলে অপ্রয়োজনীয় builds\nপ্রতিরোধ করা হয়। `BlocSelector`-এর জন্য সঠিকভাবে নির্ধারণ করতে যে `builder`\nআবার কল করা উচিত কিনা, নির্বাচিত মান অবশ্যই immutable হতে হবে।\n\n`bloc` প্যারামিটার বাদ দিলে, `BlocSelector` স্বয়ংক্রিয়ভাবে `BlocProvider` এবং\ncurrent `BuildContext` ব্যবহার করে lookup করবে।\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** হল একটি Flutter widget যা `BlocProvider.of<T>(context)` এর\nমাধ্যমে তার children-দের একটি bloc প্রদান করে। এটি dependency injection (DI)\nwidget হিসেবে ব্যবহৃত হয় যাতে একটি subtree-এর মধ্যে একাধিক widgets-এ একটি\nbloc-এর single instance প্রদান করা যায়।\n\nবেশিরভাগ ক্ষেত্রে, `BlocProvider` নতুন blocs তৈরি করতে ব্যবহার করা উচিত যা\nsubtree-এর বাকি অংশে উপলব্ধ করা হবে। এই ক্ষেত্রে, যেহেতু `BlocProvider` bloc\nতৈরি করার জন্য দায়ী, এটি স্বয়ংক্রিয়ভাবে bloc বন্ধ করার কাজটি handle করবে।\n\n<BlocProviderSnippet />\n\nডিফল্টভাবে, `BlocProvider` bloc-টি lazily তৈরি করবে, অর্থাৎ `create` তখনই\nএক্সিকিউট হবে যখন bloc-টি `BlocProvider.of<BlocA>(context)` এর মাধ্যমে lookup\nকরা হবে।\n\nএই আচরণ override করতে এবং `create`-কে অবিলম্বে চালানোর জন্য, `lazy` কে `false`\nসেট করা যেতে পারে।\n\n<BlocProviderEagerSnippet />\n\nকিছু ক্ষেত্রে, `BlocProvider` widget tree-এর একটি নতুন অংশে একটি existing bloc\nপ্রদান করতে ব্যবহার করা যেতে পারে। এটি সাধারণত ব্যবহৃত হয় যখন একটি existing\nbloc-কে একটি নতুন route-এ উপলব্ধ করতে হবে। এই ক্ষেত্রে, `BlocProvider`\nস্বয়ংক্রিয়ভাবে bloc বন্ধ করবে না যেহেতু এটি এটি তৈরি করেনি।\n\n<BlocProviderValueSnippet />\n\nতারপর `ChildA`, অথবা `ScreenA` থেকে আমরা `BlocA` পেতে পারি:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** হল একটি Flutter widget যা একাধিক `BlocProvider` widgets-কে\nএকটিতে merge করে। `MultiBlocProvider` readability উন্নত করে এবং একাধিক\n`BlocProviders` nest করার প্রয়োজন দূর করে। `MultiBlocProvider` ব্যবহার করে আমরা\nযেতে পারি:\n\n<NestedBlocProviderSnippet />\n\nথেকে:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nযখন একটি `BlocProvider` একটি `MultiBlocProvider`-এর context-এর মধ্যে সংজ্ঞায়িত\nকরা হয়, যেকোনো `child` উপেক্ষা করা হবে।\n\n:::\n\n### BlocListener\n\n**BlocListener** হল একটি Flutter widget যা একটি `BlocWidgetListener` এবং একটি\noptional `Bloc` নেয় এবং bloc-এ state পরিবর্তনের প্রতিক্রিয়ায় `listener`\ninvoke করে। এটি এমন functionality-এর জন্য ব্যবহার করা উচিত যার জন্য প্রতি state\nপরিবর্তনে একবার ঘটতে হবে যেমন navigation, `SnackBar` দেখানো, `Dialog` দেখানো\nইত্যাদি...\n\n`listener` প্রতিটি state change-এর জন্য শুধুমাত্র একবার কল হয় (**NOT** initial\nstate সহ) `BlocBuilder`-এ `builder`-এর মতো নয় এবং এটি একটি `void` ফাংশন।\n\n`bloc` প্যারামিটার বাদ দিলে, `BlocListener` স্বয়ংক্রিয়ভাবে `BlocProvider` এবং\ncurrent `BuildContext` ব্যবহার করে lookup করবে।\n\n<BlocListenerSnippet />\n\nশুধুমাত্র তখনই bloc নির্দিষ্ট করুন যখন আপনি একটি bloc প্রদান করতে চান যা\nঅন্যথায় `BlocProvider` এবং current `BuildContext` দিয়ে অ্যাক্সেসযোগ্য নয়।\n\n<BlocListenerExplicitBlocSnippet />\n\n`listener` ফাংশন কখন কল হবে তার উপর fine-grained control-এর জন্য একটি optional\n`listenWhen` প্রদান করা যেতে পারে। `listenWhen` previous bloc state এবং current\nbloc state নেয় এবং একটি boolean রিটার্ন করে। যদি `listenWhen` true রিটার্ন করে,\n`listener` `state` সহ কল হবে। যদি `listenWhen` false রিটার্ন করে, `listener`\n`state` সহ কল হবে না।\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** হল একটি Flutter widget যা একাধিক `BlocListener` widgets-কে\nএকটিতে merge করে। `MultiBlocListener` readability উন্নত করে এবং একাধিক\n`BlocListeners` nest করার প্রয়োজন দূর করে। `MultiBlocListener` ব্যবহার করে আমরা\nযেতে পারি:\n\n<NestedBlocListenerSnippet />\n\nথেকে:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nযখন একটি `BlocListener` একটি `MultiBlocListener`-এর context-এর মধ্যে সংজ্ঞায়িত\nকরা হয়, যেকোনো `child` উপেক্ষা করা হবে।\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** নতুন states-এর প্রতিক্রিয়ায় একটি `builder` এবং `listener`\nএক্সপোজ করে। `BlocConsumer` একটি nested `BlocListener` এবং `BlocBuilder`-এর\nঅনুরূপ কিন্তু প্রয়োজনীয় boilerplate-এর পরিমাণ কমায়। `BlocConsumer` শুধুমাত্র\nতখনই ব্যবহার করা উচিত যখন `bloc`-এ state changes-এর জন্য UI rebuild এবং অন্যান্য\nreactions উভয়ই এক্সিকিউট করা প্রয়োজন। `BlocConsumer` একটি required\n`BlocWidgetBuilder` এবং `BlocWidgetListener` এবং একটি optional `bloc`,\n`BlocBuilderCondition`, এবং `BlocListenerCondition` নেয়।\n\n`bloc` প্যারামিটার বাদ দিলে, `BlocConsumer` স্বয়ংক্রিয়ভাবে `BlocProvider` এবং\ncurrent `BuildContext` ব্যবহার করে lookup করবে।\n\n<BlocConsumerSnippet />\n\n`listener` এবং `builder` কখন কল হবে তার উপর আরও granular control-এর জন্য একটি\noptional `listenWhen` এবং `buildWhen` ইমপ্লিমেন্ট করা যেতে পারে। `listenWhen`\nএবং `buildWhen` প্রতিটি `bloc` `state` change-এ invoke হবে। তারা প্রতিটি\nprevious `state` এবং current `state` নেয় এবং অবশ্যই একটি `bool` রিটার্ন করতে\nহবে যা নির্ধারণ করে `builder` এবং/অথবা `listener` ফাংশন invoke হবে কিনা।\nPrevious `state` `bloc`-এর `state`-এ initialize হবে যখন `BlocConsumer`\ninitialize হয়। `listenWhen` এবং `buildWhen` optional এবং যদি তারা ইমপ্লিমেন্ট\nনা করা হয়, তারা `true`-তে default হবে।\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** হল একটি Flutter widget যা\n`RepositoryProvider.of<T>(context)` এর মাধ্যমে তার children-দের একটি repository\nপ্রদান করে। এটি dependency injection (DI) widget হিসেবে ব্যবহৃত হয় যাতে একটি\nsubtree-এর মধ্যে একাধিক widgets-এ একটি repository-এর single instance প্রদান করা\nযায়। `BlocProvider` blocs প্রদান করতে ব্যবহার করা উচিত যেখানে\n`RepositoryProvider` শুধুমাত্র repositories-এর জন্য ব্যবহার করা উচিত।\n\n<RepositoryProviderSnippet />\n\nতারপর `ChildA` থেকে আমরা `Repository` instance পেতে পারি:\n\n<RepositoryProviderLookupSnippet />\n\nযে repositories resources manage করে যা dispose করতে হবে তারা `dispose`\ncallback-এর মাধ্যমে তা করতে পারে:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** হল একটি Flutter widget যা একাধিক\n`RepositoryProvider` widgets-কে একটিতে merge করে। `MultiRepositoryProvider`\nreadability উন্নত করে এবং একাধিক `RepositoryProvider` nest করার প্রয়োজন দূর\nকরে। `MultiRepositoryProvider` ব্যবহার করে আমরা যেতে পারি:\n\n<NestedRepositoryProviderSnippet />\n\nথেকে:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nযখন একটি `RepositoryProvider` একটি `MultiRepositoryProvider`-এর context-এর মধ্যে\nসংজ্ঞায়িত করা হয়, যেকোনো `child` উপেক্ষা করা হবে।\n\n:::\n\n## BlocProvider ব্যবহার\n\nআসুন দেখি কিভাবে `BlocProvider` ব্যবহার করে একটি `CounterBloc` একটি\n`CounterPage`-এ প্রদান করা যায় এবং `BlocBuilder` দিয়ে state changes-এর\nপ্রতিক্রিয়া জানানো যায়।\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nএই মুহূর্তে আমরা আমাদের presentational layer-কে আমাদের business logic layer থেকে\nসফলভাবে আলাদা করেছি। লক্ষ্য করুন যে `CounterPage` widget-টি buttons-এ tap করলে\nকী ঘটে সে সম্পর্কে কিছুই জানে না। Widget-টি কেবল `CounterBloc`-কে বলে যে\nব্যবহারকারী increment অথবা decrement button চাপেছে।\n\n## RepositoryProvider ব্যবহার\n\nআমরা [`flutter_weather`][flutter_weather_link] উদাহরণের context-এ\n`RepositoryProvider` কিভাবে ব্যবহার করা যায় তা দেখব।\n\n<WeatherRepositorySnippet />\n\nআমাদের `main.dart`-এ, আমরা আমাদের `WeatherApp` widget-এর সাথে `runApp` কল করি।\n\n<WeatherMainSnippet />\n\nআমরা `RepositoryProvider`-এর মাধ্যমে আমাদের `WeatherRepository` instance-কে\nআমাদের widget tree-তে inject করব।\n\nএকটি bloc instantiate করার সময়, আমরা `context.read`-এর মাধ্যমে একটি\nrepository-এর instance অ্যাক্সেস করতে পারি এবং constructor-এর মাধ্যমে\nrepository-কে bloc-এ inject করতে পারি।\n\n<WeatherAppSnippet />\n\n:::tip\n\nআপনার যদি একাধিক repository থাকে, আপনি `MultiRepositoryProvider` ব্যবহার করে\nsubtree-তে একাধিক repository instances প্রদান করতে পারেন।\n\n:::\n\n:::note\n\n`RepositoryProvider` unmount হলে resources মুক্ত করার জন্য `dispose` callback\nব্যবহার করুন।\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension Methods\n\n[Extension methods](https://dart.dev/guides/language/extension-methods), Dart\n2.7-এ চালু, existing libraries-এ functionality যোগ করার একটি উপায়। এই সেকশনে,\nআমরা `package:flutter_bloc`-এ অন্তর্ভুক্ত extension methods এবং সেগুলো কিভাবে\nব্যবহার করা যায় তা দেখব।\n\n`flutter_bloc`-এর উপর একটি dependency আছে\n[package:provider](https://pub.dev/packages/provider) যা\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)-এর\nব্যবহার সহজ করে।\n\nঅভ্যন্তরীণভাবে, `package:flutter_bloc` `package:provider` ব্যবহার করে implement\nকরে: `BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` এবং\n`MultiRepositoryProvider` widgets। `package:flutter_bloc` `ReadContext`,\n`WatchContext` এবং `SelectContext` extensions এক্সপোর্ট করে `package:provider`\nথেকে।\n\n:::note\n\n[`package:provider`](https://pub.dev/packages/provider) সম্পর্কে আরও জানুন।\n\n:::\n\n### context.read\n\n`context.read<T>()` type `T`-এর closest ancestor instance lookup করে এবং\nfunctionally `BlocProvider.of<T>(context)`-এর সমতুল্য। `context.read` সবচেয়ে\nবেশি সাধারণভাবে একটি bloc instance পেতে ব্যবহৃত হয় যাতে `onPressed`\ncallbacks-এর মধ্যে একটি event add করা যায়।\n\n:::note\n\n`context.read<T>()` `T`-কে listen করে না -- যদি type `T`-এর প্রদত্ত `Object`\nপরিবর্তন হয়, `context.read` একটি widget rebuild trigger করবে না।\n\n:::\n\n#### ব্যবহার\n\n✅ **করুন** callbacks-এ events add করতে `context.read` ব্যবহার করুন।\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **এড়িয়ে চলুন** একটি `build` method-এর মধ্যে state পেতে `context.read`\nব্যবহার করা।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nউপরের ব্যবহার error-prone কারণ `Text` widget rebuild হবে না যদি bloc-এর state\nপরিবর্তন হয়।\n\n:::caution\n\nState changes-এর প্রতিক্রিয়ায় rebuild করতে পরিবর্তে `BlocBuilder` অথবা\n`context.watch` ব্যবহার করুন।\n\n:::\n\n### context.watch\n\n`context.read<T>()`-এর মতো, `context.watch<T>()` type `T`-এর closest ancestor\ninstance প্রদান করে, তবে এটি instance-এ changes-ও listen করে। এটি functionally\n`BlocProvider.of<T>(context, listen: true)`-এর সমতুল্য।\n\nযদি type `T`-এর প্রদত্ত `Object` পরিবর্তন হয়, `context.watch` একটি rebuild\ntrigger করবে।\n\n:::caution\n\n`context.watch` শুধুমাত্র একটি `StatelessWidget` অথবা `State` class-এর `build`\nmethod-এর মধ্যে অ্যাক্সেসযোগ্য।\n\n:::\n\n#### ব্যবহার\n\n✅ **করুন** rebuilds-কে explicitly scope করতে `context.watch`-এর পরিবর্তে\n`BlocBuilder` ব্যবহার করুন।\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Whenever the state changes, only the Text is rebuilt.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nবিকল্পভাবে, rebuilds scope করতে একটি `Builder` ব্যবহার করুন।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever the state changes, only the Text is rebuilt.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **করুন** `MultiBlocBuilder` হিসেবে `Builder` এবং `context.watch` ব্যবহার\nকরুন।\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n❌ **এড়িয়ে চলুন** `build` method-এ parent widget state-এর উপর নির্ভর না করলে\n`context.watch` ব্যবহার করা।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build` method-এর root-এ `context.watch` ব্যবহার করলে bloc state পরিবর্তন হলে\nentire widget rebuild হবে।\n\n:::\n\n### context.select\n\n`context.watch<T>()`-এর মতোই, `context.select<T, R>(R function(T value))` type\n`T`-এর closest ancestor instance প্রদান করে এবং `T`-এ changes listen করে।\n`context.watch`-এর মতো নয়, `context.select` আপনাকে state-এর একটি ছোট অংশে\nchanges listen করতে দেয়।\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nউপরেরটি শুধুমাত্র তখনই widget rebuild করবে যখন `ProfileBloc`-এর state-এর `name`\nproperty পরিবর্তন হয়।\n\n#### ব্যবহার\n\n✅ **করুন** rebuilds-কে explicitly scope করতে `context.select`-এর পরিবর্তে\n`BlocSelector` ব্যবহার করুন।\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Whenever the state.name changes, only the Text is rebuilt.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nবিকল্পভাবে, rebuilds scope করতে একটি `Builder` ব্যবহার করুন।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever state.name changes, only the Text is rebuilt.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **এড়িয়ে চলুন** একটি build method-এ parent widget state-এর উপর নির্ভর না\nকরলে `context.select` ব্যবহার করা।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state.value changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build` method-এর root-এ `context.select` ব্যবহার করলে selection পরিবর্তন হলে\nentire widget rebuild হবে।\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/bn/getting-started.mdx",
    "content": "---\ntitle: শুরু করা\ndescription: Bloc দিয়ে শুরু করতে যা যা দরকার তা সবকিছু।\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## প্যাকেজসমূহ\n\nBloc ইকোসিস্টেমের মধ্যে বিভিন্ন প্যাকেজ রয়েছে, যেগুলো নিচে তালিকাভুক্ত করা হলো:\n\n| প্যাকেজ                                                                                    | বর্ণনা                       | লিংক                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart কম্পোনেন্টস      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | মূল Dart API                 | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | ইভেন্ট ট্রান্সফর্মার         | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | কাস্টম লিন্টার               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | টেস্টিং API                  | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | কমান্ড-লাইন টুলস             | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter উইজেটস               | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | ক্যাশিং/পারসিস্টেন্স সাপোর্ট | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo সাপোর্ট            | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## ইনস্টলেশন\n\n<InstallationTabs />\n\n:::note\n\nBloc ব্যবহার শুরু করার জন্য আপনার মেশিনে [Dart SDK](https://dart.dev/get-dart)\nইনস্টল করা থাকতে হবে।\n\n:::\n\n## ইমপোর্টস\n\nএখন যেহেতু আমরা Bloc সফলভাবে ইনস্টল করেছি, আমরা আমাদের `main.dart` তৈরি করতে\nপারি এবং প্রয়োজনীয় `bloc` প্যাকেজ ইমপোর্ট করতে পারি।\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/bn/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc স্টেট ম্যানেজমেন্ট লাইব্রেরি\ndescription:\n  Bloc স্টেট ম্যানেজমেন্ট লাইব্রেরির অফিসিয়াল ডকুমেন্টেশন। Dart, Flutter, এবং\n  AngularDart-এর জন্য সাপোর্ট। উদাহরণ এবং টিউটোরিয়াল অন্তর্ভুক্ত।\nbanner:\n  content: |\n    ✨ ভিজিট করুন\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Dart-এর জন্য একটি পূর্বানুমানযোগ্য স্টেট ম্যানেজমেন্ট লাইব্রেরি।\n  image:\n    alt: Bloc লোগো\n    file: ~/assets/bloc.svg\n  actions:\n    - text: শুরু করুন\n      link: /bn/getting-started/\n      variant: primary\n      icon: rocket\n    - text: GitHub-এ দেখুন\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"শুরু করুন\" icon=\"rocket\">\n\t```sh\n\t# আপনার প্রজেক্টে bloc যুক্ত করুন।\n\tdart pub add bloc\n\t```\n\nআমাদের [গেটিং স্টার্টেড গাইড](/bn/getting-started)-এ ধাপে-ধাপে নির্দেশনা রয়েছে\nযা অনুসরণ করে আপনি কয়েক মিনিটেই Bloc ব্যবহার শুরু করতে পারবেন।\n\n</SplitCard>\n\n<Card title=\"একটি গাইডেড ট্যুর নিন\" icon=\"star\">\n\t[অফিসিয়াল টিউটোরিয়ালগুলো](/bn/tutorials/flutter-counter) সম্পূর্ণ করুন যাতে\n\tসেরা প্র্যাকটিস শিখতে পারেন এবং Bloc-চালিত বিভিন্ন ধরনের অ্যাপ তৈরি করতে\n\tপারেন।\n</Card>\n\n<Card title=\"Bloc দিয়ে তৈরি করুন\" icon=\"laptop\">\n\tউচ্চমানের, সম্পূর্ণ পরীক্ষিত [স্যাম্পল\n\tঅ্যাপ](https://github.com/felangel/bloc/tree/master/examples) অনুসন্ধান করুন —\n\tযেমন counter, timer, infinite list, weather, todo এবং আরও অনেক কিছু!\n</Card>\n\n<ListCard title=\"শিখুন\" icon=\"open-book\">\n\n    - [কেন Bloc?](/bn/why-bloc)\n    - [মূল ধারণা](/bn/bloc-concepts)\n    - [আর্কিটেকচার](/bn/architecture)\n    - [টেস্টিং](/bn/testing)\n    - [নেমিং কনভেনশন](/bn/naming-conventions)\n    - [সাধারণ প্রশ্নোত্তর](/bn/faqs)\n\n</ListCard>\n\n  <ListCard title=\"ইন্টিগ্রেশনসমূহ\" icon=\"puzzle\">\n    - [VSCode ইন্টিগ্রেশন](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ ইন্টিগ্রেশন](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim ইন্টিগ্রেশন](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI ইন্টিগ্রেশন](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [কাস্টম টেমপ্লেট](https://brickhub.dev/search?q=bloc)\n    - [ডেভেলপার টুলস](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/bn/migration.mdx",
    "content": "---\ntitle: মাইগ্রেশন গাইড\ndescription: Bloc-এর সর্বশেষ stable ভার্সনে মাইগ্রেট করুন।\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nপ্রতিটি release-এ কী পরিবর্তন হয়েছে সে সম্পর্কে আরও তথ্যের জন্য\n[release log](https://github.com/felangel/bloc/releases) দেখুন।\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ `blocTest`-কে `BlocBase` থেকে Decouple করা\n\n:::note[What Changed?]\n\nbloc_test v10.0.0-এ, `blocTest` API আর `BlocBase`-এর সাথে tightly coupled নেই।\n\n:::\n\n##### Rationale\n\n`blocTest`-এর উচিত সম্ভব হলে core bloc interfaces ব্যবহার করা flexibility এবং\nreusability বাড়ানোর জন্য। আগে এটি সম্ভব ছিল না কারণ `BlocBase`\n`StateStreamableSource` implement করত যা `blocTest`-এর জন্য যথেষ্ট ছিল না `emit`\nAPI-এর উপর internal dependency-র কারণে।\n\n### `package:hydrated_bloc`\n\n#### ❗✨ WebAssembly সাপোর্ট\n\n:::note[What Changed?]\n\nhydrated_bloc v10.0.0-এ, WebAssembly (wasm)-এ compile করার সাপোর্ট যোগ করা\nহয়েছে।\n\n:::\n\n##### Rationale\n\nআগে `hydrated_bloc` ব্যবহার করার সময় apps-কে wasm-এ compile করা সম্ভব ছিল না।\nv10.0.0-এ, প্যাকেজটি refactor করা হয়েছে wasm-এ compile করার অনুমতি দেওয়ার\nজন্য।\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Deprecated APIs সরানো\n\n:::note[What Changed?]\n\nbloc v9.0.0-এ, আগে থেকে deprecated সব APIs সরানো হয়েছে।\n\n:::\n\n##### Summary\n\n- `BlocOverrides` সরানো হয়েছে `Bloc.observer` এবং `Bloc.transformer`-এর পক্ষে\n\n#### ❗✨ নতুন `EmittableStateStreamableSource` Interface চালু\n\n:::note[What Changed?]\n\nbloc v9.0.0-এ, একটি নতুন core interface `EmittableStateStreamableSource` চালু\nকরা হয়েছে।\n\n:::\n\n##### Rationale\n\n`package:bloc_test` আগে `BlocBase`-এর সাথে tightly coupled ছিল।\n`EmittableStateStreamableSource` interface চালু করা হয়েছে যাতে `blocTest`-কে\n`BlocBase` concrete implementation থেকে decouple করা যায়।\n\n### `package:hydrated_bloc`\n\n#### ✨ `HydratedBloc.storage` API পুনরায় চালু\n\n:::note[What Changed?]\n\nhydrated_bloc v9.0.0-এ, `HydratedBlocOverrides` সরানো হয়েছে\n`HydratedBloc.storage` API-এর পক্ষে।\\*\\*\n\n:::\n\n##### Rationale\n\nBloc.observer এবং Bloc.transformer overrides পুনরায় চালু করার\n[rationale-এর জন্য দেখুন](/bn/migration/#-blocobserver-এবং-bloctransformer-apis-পুনরায়-চালু)।\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ `Bloc.observer` এবং `Bloc.transformer` APIs পুনরায় চালু\n\n:::note[What Changed?]\n\nbloc v8.1.0-এ, `BlocOverrides` deprecated করা হয়েছে `Bloc.observer` এবং\n`Bloc.transformer` APIs-এর পক্ষে।\n\n:::\n\n##### Rationale\n\n`BlocOverrides` API v8.0.0-এ চালু করা হয়েছিল `BlocObserver`,\n`EventTransformer`, এবং `HydratedStorage`-এর মতো bloc-specific configurations\nscope করার চেষ্টায়। Pure Dart applications-এ, পরিবর্তনগুলো ভালো কাজ করেছিল;\nতবে, Flutter applications-এ নতুন API যতটা সমস্যা সমাধান করেছে তার চেয়ে বেশি\nসমস্যা তৈরি করেছে।\n\n`BlocOverrides` API Flutter/Dart-এ অনুরূপ APIs দ্বারা অনুপ্রাণিত হয়েছিল:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**সমস্যা**\n\nযদিও এটি এই পরিবর্তনগুলোর প্রাথমিক কারণ ছিল না, `BlocOverrides` API ডেভেলপারদের\nজন্য অতিরিক্ত complexity নিয়ে এসেছে। একই প্রভাব অর্জনের জন্য প্রয়োজনীয়\nnesting এবং code-এর লাইনের পরিমাণ বাড়ানোর পাশাপাশি, `BlocOverrides` API-এর জন্য\nডেভেলপারদের Dart-এ\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html)-এর উপর\nsolid understanding থাকতে হয়েছিল। `Zones` beginner-friendly ধারণা নয় এবং Zones\nকিভাবে কাজ করে তা বুঝতে ব্যর্থতা bugs (যেমন uninitialized observers,\ntransformers, storage instances) তৈরি করতে পারে।\n\nউদাহরণস্বরূপ, অনেক ডেভেলপারের এমন কিছু থাকত:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nউপরের কোড, যদিও harmless মনে হয়, আসলে অনেক track করা কঠিন bugs তৈরি করতে পারে।\n`WidgetsFlutterBinding.ensureInitialized` যে zone থেকে প্রথমে কল হয় সেটিই হবে\nযে zone-এ gesture events handle করা হয় (যেমন `onTap`, `onPressed` callbacks)\n`GestureBinding.initInstances`-এর কারণে। এটি `zoneValues` ব্যবহারের কারণে সৃষ্ট\nঅনেক সমস্যার মধ্যে একটি মাত্র।\n\nএছাড়াও, Flutter অনেক কিছু behind the scenes করে যা forking/manipulating\nZones-কে জড়িত করে (বিশেষ করে tests চালানোর সময়) যা unexpected behaviors তৈরি\nকরতে পারে (এবং অনেক ক্ষেত্রে behaviors যা developer-এর নিয়ন্ত্রণের বাইরে --\nনিচের issues দেখুন)।\n\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html) ব্যবহারের\nকারণে, `BlocOverrides` API-তে transition Flutter-এ বেশ কয়েকটি bugs/limitations\nআবিষ্কারের দিকে নিয়ে গেছে (বিশেষ করে Widget এবং Integration Tests-এর আশেপাশে):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nযা bloc library ব্যবহারকারী অনেক ডেভেলপারকে প্রভাবিত করেছে:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ নতুন `BlocOverrides` API চালু\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, `Bloc.observer` এবং `Bloc.transformer` সরানো হয়েছে\n`BlocOverrides` API-এর পক্ষে।\n\n:::\n\n##### Rationale\n\nআগের API যা default `BlocObserver` এবং `EventTransformer` override করতে ব্যবহৃত\nহত `BlocObserver` এবং `EventTransformer` উভয়ের জন্য একটি global singleton-এর\nউপর নির্ভর করত।\n\nফলস্বরূপ, এটি সম্ভব ছিল না:\n\n- Application-এর বিভিন্ন অংশে scoped একাধিক `BlocObserver` বা `EventTransformer`\n  implementations থাকা\n- একটি package-এ scoped `BlocObserver` বা `EventTransformer` overrides থাকা\n  - যদি একটি package `package:bloc`-এর উপর নির্ভর করে এবং তার নিজস্ব\n    `BlocObserver` register করে, package-এর যেকোনো consumer-কে হয় package-এর\n    `BlocObserver` overwrite করতে হবে অথবা package-এর `BlocObserver`-এ report\n    করতে হবে।\n\nTests জুড়ে shared global state-এর কারণে test করাও আরও কঠিন ছিল।\n\nBloc v8.0.0 একটি `BlocOverrides` class চালু করেছে যা ডেভেলপারদের একটি specific\n`Zone`-এর জন্য `BlocObserver` এবং/অথবা `EventTransformer` override করতে দেয়\nglobal mutable singleton-এর উপর নির্ভর করার পরিবর্তে।\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n`Bloc` instances `BlocOverrides.current`-এর মাধ্যমে current `Zone`-এর জন্য\n`BlocObserver` এবং/অথবা `EventTransformer` ব্যবহার করবে। যদি zone-এর জন্য কোনো\n`BlocOverrides` না থাকে তারা existing internal defaults ব্যবহার করবে\n(behavior/functionality-তে কোনো পরিবর্তন নেই)।\n\nএটি প্রতিটি `Zone`-কে তার নিজস্ব `BlocOverrides`-এর সাথে স্বাধীনভাবে কাজ করতে\nদেয়।\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA and eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Blocs in this zone report to BlocObserverA\n    // and use eventTransformerA as the default transformer.\n    // ...\n\n    // Later...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB and eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Blocs in this zone report to BlocObserverB\n        // and use eventTransformerB as the default transformer.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Error Handling এবং Reporting উন্নতি\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, `BlocUnhandledErrorException` সরানো হয়েছে। এছাড়াও, যেকোনো\nuncaught exceptions সবসময় `onError`-এ report করা হয় এবং rethrown হয় (debug বা\nrelease mode নির্বিশেষে)। `addError` API errors-কে `onError`-এ report করে,\nকিন্তু reported errors-কে uncaught exceptions হিসেবে treat করে না।\n\n:::\n\n##### Rationale\n\nএই পরিবর্তনগুলোর লক্ষ্য হল:\n\n- bloc functionality সংরক্ষণ করার সময় internal unhandled exceptions অত্যন্ত\n  স্পষ্ট করা\n- control flow বিঘ্নিত না করে `addError` support করা\n\nআগে, error handling এবং reporting application debug বা release mode-এ চলছে কিনা\nতার উপর নির্ভর করে পরিবর্তিত হত। এছাড়াও, `addError`-এর মাধ্যমে reported errors\ndebug mode-এ uncaught exceptions হিসেবে treat করা হত যা `addError` API ব্যবহার\nকরার সময় একটি poor developer experience তৈরি করেছিল (বিশেষ করে unit tests লেখার\nসময়)।\n\nv8.0.0-এ, `addError` errors report করতে নিরাপদে ব্যবহার করা যেতে পারে এবং\n`blocTest` ব্যবহার করা যেতে পারে যাচাই করতে যে errors report করা হয়েছে। সব\nerrors এখনও `onError`-এ report করা হয়, তবে, শুধুমাত্র uncaught exceptions\nrethrown হয় (debug বা release mode নির্বিশেষে)।\n\n#### ❗🧹 `BlocObserver`-কে abstract করা\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, `BlocObserver`-কে একটি `abstract` class-এ রূপান্তর করা হয়েছে যার\nমানে `BlocObserver`-এর একটি instance instantiate করা যায় না।\n\n:::\n\n##### Rationale\n\n`BlocObserver` একটি interface হওয়ার উদ্দেশ্যে ছিল। যেহেতু default API\nimplementation no-ops, `BlocObserver` এখন একটি `abstract` class যাতে স্পষ্টভাবে\nযোগাযোগ করা যায় যে class-টি extend করার জন্য এবং সরাসরি instantiate করার জন্য\nনয়।\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // It was possible to create an instance of the base class.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // Cannot instantiate the base class.\n  final observer = BlocObserver(); // ERROR\n\n  // Extend `BlocObserver` instead.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ Bloc বন্ধ থাকলে `add` `StateError` throw করে\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, একটি closed bloc-এ `add` কল করলে `StateError` হবে।\n\n:::\n\n##### Rationale\n\nআগে, একটি closed bloc-এ `add` কল করা সম্ভব ছিল এবং internal error swallow হয়ে\nযেত, যা debug করা কঠিন করত কেন added event process করা হচ্ছিল না। এই scenario-টি\nআরও দৃশ্যমান করতে, v8.0.0-এ, একটি closed bloc-এ `add` কল করলে `StateError` throw\nকরবে যা uncaught exception হিসেবে report করা হবে এবং `onError`-এ propagate করা\nহবে।\n\n#### ❗✨ Bloc বন্ধ থাকলে `emit` `StateError` throw করে\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, একটি closed bloc-এর মধ্যে `emit` কল করলে `StateError` হবে।\n\n:::\n\n##### Rationale\n\nআগে, একটি closed bloc-এর মধ্যে `emit` কল করা সম্ভব ছিল এবং কোনো state change ঘটত\nনা কিন্তু কী ভুল হয়েছে তার কোনো ইঙ্গিতও থাকত না, যা debug করা কঠিন করত। এই\nscenario-টি আরও দৃশ্যমান করতে, v8.0.0-এ, একটি closed bloc-এর মধ্যে `emit` কল\nকরলে `StateError` throw করবে যা uncaught exception হিসেবে report করা হবে এবং\n`onError`-এ propagate করা হবে।\n\n#### ❗🧹 Deprecated APIs সরানো\n\n:::note[What Changed?]\n\nbloc v8.0.0-এ, আগে থেকে deprecated সব APIs সরানো হয়েছে।\n\n:::\n\n##### Summary\n\n- `mapEventToState` সরানো হয়েছে `on<Event>`-এর পক্ষে\n- `transformEvents` সরানো হয়েছে `EventTransformer` API-এর পক্ষে\n- `TransitionFunction` typedef সরানো হয়েছে `EventTransformer` API-এর পক্ষে\n- `listen` সরানো হয়েছে `stream.listen`-এর পক্ষে\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` এবং `MockCubit` আর `registerFallbackValue` প্রয়োজন নেই\n\n:::note[What Changed?]\n\nbloc_test v9.0.0-এ, ডেভেলপারদের আর `MockBloc` বা `MockCubit` ব্যবহার করার সময়\nস্পষ্টভাবে `registerFallbackValue` কল করার প্রয়োজন নেই।\n\n:::\n\n##### Summary\n\n`registerFallbackValue` শুধুমাত্র একটি custom type-এর জন্য `package:mocktail`\nথেকে `any()` matcher ব্যবহার করার সময় প্রয়োজন। আগে, `registerFallbackValue`\nপ্রতিটি `Event` এবং `State`-এর জন্য প্রয়োজন ছিল যখন `MockBloc` বা `MockCubit`\nব্যবহার করা হত।\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Tests...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Tests...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ নতুন `HydratedBlocOverrides` API চালু\n\n:::note[What Changed?]\n\nhydrated_bloc v8.0.0-এ, `HydratedBloc.storage` সরানো হয়েছে\n`HydratedBlocOverrides` API-এর পক্ষে।\n\n:::\n\n##### Rationale\n\nআগে, `Storage` implementation override করতে একটি global singleton ব্যবহৃত হত।\n\nফলস্বরূপ, application-এর বিভিন্ন অংশে scoped একাধিক `Storage` implementations\nথাকা সম্ভব ছিল না। Tests জুড়ে shared global state-এর কারণে test করাও আরও কঠিন\nছিল।\n\n`HydratedBloc` v8.0.0 একটি `HydratedBlocOverrides` class চালু করেছে যা\nডেভেলপারদের একটি specific `Zone`-এর জন্য `Storage` override করতে দেয় global\nmutable singleton-এর উপর নির্ভর করার পরিবর্তে।\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\n`HydratedBloc` instances `HydratedBlocOverrides.current`-এর মাধ্যমে current\n`Zone`-এর জন্য `Storage` ব্যবহার করবে।\n\nএটি প্রতিটি `Zone`-কে তার নিজস্ব `BlocOverrides`-এর সাথে স্বাধীনভাবে কাজ করতে\nদেয়।\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ নতুন `on<Event>` API চালু\n\n:::note[What Changed?]\n\nbloc v7.2.0-এ, `mapEventToState` deprecated করা হয়েছে `on<Event>`-এর পক্ষে।\n`mapEventToState` bloc v8.0.0-এ সরানো হবে।\n\n:::\n\n##### Rationale\n\n`on<Event>` API চালু করা হয়েছিল\n[[Proposal] Replace mapEventToState with on\\<Event\\> in Bloc](https://github.com/felangel/bloc/issues/2526)-এর\nঅংশ হিসেবে।\n[Dart-এ একটি issue](https://github.com/dart-lang/sdk/issues/44616)-এর কারণে\nnested async generators (`async*`)-এর সাথে deal করার সময় `state`-এর মান কী হবে\nতা সবসময় স্পষ্ট নয়। যদিও issue-টি work around করার উপায় আছে, bloc library-এর\ncore principles-এর মধ্যে একটি হল predictable হওয়া। `on<Event>` API library-টি\nযতটা সম্ভব নিরাপদে ব্যবহার করার জন্য এবং state changes-এর ক্ষেত্রে যেকোনো\nuncertainty দূর করার জন্য তৈরি করা হয়েছিল।\n\n:::tip\n\nআরও তথ্যের জন্য,\n[full proposal পড়ুন](https://github.com/felangel/bloc/issues/2526)।\n\n:::\n\n**Summary**\n\n`on<E>` আপনাকে type `E`-এর সব events-এর জন্য একটি event handler register করতে\nদেয়। ডিফল্টভাবে, events concurrently process হবে যখন `on<E>` ব্যবহার করা হবে\n`mapEventToState`-এর বিপরীতে যা events `sequentially` process করে।\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nপ্রতিটি registered `EventHandler` স্বাধীনভাবে কাজ করে তাই এটি গুরুত্বপূর্ণ যে\nআপনি যে ধরনের transformer apply করতে চান তার উপর ভিত্তি করে event handlers\nregister করুন।\n\n:::\n\nআপনি যদি v7.1.0-এর মতো exact same behavior রাখতে চান আপনি সব events-এর জন্য একটি\nsingle event handler register করতে পারেন এবং একটি `sequential` transformer apply\nকরতে পারেন:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: logic goes here...\n  }\n}\n```\n\nআপনি আপনার application-এ সব blocs-এর জন্য default `EventTransformer` override\nকরতে পারেন:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ নতুন `EventTransformer` API চালু\n\n:::note[What Changed?]\n\nbloc v7.2.0-এ, `transformEvents` deprecated করা হয়েছে `EventTransformer` API-এর\nপক্ষে। `transformEvents` bloc v8.0.0-এ সরানো হবে।\n\n:::\n\n##### Rationale\n\n`on<Event>` API per event handler একটি custom event transformer প্রদান করার দরজা\nখুলে দিয়েছে। একটি নতুন `EventTransformer` typedef চালু করা হয়েছে যা\nডেভেলপারদের প্রতিটি event handler-এর জন্য incoming event stream transform করতে\nদেয় সব events-এর জন্য একটি single event transformer specify করার পরিবর্তে।\n\n**Summary**\n\nএকটি `EventTransformer` incoming events-এর stream একটি `EventMapper` (আপনার\nevent handler) সহ নেওয়ার জন্য দায়ী এবং events-এর একটি নতুন stream রিটার্ন করে।\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nDefault `EventTransformer` সব events concurrently process করে এবং দেখতে কিছুটা\nএমন:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\n[package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) দেখুন\ncustom event transformers-এর একটি opinionated set-এর জন্য\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Define a custom `EventTransformer`\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Apply the custom `EventTransformer` to the `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ `transformTransitions` API Deprecate\n\n:::note[What Changed?]\n\nbloc v7.2.0-এ, `transformTransitions` deprecated করা হয়েছে `stream` API\noverride করার পক্ষে। `transformTransitions` bloc v8.0.0-এ সরানো হবে।\n\n:::\n\n##### Rationale\n\n`Bloc`-এ `stream` getter outbound stream of states override করা সহজ করে তোলে তাই\nএকটি separate `transformTransitions` API বজায় রাখা আর মূল্যবান নয়।\n\n**Summary**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc এবং Cubit BlocBase extend করে\n\n##### Rationale\n\nএকজন ডেভেলপার হিসেবে, blocs এবং cubits-এর মধ্যে সম্পর্ক কিছুটা awkward ছিল। যখন\ncubit প্রথম চালু করা হয়েছিল এটি blocs-এর base class হিসেবে শুরু হয়েছিল যা\nযুক্তিসঙ্গত ছিল কারণ এটির functionality-এর একটি subset ছিল এবং blocs কেবল Cubit\nextend করত এবং additional APIs define করত। এটি কয়েকটি drawback নিয়ে এসেছিল:\n\n- সব APIs হয় accuracy-এর জন্য cubit accept করতে rename করতে হবে অথবা\n  consistency-এর জন্য bloc হিসেবে রাখতে হবে যদিও hierarchically এটি inaccurate\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560))।\n\n- Cubit-এর একটি common base থাকার জন্য Stream extend করতে হবে এবং EventSink\n  implement করতে হবে যাতে BlocBuilder, BlocListener ইত্যাদির মতো widgets\n  implement করা যায় ([#1429](https://github.com/felangel/bloc/issues/1429))।\n\nপরে, আমরা relationship-টি invert করে এবং bloc-কে base class করে পরীক্ষা করেছিলাম\nযা উপরের প্রথম bullet-টি আংশিকভাবে সমাধান করেছিল কিন্তু অন্যান্য issues নিয়ে\nএসেছিল:\n\n- Cubit API bloated underlying bloc APIs-এর কারণে যেমন mapEventToState, add,\n  etc. ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - ডেভেলপাররা technically এই APIs invoke করতে পারে এবং things break করতে পারে\n- আমাদের এখনও একই issue আছে cubit entire stream API expose করার আগের মতো\n  ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nএই issues-গুলি সমাধান করতে আমরা `Bloc` এবং `Cubit` উভয়ের জন্য একটি base class\nচালু করেছি `BlocBase` নামে যাতে upstream components এখনও উভয় bloc এবং cubit\ninstances-এর সাথে interoperate করতে পারে কিন্তু entire `Stream` এবং `EventSink`\nAPI সরাসরি expose না করে।\n\n**Summary**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed returns a function to support dynamic values\n\n##### Rationale\n\nIn order to support having a mutable seed value which can be updated dynamically\nin `setUp`, `seed` returns a function.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect returns a function to support dynamic values and includes matcher support\n\n##### Rationale\n\nIn order to support having a mutable expectation which can be updated\ndynamically in `setUp`, `expect` returns a function. `expect` also supports\n`Matchers`.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors returns a function to support dynamic values and includes matcher support\n\n##### Rationale\n\nIn order to support having a mutable errors which can be updated dynamically in\n`setUp`, `errors` returns a function. `errors` also supports `Matchers`.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc and MockCubit\n\n##### Rationale\n\nTo support stubbing of various core APIs, `MockBloc` and `MockCubit` are\nexported as part of the `bloc_test` package. Previously, `MockBloc` had to be\nused for both `Bloc` and `Cubit` instances which was not intuitive.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Mocktail Integration\n\n##### Rationale\n\nDue to various limitations of the null-safe\n[package:mockito](https://pub.dev/packages/mockito) described\n[here](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) is used by `MockBloc` and\n`MockCubit`. This allows developers to continue using a familiar mocking API\nwithout the need to manually write stubs or rely on code generation.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Please refer to [#347](https://github.com/dart-lang/mockito/issues/347) as\n> well as the\n> [mocktail documentation](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> for more information.\n\n### `package:flutter_bloc`\n\n#### ❗ rename `cubit` parameter to `bloc`\n\n##### Rationale\n\nAs a result of the refactor in `package:bloc` to introduce `BlocBase` which\n`Bloc` and `Cubit` extend, the parameters of `BlocBuilder`, `BlocConsumer`, and\n`BlocListener` were renamed from `cubit` to `bloc` because the widgets operate\non the `BlocBase` type. This also further aligns with the library name and\nhopefully improves readability.\n\n**Summary**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory is required when calling HydratedStorage.build\n\n##### Rationale\n\nIn order to make `package:hydrated_bloc` a pure Dart package, the dependency on\n[package:path_provider](https://pub.dev/packages/path_provider) was removed and\nthe `storageDirectory` parameter when calling `HydratedStorage.build` is\nrequired and no longer defaults to `getTemporaryDirectory`.\n\n**Summary**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc এবং context.repository deprecated হয়েছে context.read এবং context.watch-এর পক্ষে\n\n##### Rationale\n\n`context.read`, `context.watch`, এবং `context.select` যোগ করা হয়েছে existing\n[provider](https://pub.dev/packages/provider) API-এর সাথে align করতে যা অনেক\nডেভেলপার familiar এবং community-এর দ্বারা উত্থাপিত issues-গুলি address করতে।\nCode-এর safety উন্নত করতে এবং consistency বজায় রাখতে, `context.bloc` deprecated\nকরা হয়েছে কারণ এটি `context.read` বা `context.watch` দিয়ে replace করা যেতে\nপারে এটি `build`-এর মধ্যে সরাসরি ব্যবহার করা হয় কিনা তার উপর নির্ভর করে।\n\n**context.watch**\n\n`context.watch` একটি\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) থাকার request\naddress করে কারণ আমরা একটি single `Builder`-এর মধ্যে একাধিক blocs watch করতে\nপারি multiple states-এর উপর ভিত্তি করে UI render করার জন্য:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` ডেভেলপারদের একটি bloc-এর state-এর একটি অংশের উপর ভিত্তি করে UI\nrender/update করতে দেয় এবং একটি\n[simpler buildWhen](https://github.com/felangel/bloc/issues/1521) থাকার request\naddress করে।\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nউপরের snippet আমাদের access করতে এবং widget rebuild করতে দেয় শুধুমাত্র যখন\ncurrent user-এর name পরিবর্তন হয়।\n\n**context.read**\n\nযদিও `context.read` `context.bloc`-এর মতো দেখায় সেখানে কিছু subtle কিন্তু\nsignificant differences আছে। উভয়ই আপনাকে একটি bloc access করতে দেয় একটি\n`BuildContext` সহ এবং rebuilds তৈরি করে না; তবে, `context.read` `build`\nmethod-এর মধ্যে সরাসরি call করা যায় না। `build`-এর মধ্যে `context.bloc` ব্যবহার\nকরার দুটি main কারণ আছে:\n\n1. **Bloc-এর state access করতে**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nউপরের ব্যবহার error-prone কারণ `Text` widget rebuild হবে না যদি bloc-এর state\nপরিবর্তন হয়। এই scenario-তে, হয় `BlocBuilder` বা `context.watch` ব্যবহার করা\nউচিত।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nঅথবা\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\n`build` method-এর root-এ `context.watch` ব্যবহার করলে bloc state পরিবর্তন হলে\nentire widget rebuild হবে। যদি entire widget rebuild করার প্রয়োজন না থাকে, হয়\n`BlocBuilder` ব্যবহার করুন যে parts-গুলি rebuild করা উচিত সেগুলি wrap করতে,\nrebuilds scope করতে `Builder` সহ `context.watch` ব্যবহার করুন, অথবা widget-টি\nsmaller widgets-এ decompose করুন।\n\n:::\n\n2. **Bloc access করতে যাতে একটি event add করা যায়**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nউপরের ব্যবহার inefficient কারণ এটি প্রতিটি rebuild-এ bloc lookup তৈরি করে যখন\nbloc শুধুমাত্র প্রয়োজন যখন user `ElevatedButton` tap করে। এই scenario-তে, bloc\nসরাসরি access করতে `context.read` ব্যবহার করুন যেখানে এটি প্রয়োজন (এই ক্ষেত্রে,\n`onPressed` callback-এ)।\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Summary**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> যদি একটি event add করতে bloc access করা হয়, bloc access করুন `context.read`\nব্যবহার করে callback-এ যেখানে এটি প্রয়োজন।\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Bloc-এর state access করার সময় `context.watch` ব্যবহার করুন যাতে নিশ্চিত করা\nযায় state পরিবর্তন হলে widget rebuild হয়।\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError Cubit নেয়\n\n##### Rationale\n\n`Cubit`-এর integration-এর কারণে, `onError` এখন `Bloc` এবং `Cubit` instances\nউভয়ের মধ্যে shared। যেহেতু `Cubit` base, `BlocObserver` `onError` override-এ\n`Bloc` type-এর পরিবর্তে একটি `Cubit` type accept করবে।\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc subscription-এ last state emit করে না\n\n##### Rationale\n\nএই পরিবর্তনটি `Bloc` এবং `Cubit`-কে `Dart`-এ built-in `Stream` behavior-এর সাথে\nalign করার জন্য করা হয়েছিল। এছাড়াও, `Cubit`-এর context-এ এই old behavior\nconform করা অনেক unintended side-effects তৈরি করেছিল এবং overall অন্যান্য\npackages যেমন `flutter_bloc` এবং `bloc_test`-এর internal implementations জটিল\nকরেছিল unnecessarily (`skip(1)`, etc... প্রয়োজন)।\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nআগে, উপরোক্ত snippet bloc-এর initial state output করত এর পরে subsequent state\nchanges।\n\n**v6.x.x**\n\nv6.0.0-এ, উপরোক্ত snippet initial state output করে না এবং শুধুমাত্র outputs\nsubsequent state changes। Previous behavior নিম্নলিখিত দিয়ে অর্জন করা যেতে\nপারে:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Note**: এই পরিবর্তনটি শুধুমাত্র direct bloc subscriptions-এর উপর নির্ভরশীল\ncode-কে প্রভাবিত করবে। `BlocBuilder`, `BlocListener`, বা `BlocConsumer` ব্যবহার\nকরার সময় behavior-তে কোনো noticeable change থাকবে না।\n\n### `package:bloc_test`\n\n#### ❗MockBloc only requires State type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `MockBloc`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen only requires State type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `whenListen`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest does not require Event type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `blocTest`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip defaults to 0\n\n##### Rationale\n\nSince `bloc` and `cubit` instances will no longer emit the latest state for new\nsubscriptions, it was no longer necessary to default `skip` to `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nThe initial state of a bloc or cubit can be tested with the following:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest make build synchronous\n\n##### Rationale\n\nPreviously, `build` was made `async` so that various preparation could be done\nto put the bloc under test in a specific state. It is no longer necessary and\nalso resolves several issues due to the added latency between the build and the\nsubscription internally. Instead of doing async prep to get a bloc in a desired\nstate we can now set the bloc state by chaining `emit` with the desired state.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` is only visible for testing and should never be used outside of tests.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocBuilder` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocListener` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗BlocConsumer bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocConsumer` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState সরানো হয়েছে\n\n##### Rationale\n\nএকজন ডেভেলপার হিসেবে, একটি bloc তৈরি করার সময় `initialState` override করতে হলে\nদুটি main issue দেখা দেয়:\n\n- Bloc-এর `initialState` dynamic হতে পারে এবং এটি একটি পরবর্তী সময়ে reference\n  করা যেতে পারে (bloc-এর বাইরেও)। কিছু উপায়ে, এটি internal bloc information UI\n  layer-এ leak করা হিসেবে দেখা যেতে পারে।\n- এটি verbose।\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> আরও তথ্যের জন্য দেখুন [#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate-কে BlocObserver নামকরণ\n\n##### Rationale\n\n`BlocDelegate` নামটি class-টি যে role পালন করত তার একটি accurate description ছিল\nনা। `BlocDelegate` suggests করে যে class-টি একটি active role পালন করে যেখানে\nবাস্তবে `BlocDelegate`-এর intended role ছিল এটি একটি passive component হওয়া যা\nকেবল application-এ সব blocs observe করে।\n\n:::note\n\n`BlocObserver`-এর মধ্যে user-facing functionality বা features handle করা উচিত\nনয় ideally।\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor সরানো হয়েছে\n\n##### Rationale\n\n`BlocSupervisor` আরেকটি component ছিল যা ডেভেলপারদের জানতে এবং interact করতে\nহয়েছিল একটি custom `BlocDelegate` specify করার একমাত্র উদ্দেশ্যে।\n`BlocObserver`-এ পরিবর্তনের সাথে আমরা মনে করি এটি developer experience উন্নত\nকরেছে observer সরাসরি bloc-এ set করতে।\n\n?> এই পরিবর্তনটি আমাদের অন্যান্য bloc add-ons যেমন `HydratedStorage`-কে\n`BlocObserver` থেকে decouple করতেও সক্ষম করেছে।\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder condition-কে buildWhen নামকরণ\n\n##### Rationale\n\n`BlocBuilder` ব্যবহার করার সময়, আমরা আগে একটি `condition` specify করতে পারতাম\nনির্ধারণ করতে `builder` rebuild করা উচিত কিনা।\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n`condition` নামটি খুব self-explanatory বা obvious নয় এবং আরও গুরুত্বপূর্ণ,\n`BlocConsumer`-এর সাথে interact করার সময় API inconsistent হয়ে গিয়েছিল কারণ\nডেভেলপাররা দুটি condition প্রদান করতে পারে (একটি `builder`-এর জন্য এবং একটি\n`listener`-এর জন্য)। ফলস্বরূপ, `BlocConsumer` API একটি `buildWhen` এবং\n`listenWhen` expose করেছিল\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nAPI align করতে এবং আরও consistent developer experience প্রদান করতে,\n`condition`-কে `buildWhen` নামকরণ করা হয়েছে।\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener condition-কে listenWhen নামকরণ\n\n##### Rationale\n\nউপরে বর্ণিত একই কারণে, `BlocListener` condition-ও নামকরণ করা হয়েছে।\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage এবং HydratedBlocStorage নামকরণ\n\n##### Rationale\n\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) এবং\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit)-এর মধ্যে code reuse\nউন্নত করতে, concrete default storage implementation-কে `HydratedBlocStorage`\nথেকে `HydratedStorage` নামকরণ করা হয়েছে। এছাড়াও, `HydratedStorage`\ninterface-কে `HydratedStorage` থেকে `Storage` নামকরণ করা হয়েছে।\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage-কে BlocDelegate থেকে decouple করা\n\n##### Rationale\n\nআগে উল্লিখিত, `BlocDelegate`-কে `BlocObserver` নামকরণ করা হয়েছে এবং set করা\nহয়েছে সরাসরি `bloc`-এর অংশ হিসেবে:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nনিম্নলিখিত পরিবর্তন করা হয়েছে:\n\n- নতুন bloc observer API-এর সাথে consistent থাকতে\n- Storage-কে শুধুমাত্র `HydratedBloc`-এ scoped রাখতে\n- `BlocObserver`-কে `Storage` থেকে decouple করতে\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Simplified Initialization\n\n##### Rationale\n\nআগে, ডেভেলপারদের manually call করতে হত\n`super.initialState ?? DefaultInitialState()` তাদের `HydratedBloc` instances\nsetup করার জন্য। এটি clunky এবং verbose এবং `bloc`-এ `initialState`-এর breaking\nchanges-এর সাথেও incompatible। ফলস্বরূপ, v5.0.0-এ `HydratedBloc` initialization\nnormal `Bloc` initialization-এর মতোই।\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/bn/modeling-state.mdx",
    "content": "---\ntitle: স্টেট মডেলিং\ndescription:\n  package:bloc ব্যবহার করার সময় স্টেট মডেল করার বিভিন্ন উপায়ের একটি ওভারভিউ।\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nঅ্যাপ্লিকেশনের স্টেট সংগঠিত করার ক্ষেত্রে বিভিন্ন ধরনের পদ্ধতি রয়েছে। প্রতিটিরই\nনিজস্ব সুবিধা ও সীমাবদ্ধতা আছে। এই সেকশনে আমরা কয়েকটি পদ্ধতি, তাদের\nসুবিধা-অসুবিধা এবং কোন পরিস্থিতিতে কোনটি ব্যবহার করা ভালো — তা দেখব।\n\nনিচের পদ্ধতিগুলো সম্পূর্ণই ঐচ্ছিক এবং শুধুই সুপারিশ। আপনি চাইলে আপনার পছন্দমতো\nযেকোনো পদ্ধতি ব্যবহার করতে পারেন। সংক্ষিপ্ত এবং সহজ রাখার জন্য কিছু\nউদাহরণ/ডকুমেন্টেশন এই পদ্ধতিগুলো অনুসরণ নাও করতে পারে।\n\n:::tip\n\nনিচের কোড স্নিপেটগুলো মূলত স্টেট স্ট্রাকচারের উপর ফোকাস করা। বাস্তবে, আপনি\nচাইলে:\n\n- `package:equatable` থেকে `Equatable` প্রসারিত করতে পারেন\n- `package:data_class` থেকে `@Data()` দিয়ে ক্লাস অ্যানোটেট করতে পারেন\n- `package:meta` থেকে **@immutable** দিয়ে ক্লাস অ্যানোটেট করতে পারেন\n- `copyWith` মেথড ইমপ্লিমেন্ট করতে পারেন\n- কনস্ট্রাক্টরে `const` কীওয়ার্ড ব্যবহার করতে পারেন\n\n:::\n\n## Concrete Class and Status Enum\n\nএই পদ্ধতিতে সকল স্টেটের জন্য একটি **সিঙ্গেল কনক্রিট ক্লাস** থাকে এবং একটি `enum`\nথাকে যা বিভিন্ন স্ট্যাটাস উপস্থাপন করে। সকল প্রপার্টি nullable করা হয় এবং\nবর্তমান স্ট্যাটাস অনুযায়ী ব্যবহার করা হয়। এই পদ্ধতি সবচেয়ে ভালো কাজ করে যখন\nস্টেটগুলো একে অপরের থেকে কঠোরভাবে আলাদা নয় বা যখন অনেকগুলো শেয়ারড প্রপার্টি\nথাকে।\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Pros\n\n- **সিম্পল:** একটি ক্লাস এবং একটি status enum পরিচালনা করা সহজ এবং সব প্রপার্টিই\n  সহজে অ্যাক্সেস করা যায়।\n- **সংক্ষিপ্ত:** অন্যান্য পদ্ধতির তুলনায় সাধারণত কম লাইনের কোড লাগে।\n\n#### Cons\n\n- **টাইপ সেফ নয়:** প্রপার্টিতে অ্যাক্সেস করার আগে `status` চেক করতে হয়। ভুল\n  স্টেট `emit` করা সম্ভব, যা বাগ তৈরি করতে পারে। নির্দিষ্ট স্টেটের প্রপার্টি\n  nullable হওয়ায় null-check বা force unwrap করতে হয়, যা ঝামেলাযুক্ত হতে পারে।\n  ইউনিট টেস্ট এবং বিশেষায়িত নামযুক্ত কনস্ট্রাক্টর লিখে এগুলোর কিছুটা কমানো\n  যায়।\n- **Bloated:** সময়ের সাথে সাথে অনেক প্রপার্টি জমে একে ভারী ও জটিল করে ফেলতে\n  পারে।\n\n#### Verdict\n\nএই পদ্ধতি সাধারণ স্টেট বা এমন পরিস্থিতিতে ভালো কাজ করে যেখানে স্টেটগুলো একে\nঅপরের এক্সক্লুসিভ নয় (যেমন: একটি error snackbar দেখানো হচ্ছে কিন্তু পূর্বের\nsuccess স্টেটের ডেটা এখনও দেখানো হচ্ছে)। এটি টাইপ সেফটির খরচে বেশি\nফ্লেক্সিবিলিটি ও সংক্ষিপ্ততা দেয়।\n\n## Sealed Class and Subclasses\n\nএই পদ্ধতিতে একটি **sealed class** থাকে যা শেয়ারড প্রপার্টিগুলো রাখে এবং প্রতিটি\nআলাদা স্টেটের জন্য আলাদা সাবক্লাস থাকে। এটি আলাদা ও এক্সক্লুসিভ স্টেটগুলোর জন্য\nঅসাধারণভাবে কার্যকর।\n\n<SealedClassAndSubclassesSnippet />\n\n#### Pros\n\n- **টাইপ সেফ:** কম্পাইল-টাইম সেফটি থাকে এবং ভুল প্রপার্টিতে ভুলবশত অ্যাক্সেস করা\n  যায় না। প্রতিটি সাবক্লাস তার নিজস্ব প্রপার্টি রাখে, ফলে কোন প্রপার্টি কোন\n  স্টেটে তা খুব পরিষ্কার।\n- **স্পষ্ট:** শেয়ারড প্রপার্টি এবং স্টেট-নির্দিষ্ট প্রপার্টিগুলো আলাদা রাখা\n  যায়।\n- **Exhaustive:** `switch` স্টেটমেন্ট ব্যবহার করে প্রত্যেকটি স্টেট স্পষ্টভাবে\n  হ্যান্ডেল হচ্ছে কিনা তা নিশ্চিত করা যায়।\n  - যদি আপনি\n    [exhaustive switching](https://dart.dev/language/branches#exhaustiveness-checking)\n    না চান অথবা পরে নতুন subtype যোগ করতে চান API ভাঙা ছাড়া, তাহলে\n    [final](https://dart.dev/language/class-modifiers#final) modifier ব্যবহার\n    করুন।\n  - বিস্তারিত জানতে দেখুন:\n    [sealed class documentation](https://dart.dev/language/class-modifiers#sealed)\n\n#### Cons\n\n- **বিস্তারিত/Verbose:** বেশি কোড লাগে (একটি বেস ক্লাস এবং প্রতিটি স্টেটের জন্য\n  একটি করে সাবক্লাস)। এছাড়াও, সাবক্লাসগুলোতে শেয়ারড প্রপার্টি পুনরাবৃত্তি করতে\n  হতে পারে।\n- **জটিল:** নতুন প্রপার্টি যোগ করতে চাইলে প্রতিটি সাবক্লাস এবং বেস ক্লাসে আপডেট\n  করতে হয়, যা জটিলতা বাড়াতে পারে। অতিরিক্ত টাইপ-চেকিং লাগতে পারে কিছু\n  ক্ষেত্রে।\n\n#### Verdict\n\nএই পদ্ধতি সবচেয়ে ভালো কাজ করে যখন স্টেটগুলো সুস্পষ্টভাবে আলাদা ও এক্সক্লুসিভ\nএবং প্রতিটির নিজস্ব প্রপার্টি থাকে। এটি টাইপ সেফটি, স্পষ্টতা এবং exhaustiveness\nচেক প্রদান করে এবং সংক্ষিপ্ততার চেয়ে নিরাপত্তাকে বেশি গুরুত্ব দেয়।\n"
  },
  {
    "path": "docs/src/content/docs/bn/naming-conventions.mdx",
    "content": "---\ntitle: নামকরণ কনভেনশন\ndescription: bloc ব্যবহার করার সময় সুপারিশকৃত নামকরণ কনভেনশনগুলোর একটি ওভারভিউ।\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nনীচের নামকরণ কনভেনশনগুলো শুধুই সুপারিশ এবং সম্পূর্ণই ঐচ্ছিক। আপনি চাইলে আপনার\nপছন্দমতো যেকোনো নামকরণ কনভেনশন ব্যবহার করতে পারেন। কিছু উদাহরণ/ডকুমেন্টেশন\nসংক্ষিপ্ত রাখার সুবিধার্থে এই কনভেনশন অনুসরণ নাও করতে পারে। অনেক ডেভেলপারসহ বড়\nপ্রজেক্টে এই কনভেনশনগুলো অনুসরণ করা বিশেষভাবে সুপারিশ করা হয়।\n\n## Event Conventions\n\nইভেন্টগুলোর নাম **অতীত কাল**-এ হওয়া উচিত কারণ bloc-এর দৃষ্টিকোণ থেকে ইভেন্টগুলো\nহলো এমন ঘটনা যেগুলো ইতোমধ্যে ঘটেছে।\n\n### Anatomy\n\n`BlocSubject` + `Noun (optional)` + `Verb (event)`\n\nপ্রাথমিক লোড ইভেন্টগুলো এই কনভেনশন অনুসরণ করবে:  \n`BlocSubject` + `Started`\n\n:::note\n\nবেস ইভেন্ট ক্লাসের নাম হওয়া উচিত: `BlocSubject` + `Event`.\n\n:::\n\n### Examples\n\n✅ **Good**\n\n<EventExamplesGood1 />\n\n❌ **Bad**\n\n<EventExamplesBad1 />\n\n## State Conventions\n\nস্টেটের নাম হওয়া উচিত **noun**, কারণ একটি স্টেট হলো নির্দিষ্ট সময়ে অ্যাপের\nএকটি স্ন্যাপশট। স্টেট উপস্থাপন করার দুটি সাধারণ উপায় আছে: সাবক্লাস ব্যবহার করে\nবা একটি সিঙ্গেল ক্লাস ব্যবহার করে।\n\n### Anatomy\n\n#### Subclasses\n\n`BlocSubject` + `Verb (action)` + `State`\n\nযখন স্টেটকে একাধিক সাবক্লাস হিসাবে উপস্থাপন করা হয়, তখন `State` নিম্নোক্তগুলোর\nএকটি হওয়া উচিত:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nInitial স্টেটের কনভেনশন হওয়া উচিত: `BlocSubject` + `Initial`.\n\n:::\n\n#### Single Class\n\n`BlocSubject` + `State`\n\nযখন স্টেটকে একটি সিঙ্গেল বেস ক্লাস হিসেবে উপস্থাপন করা হয়, তখন স্টেটের\nস্ট্যাটাস উপস্থাপন করার জন্য `BlocSubject` + `Status` নামে একটি enum ব্যবহার করা\nউচিত:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nবেস স্টেট ক্লাসের নাম সর্বদা হওয়া উচিত: `BlocSubject` + `State`.\n\n:::\n\n### Examples\n\n✅ **Good**\n\n##### Subclasses\n\n<StateExamplesGood1Snippet />\n\n##### Single Class\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Bad**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/bn/testing.mdx",
    "content": "---\ntitle: টেস্টিং\ndescription: আপনার blocs-এর জন্য টেস্ট কীভাবে লিখবেন তার মৌলিক বিষয়গুলো।\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc এমনভাবে ডিজাইন করা হয়েছে যাতে এটি টেস্ট করা অত্যন্ত সহজ হয়। এই সেকশনে\nআমরা একটি bloc কীভাবে ইউনিট টেস্ট করতে হয় তা দেখে নেব।\n\nসহজতার জন্য, চলুন আমরা [Core Concepts](/bn/bloc-concepts)-এ তৈরি করা\n`CounterBloc`-এর জন্য টেস্ট লিখি।\n\nসংক্ষেপে বলতে গেলে, `CounterBloc`-এর ইমপ্লিমেন্টেশনটি এমন দেখায়:\n\n<CounterBlocSnippet />\n\n## Setup\n\nটেস্ট লেখার আগে আমাদের ডিপেনডেন্সিতে একটি টেস্টিং ফ্রেমওয়ার্ক যোগ করতে হবে।\n\nআমাদের প্রজেক্টে [test](https://pub.dev/packages/test) এবং\n[bloc_test](https://pub.dev/packages/bloc_test) যোগ করতে হবে।\n\n<AddDevDependenciesSnippet />\n\n## Testing\n\nএখন শুরু করি আমাদের `CounterBloc` টেস্টগুলোর জন্য ফাইল তৈরি করে। ফাইলের নাম হবে\n`counter_bloc_test.dart` এবং আমরা সেখানে test প্যাকেজ ইমপোর্ট করব।\n\n<CounterBlocTestImportsSnippet />\n\nএরপর আমাদের `main` তৈরি করতে হবে এবং টেস্ট গ্রুপ তৈরি করতে হবে।\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nগ্রুপগুলো ব্যবহৃত হয় পৃথক টেস্টগুলো সংগঠিত করতে এবং এমন একটি কনটেক্সট তৈরি করতে\nযেখানে আপনি সব টেস্টের জন্য সাধারণ `setUp` এবং `tearDown` শেয়ার করতে পারবেন।\n\n:::\n\nচলুন শুরু করি আমাদের `CounterBloc`-এর একটি ইনস্ট্যান্স তৈরি করে, যা সব টেস্টেই\nব্যবহৃত হবে।\n\n<CounterBlocTestSetupSnippet />\n\nএখন আমরা আমাদের পৃথক টেস্টগুলো লেখা শুরু করতে পারি।\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nআমরা `dart test` কমান্ড দিয়ে সব টেস্ট রান করতে পারি।\n\n:::\n\nএ পর্যায়ে আমাদের প্রথম টেস্টটি পাশ করা উচিত! এখন চলুন\n[bloc_test](https://pub.dev/packages/bloc_test) প্যাকেজ ব্যবহার করে আরও জটিল\nএকটি টেস্ট লিখি।\n\n<CounterBlocTestBlocTestSnippet />\n\nআমাদের এখন টেস্টগুলো রান করতে সক্ষম হওয়া উচিত এবং সবগুলো পাস করবে।\n\nএতেই সব শেষ — টেস্টিং হওয়া উচিত খুবই সহজ, এবং কোড পরিবর্তন বা রিফ্যাক্টর করার\nসময় আমাদের আত্মবিশ্বাসী লাগবে।\n\nআপনি সম্পূর্ণ টেস্ট করা একটি অ্যাপ্লিকেশনের উদাহরণ হিসেবে দেখতে পারেন\n[Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)।\n"
  },
  {
    "path": "docs/src/content/docs/bn/why-bloc.mdx",
    "content": "---\ntitle: কেন Bloc?\ndescription:\n  Bloc কেন একটি শক্তিশালী স্টেট ম্যানেজমেন্ট সমাধান — তার একটি সংক্ষিপ্ত ধারণা।\nsidebar:\n  order: 1\n---\n\nBloc উপস্থাপনাকে ব্যবসায়িক লজিক থেকে আলাদা করা সহজ করে তোলে, যার ফলে আপনার কোড\nহয় _দ্রুত_, _সহজে পরীক্ষাযোগ্য_, এবং _পুনঃব্যবহারযোগ্য_।\n\nপ্রোডাকশন-মানের অ্যাপ্লিকেশন তৈরি করার সময়, স্টেট ম্যানেজমেন্ট অত্যন্ত\nগুরুত্বপূর্ণ হয়ে ওঠে।\n\nডেভেলপার হিসেবে আমরা চাই:\n\n- যেকোনো মুহূর্তে আমাদের অ্যাপ্লিকেশনের স্টেট কী — তা জানতে।\n- প্রতিটি কেস সহজে পরীক্ষা করতে যাতে নিশ্চিত হওয়া যায় আমাদের অ্যাপ সঠিকভাবে\n  প্রতিক্রিয়া দিচ্ছে।\n- আমাদের অ্যাপ্লিকেশনের প্রতিটি ইউজার ইন্টারঅ্যাকশন রেকর্ড করতে যাতে ডেটা-চালিত\n  সিদ্ধান্ত নিতে পারি।\n- যতটা সম্ভব দক্ষভাবে কাজ করতে এবং আমাদের অ্যাপ্লিকেশন ও অন্যান্য অ্যাপ্লিকেশনে\n  কম্পোনেন্টগুলো পুনঃব্যবহার করতে।\n- একই প্যাটার্ন ও কনভেনশন অনুসরণ করে অনেক ডেভেলপার যেন নির্বিঘ্নে একই কোডবেসে\n  কাজ করতে পারে।\n- দ্রুত ও রিঅ্যাকটিভ অ্যাপ ডেভেলপ করতে।\n\nBloc এই সব প্রয়োজন এবং আরও অনেক কিছু পূরণ করার জন্য ডিজাইন করা হয়েছে।\n\nঅনেক স্টেট ম্যানেজমেন্ট সমাধান রয়েছে এবং কোনটি ব্যবহার করবেন তা সিদ্ধান্ত\nনেওয়া কঠিন হতে পারে। একটিও সমাধান \"পারফেক্ট\" নয়!  \nগুরুত্বপূর্ণ হলো—আপনার টিম এবং আপনার প্রজেক্টের জন্য যেটি সবচেয়ে উপযোগী, সেটি\nনির্বাচন করা।\n\nBloc তিনটি মূল মূল্যবোধকে কেন্দ্র করে ডিজাইন করা হয়েছে:\n\n- **Simple:** বুঝতে সহজ এবং বিভিন্ন দক্ষতার ডেভেলপাররা ব্যবহার করতে পারে।\n- **Powerful:** ছোট ছোট কম্পোনেন্ট মিলে জটিল ও চমৎকার অ্যাপ্লিকেশন তৈরি করতে\n  সাহায্য করে।\n- **Testable:** অ্যাপ্লিকেশনের প্রতিটি অংশ সহজেই পরীক্ষা করা যায় যাতে আমরা\n  আত্মবিশ্বাসের সাথে ইটারেট করতে পারি।\n\nসর্বোপরি, Bloc স্টেট পরিবর্তনকে পূর্বানুমানযোগ্য করতে চায়—স্টেট কখন পরিবর্তিত\nহতে পারে তা নিয়ন্ত্রণ করে এবং পুরো অ্যাপ্লিকেশন জুড়ে স্টেট পরিবর্তনের একটি\nনির্দিষ্ট পদ্ধতি অনুসরণ করিয়ে।\n"
  },
  {
    "path": "docs/src/content/docs/de/bloc-concepts.mdx",
    "content": "---\ntitle: Bloc Konzepte\ndescription: Ein Überblick über die Kernkonzepte für package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nBitte lies die folgenden Abschnitte sorgfältig durch, bevor du mit\n[`package:bloc`](https://pub.dev/packages/bloc) arbeitest.\n\n:::\n\nEs gibt mehrere Kernkonzepte, die entscheidend sind, um zu verstehen, wie man\ndas Bloc-Package verwendet.\n\nIn den kommenden Abschnitten werden wir jeden davon im Detail besprechen und\ndurcharbeiten, wie sie auf eine Counter-App angewendet werden würden.\n\n## Streams\n\n:::note\n\nSieh dir die offizielle\n[Dart Dokumentation](https://dart.dev/tutorials/language/streams) für weitere\nInformationen über `Streams` an.\n\n:::\n\nEin Stream ist eine Sequenz von asynchronen Daten.\n\nUm die Bloc Library zu verwenden, ist es entscheidend, ein grundlegendes\nVerständnis von `Streams` und ihrer Funktionsweise zu haben.\n\nWenn du mit `Streams` nicht vertraut bist, denk einfach an ein Rohr mit\nfließendem Wasser. Das Rohr ist der `Stream` und das Wasser sind die asynchronen\nDaten.\n\nWir können einen `Stream` in Dart erstellen, indem wir eine `async*` (async\ngenerator) Funktion schreiben.\n\n<CountStreamSnippet />\n\nIndem wir eine Funktion als `async*` markieren, können wir das `yield` keyword\nverwenden und einen `Stream` von Daten zurückgeben. Im obigen Beispiel geben wir\neinen `Stream` von Integers bis zum `max` Integer-Parameter zurück.\n\nJedes Mal, wenn wir in einer `async*` Funktion `yield` verwenden, pushen wir\ndieses Datenstück durch den `Stream`.\n\nWir können den obigen `Stream` auf verschiedene Weise konsumieren. Wenn wir eine\nFunktion schreiben wollten, die die Summe eines `Stream` von Integers\nzurückgibt, könnte sie so aussehen:\n\n<SumStreamSnippet />\n\nIndem wir die obige Funktion als `async` markieren, können wir das `await`\nkeyword verwenden und eine `Future` von Integers zurückgeben. In diesem Beispiel\nwarten wir auf jeden Wert im Stream und geben die Summe aller Integers im Stream\nzurück.\n\nWir können alles zusammenführen:\n\n<StreamsMainSnippet />\n\nJetzt, da wir ein grundlegendes Verständnis davon haben, wie `Streams` in Dart\nfunktionieren, sind wir bereit, mehr über die Kernkomponente des Bloc-Packages\nzu lernen: einen `Cubit`.\n\n## Cubit\n\nEin `Cubit` ist eine Klasse, die `BlocBase` erweitert und erweitert werden kann,\num jeden Typ von State zu verwalten.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nEin `Cubit` kann Funktionen bereitstellen, die aufgerufen werden können, um\nState-Änderungen auszulösen.\n\nStates sind die Ausgabe eines `Cubit` und repräsentieren einen Teil des States\ndeiner Anwendung. UI-Komponenten können über States benachrichtigt werden und\nTeile von sich selbst basierend auf dem aktuellen State neu zeichnen.\n\n:::note\n\nFür weitere Informationen über die Herkunft von `Cubit` sieh dir\n[diesen Issue](https://github.com/felangel/cubit/issues/69) an.\n\n:::\n\n### Erstellen eines Cubit\n\nWir können einen `CounterCubit` so erstellen:\n\n<CounterCubitSnippet />\n\nBeim Erstellen eines `Cubit` müssen wir den Typ des States definieren, den der\n`Cubit` verwalten wird. Im Fall des obigen `CounterCubit` kann der State durch\neinen `int` repräsentiert werden, aber in komplexeren Fällen könnte es notwendig\nsein, eine `class` anstelle eines primitiven Typs zu verwenden.\n\nDas zweite, was wir beim Erstellen eines `Cubit` tun müssen, ist den initialen\nState anzugeben. Wir können dies tun, indem wir `super` mit dem Wert des\ninitialen States aufrufen. Im obigen Snippet setzen wir den initialen State\nintern auf `0`, aber wir können den `Cubit` auch flexibler machen, indem wir\neinen externen Wert akzeptieren:\n\n<CounterCubitInitialStateSnippet />\n\nDies würde es uns ermöglichen, `CounterCubit` Instanzen mit verschiedenen\ninitialen States zu instanziieren:\n\n<CounterCubitInstantiationSnippet />\n\n### Cubit State-Änderungen\n\nJeder `Cubit` hat die Fähigkeit, einen neuen State über `emit` auszugeben.\n\n<CounterCubitIncrementSnippet />\n\nIm obigen Snippet stellt der `CounterCubit` eine öffentliche Methode namens\n`increment` bereit, die extern aufgerufen werden kann, um den `CounterCubit` zu\nbenachrichtigen, seinen State zu erhöhen. Wenn `increment` aufgerufen wird,\nkönnen wir auf den aktuellen State des `Cubit` über den `state` Getter zugreifen\nund einen neuen State durch Addition von 1 zum aktuellen State `emit`en.\n\n:::caution\n\nDie `emit` Methode ist geschützt, was bedeutet, dass sie nur innerhalb eines\n`Cubit` verwendet werden sollte.\n\n:::\n\n### Verwendung eines Cubit\n\nWir können jetzt den `CounterCubit`, den wir implementiert haben, verwenden!\n\n#### Grundlegende Verwendung\n\n<CounterCubitBasicUsageSnippet />\n\nIm obigen Snippet beginnen wir damit, eine Instanz des `CounterCubit` zu\nerstellen. Wir drucken dann den aktuellen State des Cubit, der der initiale\nState ist (da noch keine neuen States emittiert wurden). Als Nächstes rufen wir\ndie `increment` Funktion auf, um eine State-Änderung auszulösen. Schließlich\ndrucken wir den State des `Cubit` erneut, der von `0` auf `1` gegangen ist, und\nrufen `close` auf dem `Cubit` auf, um den internen State- Stream zu schließen.\n\n#### Stream-Verwendung\n\n`Cubit` stellt einen `Stream` bereit, der es uns ermöglicht,\nEchtzeit-State-Updates zu erhalten:\n\n<CounterCubitStreamUsageSnippet />\n\nIm obigen Snippet abonnieren wir den `CounterCubit` und rufen print bei jeder\nState-Änderung auf. Wir rufen dann die `increment` Funktion auf, die einen neuen\nState emittiert. Schließlich rufen wir `cancel` auf dem `subscription` auf, wenn\nwir keine Updates mehr erhalten möchten, und schließen den `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` wird für dieses Beispiel hinzugefügt, um\ndas Abonnement nicht sofort zu kündigen.\n\n:::\n\n:::caution\n\nNur nachfolgende State-Änderungen werden empfangen, wenn `listen` auf einem\n`Cubit` aufgerufen wird.\n\n:::\n\n### Beobachten eines Cubit\n\nWenn ein `Cubit` einen neuen State emittiert, tritt eine `Change` auf. Wir\nkönnen alle Änderungen für einen bestimmten `Cubit` beobachten, indem wir\n`onChange` überschreiben.\n\n<CounterCubitOnChangeSnippet />\n\nWir können dann mit dem `Cubit` interagieren und alle Änderungen in der Konsole\nausgeben.\n\n<CounterCubitOnChangeUsageSnippet />\n\nDas obige Beispiel würde folgende Ausgabe erzeugen:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nEine `Change` tritt kurz vor der Aktualisierung des States des `Cubit` auf. Eine\n`Change` besteht aus dem `currentState` und dem `nextState`.\n\n:::\n\n#### BlocObserver\n\nEin zusätzlicher Vorteil der Verwendung der Bloc Library ist, dass wir Zugriff\nauf alle `Changes` an einem Ort haben. Obwohl wir in dieser Anwendung nur einen\n`Cubit` haben, ist es in größeren Anwendungen ziemlich üblich, viele `Cubits` zu\nhaben, die verschiedene Teile des States der Anwendung verwalten.\n\nWenn wir in der Lage sein wollen, auf alle `Changes` zu reagieren, können wir\neinfach unseren eigenen `BlocObserver` erstellen.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nAlles, was wir tun müssen, ist `BlocObserver` zu erweitern und die `onChange`\nMethode zu überschreiben.\n\n:::\n\nUm den `SimpleBlocObserver` zu verwenden, müssen wir nur die `main` Funktion\nanpassen:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nDas obige Snippet würde dann folgende Ausgabe erzeugen:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nDie interne `onChange` Überschreibung wird zuerst aufgerufen, die\n`super.onChange` aufruft und damit `onChange` im `BlocObserver` benachrichtigt.\n\n:::\n\n:::tip\n\nIn `BlocObserver` haben wir Zugriff auf die `Cubit` Instanz zusätzlich zur\n`Change` selbst.\n\n:::\n\n### Cubit Fehlerbehandlung\n\nJeder `Cubit` hat eine `addError` Methode, die verwendet werden kann, um\nanzuzeigen, dass ein Fehler aufgetreten ist.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` kann innerhalb des `Cubit` überschrieben werden, um alle Fehler für\neinen spezifischen `Cubit` zu behandeln.\n\n:::\n\n`onError` kann auch in `BlocObserver` überschrieben werden, um alle gemeldeten\nFehler global zu behandeln.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nWenn wir das gleiche Programm erneut ausführen, sollten wir folgende Ausgabe\nsehen:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nEin `Bloc` ist eine fortgeschrittenere Klasse, die sich auf `Events` verlässt,\num `State`- Änderungen auszulösen, anstatt auf Funktionen. `Bloc` erweitert auch\n`BlocBase`, was bedeutet, dass es eine ähnliche öffentliche API wie `Cubit` hat.\nAnstatt jedoch eine `function` auf einem `Bloc` aufzurufen und direkt einen\nneuen `state` zu emittieren, empfangen `Blocs` `events` und konvertieren die\neingehenden `events` in ausgehende `states`.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Erstellen eines Bloc\n\nDas Erstellen eines `Bloc` ist ähnlich wie das Erstellen eines `Cubit`, außer\ndass wir zusätzlich zur Definition des States, den wir verwalten werden, auch\ndas Event definieren müssen, das der `Bloc` verarbeiten können wird.\n\nEvents sind die Eingabe eines Bloc. Sie werden häufig als Reaktion auf Benutzer-\ninteraktionen wie Button-Drücke oder Lifecycle-Events wie Seitenladungen\nhinzugefügt.\n\n<CounterBlocSnippet />\n\nGenau wie beim Erstellen des `CounterCubit` müssen wir einen initialen State\nangeben, indem wir ihn über `super` an die Superklasse übergeben.\n\n### Bloc State-Änderungen\n\n`Bloc` erfordert, dass wir Event-Handler über die `on<Event>` API registrieren,\nim Gegensatz zu Funktionen in `Cubit`. Ein Event-Handler ist dafür\nverantwortlich, alle eingehenden Events in null oder mehr ausgehende States\numzuwandeln.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nEin `EventHandler` hat Zugriff auf das hinzugefügte Event sowie einen `Emitter`,\nder verwendet werden kann, um null oder mehr States als Reaktion auf das\neingehende Event zu emittieren.\n\n:::\n\nWir können dann den `EventHandler` aktualisieren, um das\n`CounterIncrementPressed` Event zu behandeln:\n\n<CounterBlocIncrementSnippet />\n\nIm obigen Snippet haben wir einen `EventHandler` registriert, um alle\n`CounterIncrementPressed` Events zu verwalten. Für jedes eingehende\n`CounterIncrementPressed` Event können wir auf den aktuellen State des Bloc über\nden `state` Getter zugreifen und `emit(state + 1)` aufrufen.\n\n:::note\n\nDa die `Bloc` Klasse `BlocBase` erweitert, haben wir Zugriff auf den aktuellen\nState des Bloc zu jedem Zeitpunkt über den `state` Getter, genau wie in `Cubit`.\n\n:::\n\n:::caution\n\nBlocs sollten niemals direkt neue States `emit`en. Stattdessen muss jede\nState-Änderung als Reaktion auf ein eingehendes Event innerhalb eines\n`EventHandler` ausgegeben werden.\n\n:::\n\n:::caution\n\nSowohl Blocs als auch Cubits ignorieren doppelte States. Wenn wir\n`State nextState` emittieren, wobei `state == nextState`, dann tritt keine\nState-Änderung auf.\n\n:::\n\n### Verwendung eines Bloc\n\nAn diesem Punkt können wir eine Instanz unseres `CounterBloc` erstellen und\nverwenden!\n\n#### Grundlegende Verwendung\n\n<CounterBlocUsageSnippet />\n\nIm obigen Snippet beginnen wir damit, eine Instanz des `CounterBloc` zu\nerstellen. Wir drucken dann den aktuellen State des `Bloc`, der der initiale\nState ist (da noch keine neuen States emittiert wurden). Als Nächstes fügen wir\ndas `CounterIncrementPressed` Event hinzu, um eine State-Änderung auszulösen.\nSchließlich drucken wir den State des `Bloc` erneut, der von `0` auf `1`\ngegangen ist, und rufen `close` auf dem `Bloc` auf, um den internen State-Stream\nzu schließen.\n\n:::note\n\n`await Future.delayed(Duration.zero)` wird hinzugefügt, um sicherzustellen, dass\nwir auf die nächste Event-Loop-Iteration warten (ermöglicht dem `EventHandler`,\ndas Event zu verarbeiten).\n\n:::\n\n#### Stream-Verwendung\n\nGenau wie bei `Cubit` ist ein `Bloc` ein spezieller Typ von `Stream`, was\nbedeutet, dass wir uns auch für einen `Bloc` anmelden können, um\nEchtzeit-Updates seines States zu erhalten:\n\n<CounterBlocStreamUsageSnippet />\n\nIm obigen Snippet abonnieren wir den `CounterBloc` und rufen print bei jeder\nState-Änderung auf. Wir fügen dann das `CounterIncrementPressed` Event hinzu,\ndas den `on<CounterIncrementPressed>` `EventHandler` auslöst und einen neuen\nState emittiert. Schließlich rufen wir `cancel` auf dem Abonnement auf, wenn wir\nkeine Updates mehr erhalten möchten, und schließen den `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` wird für dieses Beispiel hinzugefügt, um\ndas Abonnement nicht sofort zu kündigen.\n\n:::\n\n### Beobachten eines Bloc\n\nDa `Bloc` `BlocBase` erweitert, können wir alle State-Änderungen für einen\n`Bloc` mit `onChange` beobachten.\n\n<CounterBlocOnChangeSnippet />\n\nWir können dann `main.dart` aktualisieren zu:\n\n<CounterBlocOnChangeUsageSnippet />\n\nWenn wir nun das obige Snippet ausführen, wird die Ausgabe sein:\n\n<CounterBlocOnChangeOutputSnippet />\n\nEin wichtiger Unterscheidungsfaktor zwischen `Bloc` und `Cubit` ist, dass wir,\nweil `Bloc` event-gesteuert ist, auch Informationen darüber erfassen können, was\ndie State-Änderung ausgelöst hat.\n\nWir können dies tun, indem wir `onTransition` überschreiben.\n\nDie Änderung von einem State zu einem anderen wird `Transition` genannt. Eine\n`Transition` besteht aus dem aktuellen State, dem Event und dem nächsten State.\n\n<CounterBlocOnTransitionSnippet />\n\nWenn wir dann das gleiche `main.dart` Snippet von vorher erneut ausführen,\nsollten wir die folgende Ausgabe sehen:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` wird vor `onChange` aufgerufen und enthält das Event, das die\nÄnderung von `currentState` zu `nextState` ausgelöst hat.\n\n:::\n\n#### BlocObserver\n\nGenau wie zuvor können wir `onTransition` in einem benutzerdefinierten\n`BlocObserver` überschreiben, um alle Transitions zu beobachten, die von einem\neinzigen Ort aus auftreten.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nWir können den `SimpleBlocObserver` genau wie zuvor initialisieren:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nWenn wir nun das obige Snippet ausführen, sollte die Ausgabe so aussehen:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` wird zuerst aufgerufen (lokal vor global) gefolgt von `onChange`.\n\n:::\n\nEine weitere einzigartige Funktion von `Bloc` Instanzen ist, dass sie uns\nerlauben, `onEvent` zu überschreiben, das aufgerufen wird, wann immer ein neues\nEvent zum `Bloc` hinzugefügt wird. Genau wie bei `onChange` und `onTransition`\nkann `onEvent` sowohl lokal als auch global überschrieben werden.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nWir können das gleiche `main.dart` wie zuvor ausführen und sollten folgende\nAusgabe sehen:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` wird aufgerufen, sobald das Event hinzugefügt wird. Das lokale\n`onEvent` wird vor dem globalen `onEvent` in `BlocObserver` aufgerufen.\n\n:::\n\n### Bloc Fehlerbehandlung\n\nGenau wie bei `Cubit` hat jeder `Bloc` eine `addError` und `onError` Methode.\nWir können anzeigen, dass ein Fehler aufgetreten ist, indem wir `addError` von\nüberall innerhalb unseres `Bloc` aufrufen. Wir können dann auf alle Fehler\nreagieren, indem wir `onError` überschreiben, genau wie bei `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nWenn wir das gleiche `main.dart` wie zuvor erneut ausführen, können wir sehen,\nwie es aussieht, wenn ein Fehler gemeldet wird:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nDas lokale `onError` wird zuerst aufgerufen, gefolgt vom globalen `onError` in\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` und `onChange` funktionieren auf genau die gleiche Weise für sowohl\n`Bloc` als auch `Cubit` Instanzen.\n\n:::\n\n:::caution\n\nAlle unbehandelten Ausnahmen, die innerhalb eines `EventHandler` auftreten,\nwerden auch an `onError` gemeldet.\n\n:::\n\n## Cubit vs. Bloc\n\nJetzt, da wir die Grundlagen der `Cubit` und `Bloc` Klassen behandelt haben,\nfragst du dich vielleicht, wann du `Cubit` verwenden solltest und wann du `Bloc`\nverwenden solltest.\n\n### Cubit Vorteile\n\n#### Unkompliziert\n\nEiner der größten Vorteile der Verwendung von `Cubit` ist, dass es unkompliziert\nist. Beim Erstellen eines `Cubit` müssen wir nur den State sowie die Funktionen\ndefinieren, die wir bereitstellen möchten, um den State zu ändern. Im Vergleich\ndazu müssen wir beim Erstellen eines `Bloc` die States, Events und die\n`EventHandler` Implementierung definieren. Dies macht `Cubit` einfacher zu\nverstehen und erfordert weniger Code.\n\nLass uns nun einen Blick auf die beiden Counter-Implementierungen werfen:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nDie `Cubit` Implementierung ist kompakter und anstatt Events separat zu\ndefinieren, verhalten sich die Funktionen wie Events. Zusätzlich können wir,\nwenn wir einen `Cubit` verwenden, einfach `emit` von überall aufrufen, um eine\nState-Änderung auszulösen.\n\n### Bloc Vorteile\n\n#### Nachverfolgbarkeit\n\nEiner der größten Vorteile der Verwendung von `Bloc` ist, die Sequenz von State-\nÄnderungen sowie genau zu wissen, was diese Änderungen ausgelöst hat. Für State,\nder kritisch für die Funktionalität einer Anwendung ist, könnte es sehr\nvorteilhaft sein, einen event-gesteuerten Ansatz zu verwenden, um alle Events\nzusätzlich zu State-Änderungen zu erfassen.\n\nEin häufiger Anwendungsfall könnte die Verwaltung von `AuthenticationState`\nsein. Der Einfachheit halber nehmen wir an, dass wir `AuthenticationState` über\nein `enum` repräsentieren können:\n\n<AuthenticationStateSnippet />\n\nEs könnte viele Gründe geben, warum sich der State der Anwendung von\n`authenticated` zu `unauthenticated` ändern könnte. Zum Beispiel könnte der\nBenutzer auf einen Logout-Button getippt haben und sich von der Anwendung\nabmelden wollen. Auf der anderen Seite könnte das Access-Token des Benutzers\nwiderrufen worden sein und sie wurden zwangsweise abgemeldet. Wenn wir `Bloc`\nverwenden, können wir klar nachverfolgen, wie der Anwendungsstate zu einem\nbestimmten State gelangt ist.\n\n<AuthenticationTransitionSnippet />\n\nDie obige `Transition` gibt uns alle Informationen, die wir brauchen, um zu\nverstehen, warum sich der State geändert hat. Wenn wir einen `Cubit` verwendet\nhätten, um den `AuthenticationState` zu verwalten, würden unsere Logs so\naussehen:\n\n<AuthenticationChangeSnippet />\n\nDies sagt uns, dass der Benutzer abgemeldet wurde, erklärt aber nicht warum, was\nfür das Debugging und das Verstehen, wie sich der State der Anwendung im Laufe\nder Zeit ändert, kritisch sein könnte.\n\n#### Erweiterte Event-Transformationen\n\nEin weiterer Bereich, in dem `Bloc` gegenüber `Cubit` hervorsticht, ist, wenn\nwir reaktive Operatoren wie `buffer`, `debounceTime`, `throttle`, etc. nutzen\nmüssen.\n\n:::tip\n\nSieh dir [`package:stream_transform`](https://pub.dev/packages/stream_transform)\nund [`package:rxdart`](https://pub.dev/packages/rxdart) für\nStream-Transformatoren an.\n\n:::\n\n`Bloc` hat einen Event-Sink, der es uns ermöglicht, den eingehenden Event-Flow\nzu steuern und zu transformieren.\n\nWenn wir zum Beispiel eine Echtzeit-Suche bauen würden, würden wir\nwahrscheinlich die Anfragen an das Backend debouncen wollen, um Rate-Limiting zu\nvermeiden sowie Kosten/Last auf dem Backend zu reduzieren.\n\nMit `Bloc` können wir einen benutzerdefinierten `EventTransformer`\nbereitstellen, um die Art zu ändern, wie eingehende Events vom `Bloc`\nverarbeitet werden.\n\n<DebounceEventTransformerSnippet />\n\nMit dem obigen Code können wir die eingehenden Events mit sehr wenig\nzusätzlichem Code einfach debouncen.\n\n:::tip\n\nSieh dir [`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)\nfür einen vorgefertigten Satz von Event-Transformatoren an.\n\n:::\n\nWenn du unsicher bist, was du verwenden sollst, beginne mit `Cubit` und du\nkannst später bei Bedarf zu einem `Bloc` refactoren oder hochskalieren.\n"
  },
  {
    "path": "docs/src/content/docs/de/getting-started.mdx",
    "content": "---\ntitle: Erste Schritte\ndescription: Alles, was du brauchst, um mit Bloc zu starten.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Packages\n\nDas Bloc-Ökosystem besteht aus mehreren Packages, die unten aufgeführt sind:\n\n| Package                                                                                    | Beschreibung                | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart Komponenten     | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Core Dart APIs              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers          | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | Testing APIs                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools          | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter Widgets             | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo Support           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Installation\n\n<InstallationTabs />\n\n:::note\n\nUm Bloc verwenden zu können, musst du das [Dart SDK](https://dart.dev/get-dart)\nauf deinem Computer installiert haben.\n\n:::\n\n## Imports\n\nNachdem wir Bloc erfolgreich installiert haben, können wir unsere `main.dart`\nerstellen und das entsprechende `bloc` Package importieren.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/de/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ Besuche den\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Eine vorhersehbare State Management Library für Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Erste Schritte\n      link: /de/getting-started/\n      variant: primary\n      icon: rocket\n    - text: GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"Mit 💖 gesponsert von\"\n\tbecomeASponsor=\"Sponsor werden\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Erste Schritte\" icon=\"rocket\">\n\t```sh\n\t# Füge Bloc deinem Projekt hinzu\n\tdart pub add bloc\n\t```\n\nIn unserem [Getting Started Guide](/de/getting-started) findest du eine\nSchritt-für-Schritt-Anleitung, wie du Bloc in nur wenigen Minuten nutzen kannst.\n\n</SplitCard>\n\n<Card title=\"Offizielle Tutorials\" icon=\"star\">\n\tIn den [offiziellen Tutorials](/de/tutorials/flutter-counter) lernst du Best\n\tPractices kennen und baust verschiedene Apps mit Bloc.\n</Card>\n\n<Card title=\"Apps, die Bloc nutzen\" icon=\"laptop\">\n\tEntdecke hochwertige, vollständig getestete\n\t[Beispiel-Apps](https://github.com/felangel/bloc/tree/master/examples) wie\n\tz.B. einen Counter, einen Timer, eine unendliche Liste, eine Wetter-App, eine\n\tTodo-App und weitere!\n</Card>\n\n<ListCard title=\"Lerne\" icon=\"open-book\">\n\n    - [Warum Bloc?](/de/why-bloc)\n    - [Grundlegende Konzepte](/de/bloc-concepts)\n    - [Architektur](/de/architecture)\n    - [Testen](/de/testing)\n    - [Namenskonventionen](/de/naming-conventions)\n    - [FAQs](/de/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integrationen\" icon=\"puzzle\">\n    - [VSCode Integration](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ Integration](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim Integration](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI Integration](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Custom Templates](https://brickhub.dev/search?q=bloc)\n    - [Entwickler Tools](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Tritt unserem Discord bei\" />\n"
  },
  {
    "path": "docs/src/content/docs/de/why-bloc.mdx",
    "content": "---\ntitle: Warum Bloc?\ndescription:\n  Ein Überblick darüber, was Bloc zu einer soliden State Management Lösung\n  macht.\nsidebar:\n  order: 1\n---\n\nBloc macht es einfach, UI von Geschäftslogik zu trennen, wodurch dein Code\n_schnell_, _einfach zu testen_ und _wiederverwendbar_ wird.\n\nBeim Entwickeln von produktionsreifen Anwendungen wird das State Management\nkritisch.\n\nAls Entwickler möchten wir:\n\n- zu jedem Zeitpunkt wissen, in welchem State sich unsere Anwendung befindet.\n- jeden Fall einfach testen, um sicherzustellen, dass unsere App angemessen\n  reagiert.\n- jede einzelne Benutzerinteraktion in unserer Anwendung aufzeichnen, damit wir\n  datengetriebene Entscheidungen treffen können.\n- so effizient wie möglich arbeiten und Komponenten sowohl innerhalb unserer\n  Anwendung als auch in anderen Anwendungen wiederverwenden.\n- viele Entwickler nahtlos in einer einzigen Codebasis arbeiten lassen, die\n  denselben Mustern und Konventionen folgt.\n- schnelle und reaktive Apps entwickeln.\n\nBloc wurde entwickelt, um all diese Anforderungen und viele weitere zu erfüllen.\n\nEs gibt viele State Management Lösungen und die Entscheidung, welche man\nverwenden soll, kann eine Entmutigend Aufgabe sein. Es gibt keine perfekte State\nManagement Lösung! Wichtig ist, dass du diejenige wählst, die am besten für dein\nTeam und dein Projekt funktioniert.\n\nBloc wurde nach drei Grundprinzipien entwickelt:\n\n- **Einfach:** Leicht zu verstehen & kann von Entwicklern mit unterschiedlichen\n  Fähigkeitsniveaus verwendet werden.\n- **Mächtig:** Hilft dabei, großartige, komplexe Anwendungen zu erstellen, indem\n  sie aus kleineren Komponenten zusammengesetzt werden.\n- **Testbar:** Jeden Aspekt einer Anwendung einfach testen, damit wir mit\n  Vertrauen iterieren können.\n\nIm Großen und Ganzen versucht Bloc, State-Änderungen vorhersehbar zu machen,\nindem es regelt, wann eine State-Änderung auftreten kann und eine einzige Art\nder State-Änderung in einer gesamten Anwendung durchsetzt.\n"
  },
  {
    "path": "docs/src/content/docs/es/architecture.mdx",
    "content": "---\ntitle: Arquitectura\ndescription:\n  Descripción general de los patrones de arquitectura recomendados al usar bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Arquitectura Bloc](~/assets/concepts/bloc_architecture_full.png)\n\nUsar la biblioteca bloc nos permite separar nuestra aplicación en tres capas:\n\n- Presentación\n- Lógica de Negocio\n- Datos\n  - Repositorio\n  - Proveedor de Datos\n\nVamos a comenzar en la capa de nivel más bajo (más alejada de la interfaz de\nusuario) y trabajaremos hacia arriba hasta la capa de presentación.\n\n## Capa de Datos\n\nLa responsabilidad de la capa de datos es recuperar/manipular datos de una o más\nfuentes.\n\nLa capa de datos se puede dividir en dos partes:\n\n- Repositorio\n- Proveedor de Datos\n\nEsta capa es el nivel más bajo de la aplicación e interactúa con bases de datos,\nsolicitudes de red y otras fuentes de datos asíncronas.\n\n### Proveedor de Datos\n\nLa responsabilidad del proveedor de datos es proporcionar datos en bruto. El\nproveedor de datos debe ser genérico y versátil.\n\nEl proveedor de datos generalmente expondrá APIs simples para realizar\noperaciones\n[CRUD](https://es.wikipedia.org/wiki/Crear,_leer,_actualizar_y_borrar).\nPodríamos tener un método `createData`, `readData`, `updateData` y `deleteData`\ncomo parte de nuestra capa de datos.\n\n<DataProviderSnippet />\n\n### Repositorio\n\nLa capa de repositorio es un envoltorio alrededor de uno o más proveedores de\ndatos con los que se comunica la capa Bloc.\n\n<RepositorySnippet />\n\nComo puedes ver, nuestra capa de repositorio puede interactuar con múltiples\nproveedores de datos y realizar transformaciones en los datos antes de entregar\nel resultado a la capa de lógica de negocio.\n\n## Capa de Lógica de Negocio\n\nLa responsabilidad de la capa de lógica de negocio es responder a la entrada de\nla capa de presentación con nuevos estados. Esta capa puede depender de uno o\nmás repositorios para recuperar los datos necesarios para construir el estado de\nla aplicación.\n\nPiensa en la capa de lógica de negocio como el puente entre la interfaz de\nusuario (capa de presentación) y la capa de datos. La capa de lógica de negocio\nes notificada de eventos/acciones desde la capa de presentación y luego se\ncomunica con el repositorio para construir un nuevo estado para que la capa de\npresentación lo consuma.\n\n<BusinessLogicComponentSnippet />\n\n### Comunicación Bloc a Bloc\n\nDebido a que los blocs exponen streams, puede ser tentador hacer un bloc que\nescuche a otro bloc. No deberías **hacer** esto. Hay mejores alternativas que\nrecurrir al siguiente código:\n\n<BlocTightCouplingSnippet />\n\nAunque el código anterior no tiene errores (e incluso se limpia después de sí\nmismo), tiene un problema mayor: crea una dependencia entre dos blocs.\n\nGeneralmente, las dependencias entre dos entidades en la misma capa\narquitectónica deben evitarse a toda costa, ya que crea un acoplamiento fuerte\nque es difícil de mantener. Dado que los blocs residen en la capa arquitectónica\nde lógica de negocio, ningún bloc debería conocer a otro bloc.\n\n![Capas de Arquitectura de la Aplicación](~/assets/architecture/architecture.png)\n\nUn bloc solo debería recibir información a través de eventos y de repositorios\ninyectados (es decir, repositorios dados al bloc en su constructor).\n\nSi te encuentras en una situación donde un bloc necesita responder a otro bloc,\ntienes dos opciones. Puedes empujar el problema hacia arriba (a la capa de\npresentación) o hacia abajo (a la capa de dominio).\n\n#### Conectando Blocs a través de la Presentación\n\nPuedes usar un `BlocListener` para escuchar a un bloc y agregar un evento a otro\nbloc cada vez que el primer bloc cambie.\n\n<BlocLooseCouplingPresentationSnippet />\n\nEl código anterior evita que `SecondBloc` necesite conocer a `FirstBloc`,\nfomentando el acoplamiento débil. La aplicación\n[flutter_weather](/es/tutorials/flutter-weather)\n[usa esta técnica](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\npara cambiar el tema de la aplicación basado en la información del clima que se\nrecibe.\n\nEn algunas situaciones, puede que no quieras acoplar dos blocs en la capa de\npresentación. En su lugar, a menudo tiene sentido que dos blocs compartan la\nmisma fuente de datos y se actualicen cada vez que los datos cambien.\n\n#### Conectando Blocs a través del Dominio\n\nDos blocs pueden escuchar un stream de un repositorio y actualizar sus estados\nindependientemente cada vez que los datos del repositorio cambien. Usar\nrepositorios reactivos para mantener el estado sincronizado es común en\naplicaciones empresariales a gran escala.\n\nPrimero, crea o usa un repositorio que proporcione un `Stream` de datos. Por\nejemplo, el siguiente repositorio expone un stream interminable de las mismas\npocas ideas de aplicaciones:\n\n<AppIdeasRepositorySnippet />\n\nEl mismo repositorio puede ser inyectado en cada bloc que necesite reaccionar a\nnuevas ideas de aplicaciones. A continuación se muestra un `AppIdeaRankingBloc`\nque emite un estado por cada idea de aplicación entrante del repositorio\nanterior:\n\n<AppIdeaRankingBlocSnippet />\n\nPara más información sobre cómo usar streams con Bloc, consulta\n[Cómo usar Bloc con streams y concurrencia](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Capa de Presentación\n\nLa responsabilidad de la capa de presentación es determinar cómo renderizarse a\nsí misma en función de uno o más estados del bloc. Además, debe manejar la\nentrada del usuario y los eventos del ciclo de vida de la aplicación.\n\nLa mayoría de los flujos de aplicaciones comenzarán con un evento `AppStart` que\ndesencadena la aplicación para obtener algunos datos para presentar al usuario.\n\nEn este escenario, la capa de presentación agregaría un evento `AppStart`.\n\nAdemás, la capa de presentación tendrá que determinar qué renderizar en la\npantalla en función del estado de la capa de bloc.\n\n<PresentationComponentSnippet />\n\nHasta ahora, aunque hemos tenido algunos fragmentos de código, todo esto ha sido\nbastante a alto nivel. En la sección de tutoriales vamos a juntar todo esto\nmientras construimos varias aplicaciones de ejemplo diferentes.\n"
  },
  {
    "path": "docs/src/content/docs/es/bloc-concepts.mdx",
    "content": "---\ntitle: Conceptos de Bloc\ndescription: Una visión general de los conceptos básicos para package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nPor favor, asegúrate de leer cuidadosamente las siguientes secciones antes de\ntrabajar con [`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nHay varios conceptos clave que son críticos para entender cómo usar el paquete\nbloc.\n\nEn las próximas secciones, vamos a discutir cada uno de ellos en detalle y\ntambién trabajaremos en cómo se aplicarían a una aplicación de contador.\n\n## Streams\n\n:::note\n\nConsulta la\n[Documentación oficial de Dart](https://dart.dev/tutorials/language/streams)\npara obtener más información sobre `Streams`.\n\n:::\n\nUn stream es una secuencia de datos asíncronos.\n\nPara usar la biblioteca bloc, es fundamental tener una comprensión básica de los\n`Streams` y cómo funcionan.\n\nSi no estás familiarizado con los `Streams`, piensa en una tubería con agua\nfluyendo a través de ella. La tubería es el `Stream` y el agua son los datos\nasíncronos.\n\nPodemos crear un `Stream` en Dart escribiendo una función `async*` (generador\nasíncrono).\n\n<CountStreamSnippet />\n\nAl marcar una función como `async*` podemos usar la palabra clave `yield` y\ndevolver un `Stream` de datos. En el ejemplo anterior, estamos devolviendo un\n`Stream` de enteros hasta el parámetro entero `max`.\n\nCada vez que usamos `yield` en una función `async*` estamos empujando ese dato a\ntravés del `Stream`.\n\nPodemos consumir el `Stream` anterior de varias maneras. Si quisiéramos escribir\nuna función para devolver la suma de un `Stream` de enteros, podría verse algo\nasí:\n\n<SumStreamSnippet />\n\nAl marcar la función anterior como `async` podemos usar la palabra clave `await`\ny devolver un `Future` de enteros. En este ejemplo, estamos esperando cada valor\nen el stream y devolviendo la suma de todos los enteros en el stream.\n\nPodemos juntar todo de la siguiente manera:\n\n<StreamsMainSnippet />\n\nAhora que tenemos una comprensión básica de cómo funcionan los `Streams` en\nDart, estamos listos para aprender sobre el componente principal del paquete\nbloc: un `Cubit`.\n\n## Cubit\n\nUn `Cubit` es una clase que extiende `BlocBase` y puede ser extendida para\ngestionar cualquier tipo de estado.\n\n![Arquitectura de Cubit](~/assets/concepts/cubit_architecture_full.png)\n\nUn `Cubit` puede exponer funciones que pueden ser invocadas para desencadenar\ncambios de estado.\n\nLos estados son la salida de un `Cubit` y representan una parte del estado de tu\naplicación. Los componentes de la interfaz de usuario pueden ser notificados de\nlos estados y redibujar partes de sí mismos en función del estado actual.\n\n:::note\n\nPara obtener más información sobre los orígenes de `Cubit`, consulta\n[el siguiente issue](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Creando un Cubit\n\nPodemos crear un `CounterCubit` así:\n\n<CounterCubitSnippet />\n\nCuando creamos un `Cubit`, necesitamos definir el tipo de estado que el `Cubit`\ngestionará. En el caso del `CounterCubit` anterior, el estado puede ser\nrepresentado mediante un `int`, pero en casos más complejos podría ser necesario\nusar una `class` en lugar de un tipo primitivo.\n\nLa segunda cosa que necesitamos hacer al crear un `Cubit` es especificar el\nestado inicial. Podemos hacer esto llamando a `super` con el valor del estado\ninicial. En el fragmento anterior, estamos configurando el estado inicial a `0`\ninternamente, pero también podemos permitir que el `Cubit` sea más flexible\naceptando un valor externo:\n\n<CounterCubitInitialStateSnippet />\n\nEsto nos permitiría instanciar `CounterCubit` con diferentes estados iniciales\ncomo:\n\n<CounterCubitInstantiationSnippet />\n\n### Cambios de Estado en Cubit\n\nCada `Cubit` tiene la capacidad de emitir un nuevo estado mediante `emit`.\n\n<CounterCubitIncrementSnippet />\n\nEn el fragmento anterior, el `CounterCubit` está exponiendo un método público\nllamado `increment` que puede ser llamado externamente para notificar al\n`CounterCubit` que incremente su estado. Cuando se llama a `increment`, podemos\nacceder al estado actual del `Cubit` mediante el getter `state` y emitir un\nnuevo estado sumando 1 al estado actual.\n\n:::caution\n\nEl método `emit` es protegido, lo que significa que solo debe ser usado dentro\nde un `Cubit`.\n\n:::\n\n### Usando un Cubit\n\nAhora podemos tomar el `CounterCubit` que hemos implementado y ponerlo en uso.\n\n#### Uso Básico\n\n<CounterCubitBasicUsageSnippet />\n\nEn el fragmento anterior, comenzamos creando una instancia del `CounterCubit`.\nLuego imprimimos el estado actual del cubit, que es el estado inicial (ya que no\nse han emitido nuevos estados aún). A continuación, llamamos a la función\n`increment` para desencadenar un cambio de estado. Finalmente, imprimimos el\nestado del `Cubit` nuevamente, que pasó de `0` a `1` y llamamos a `close` en el\n`Cubit` para cerrar el stream interno de estado.\n\n#### Uso de Stream\n\n`Cubit` expone un `Stream` que nos permite recibir actualizaciones de estado en\ntiempo real:\n\n<CounterCubitStreamUsageSnippet />\n\nEn el fragmento anterior, nos estamos suscribiendo al `CounterCubit` y llamando\na imprimir en cada cambio de estado. Luego invocamos la función `increment` que\nemitirá un nuevo estado. Por último, llamamos a `cancel` en la suscripción\ncuando ya no queremos recibir actualizaciones y cerramos el `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` se agrega para este ejemplo para evitar\ncancelar la suscripción inmediatamente.\n\n:::\n\n:::caution\n\nSolo se recibirán cambios de estado subsecuentes al llamar a `listen` en un\n`Cubit`.\n\n:::\n\n### Observando un Cubit\n\nCuando un `Cubit` emite un nuevo estado, ocurre un `Change`. Podemos observar\ntodos los cambios para un `Cubit` dado sobrescribiendo `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nLuego podemos interactuar con el `Cubit` y observar todos los cambios impresos\nen la consola.\n\n<CounterCubitOnChangeUsageSnippet />\n\nEl ejemplo anterior imprimiría:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nUn `Change` ocurre justo antes de que el estado del `Cubit` se actualice. Un\n`Change` consiste en el `currentState` y el `nextState`.\n\n:::\n\n#### BlocObserver\n\nUna ventaja adicional de usar la biblioteca bloc es que podemos tener acceso a\ntodos los `Changes` en un solo lugar. Aunque en esta aplicación solo tenemos un\n`Cubit`, es bastante común en aplicaciones más grandes tener muchos `Cubits`\ngestionando diferentes partes del estado de la aplicación.\n\nSi queremos poder hacer algo en respuesta a todos los `Changes`, simplemente\npodemos crear nuestro propio `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nTodo lo que necesitamos hacer es extender `BlocObserver` y sobrescribir el\nmétodo `onChange`.\n\n:::\n\nPara usar el `SimpleBlocObserver`, solo necesitamos ajustar la función `main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nEl fragmento anterior imprimiría:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nLa sobrescritura interna de `onChange` se llama primero, lo que llama a\n`super.onChange` notificando al `onChange` en el `BlocObserver`.\n\n:::\n\n:::tip\n\nEn `BlocObserver` tenemos acceso a la instancia del `Cubit` además del `Change`\nen sí.\n\n:::\n\n### Manejo de Errores en Cubit\n\nCada `Cubit` tiene un método `addError` que puede ser usado para indicar que ha\nocurrido un error.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` puede ser sobrescrito dentro del `Cubit` para manejar todos los\nerrores para un `Cubit` específico.\n\n:::\n\n`onError` también puede ser sobrescrito en `BlocObserver` para manejar todos los\nerrores reportados globalmente.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nSi ejecutamos el mismo programa nuevamente, deberíamos ver la siguiente salida:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nUn `Bloc` es una clase más avanzada que se basa en `eventos` para desencadenar\ncambios de `estado` en lugar de funciones. `Bloc` también extiende `BlocBase`,\nlo que significa que tiene una API pública similar a `Cubit`. Sin embargo, en\nlugar de llamar a una `función` en un `Bloc` y emitir directamente un nuevo\n`estado`, los `Blocs` reciben `eventos` y convierten los `eventos` entrantes en\n`estados` salientes.\n\n![Arquitectura de Bloc](~/assets/concepts/bloc_architecture_full.png)\n\n### Creando un Bloc\n\nCrear un `Bloc` es similar a crear un `Cubit`, excepto que además de definir el\nestado que gestionaremos, también debemos definir el evento que el `Bloc` podrá\nprocesar.\n\nLos eventos son la entrada a un Bloc. Comúnmente se agregan en respuesta a\ninteracciones del usuario, como presiones de botones o eventos de ciclo de vida\ncomo cargas de página.\n\n<CounterBlocSnippet />\n\nAl igual que cuando creamos el `CounterCubit`, debemos especificar un estado\ninicial pasándolo a la superclase a través de `super`.\n\n### Cambios de Estado en Bloc\n\n`Bloc` requiere que registremos manejadores de eventos a través de la API\n`on<Event>`, a diferencia de las funciones en `Cubit`. Un manejador de eventos\nes responsable de convertir cualquier evento entrante en cero o más estados\nsalientes.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nUn `EventHandler` tiene acceso al evento agregado así como a un `Emitter` que\npuede ser usado para emitir cero o más estados en respuesta al evento entrante.\n\n:::\n\nLuego podemos actualizar el `EventHandler` para manejar el evento\n`CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nEn el fragmento anterior, hemos registrado un `EventHandler` para gestionar\ntodos los eventos `CounterIncrementPressed`. Para cada evento\n`CounterIncrementPressed` entrante, podemos acceder al estado actual del bloc a\ntravés del getter `state` y `emit(state + 1)`.\n\n:::note\n\nDado que la clase `Bloc` extiende `BlocBase`, tenemos acceso al estado actual\ndel bloc en cualquier momento a través del getter `state`, al igual que en\n`Cubit`.\n\n:::\n\n:::caution\n\nLos blocs nunca deben emitir directamente nuevos estados. En su lugar, cada\ncambio de estado debe ser resultado de un evento entrante dentro de un\n`EventHandler`.\n\n:::\n\n:::caution\n\nTanto los blocs como los cubits ignorarán estados duplicados. Si emitimos\n`State nextState` donde `state == nextState`, entonces no ocurrirá ningún cambio\nde estado.\n\n:::\n\n### Usando un Bloc\n\nEn este punto, podemos crear una instancia de nuestro `CounterBloc` y ponerlo en\nuso.\n\n#### Uso Básico\n\n<CounterBlocUsageSnippet />\n\nEn el fragmento anterior, comenzamos creando una instancia del `CounterBloc`.\nLuego imprimimos el estado actual del `Bloc`, que es el estado inicial (ya que\nno se han emitido nuevos estados aún). A continuación, agregamos el evento\n`CounterIncrementPressed` para desencadenar un cambio de estado. Finalmente,\nimprimimos el estado del `Bloc` nuevamente, que pasó de `0` a `1` y llamamos a\n`close` en el `Bloc` para cerrar el stream interno de estado.\n\n:::note\n\n`await Future.delayed(Duration.zero)` se agrega para asegurar que esperemos a la\nsiguiente iteración del ciclo de eventos (permitiendo que el `EventHandler`\nprocese el evento).\n\n:::\n\n#### Uso de Stream\n\nAl igual que con `Cubit`, un `Bloc` es un tipo especial de `Stream`, lo que\nsignifica que también podemos suscribirnos a un `Bloc` para recibir\nactualizaciones en tiempo real de su estado:\n\n<CounterBlocStreamUsageSnippet />\n\nEn el fragmento anterior, nos estamos suscribiendo al `CounterBloc` y llamando a\nimprimir en cada cambio de estado. Luego agregamos el evento\n`CounterIncrementPressed` que desencadena el `EventHandler`\n`on<CounterIncrementPressed>` y emite un nuevo estado. Por último, llamamos a\n`cancel` en la suscripción cuando ya no queremos recibir actualizaciones y\ncerramos el `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` se agrega para este ejemplo para evitar\ncancelar la suscripción inmediatamente.\n\n:::\n\n### Observando un Bloc\n\nDado que `Bloc` extiende `BlocBase`, podemos observar todos los cambios de\nestado para un `Bloc` usando `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nLuego podemos actualizar `main.dart` a:\n\n<CounterBlocOnChangeUsageSnippet />\n\nAhora, si ejecutamos el fragmento anterior, la salida será:\n\n<CounterBlocOnChangeOutputSnippet />\n\nUn factor diferenciador clave entre `Bloc` y `Cubit` es que, dado que `Bloc`\nestá basado en eventos, también podemos capturar información sobre lo que\ndesencadenó el cambio de estado.\n\nPodemos hacer esto sobrescribiendo `onTransition`.\n\nEl cambio de un estado a otro se llama `Transition`. Una `Transition` consiste\nen el estado actual, el evento y el siguiente estado.\n\n<CounterBlocOnTransitionSnippet />\n\nSi luego volvemos a ejecutar el mismo fragmento `main.dart` de antes, deberíamos\nver la siguiente salida:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` se invoca antes que `onChange` y contiene el evento que\ndesencadenó el cambio de `currentState` a `nextState`.\n\n:::\n\n#### BlocObserver\n\nAl igual que antes, podemos sobrescribir `onTransition` en un `BlocObserver`\npersonalizado para observar todas las transiciones que ocurren desde un solo\nlugar.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nPodemos inicializar el `SimpleBlocObserver` de la misma manera que antes:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nAhora, si ejecutamos el fragmento anterior, la salida debería verse así:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` se invoca primero (local antes que global) seguido de `onChange`.\n\n:::\n\nOtra característica única de las instancias de `Bloc` es que nos permiten\nsobrescribir `onEvent`, que se llama cada vez que se agrega un nuevo evento al\n`Bloc`. Al igual que con `onChange` y `onTransition`, `onEvent` puede ser\nsobrescrito localmente así como globalmente.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nPodemos ejecutar el mismo `main.dart` de antes y deberíamos ver la siguiente\nsalida:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` se llama tan pronto como se agrega el evento. El `onEvent` local se\ninvoca antes que el `onEvent` global en `BlocObserver`.\n\n:::\n\n### Manejo de Errores en Bloc\n\nAl igual que con `Cubit`, cada `Bloc` tiene un método `addError` y `onError`.\nPodemos indicar que ha ocurrido un error llamando a `addError` desde cualquier\nlugar dentro de nuestro `Bloc`. Luego podemos reaccionar a todos los errores\nsobrescribiendo `onError` al igual que con `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nSi volvemos a ejecutar el mismo `main.dart` de antes, podemos ver cómo se ve\ncuando se informa un error:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nEl `onError` local se invoca primero seguido del `onError` global en\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` y `onChange` funcionan exactamente de la misma manera tanto para\ninstancias de `Bloc` como de `Cubit`.\n\n:::\n\n:::caution\n\nCualquier excepción no manejada que ocurra dentro de un `EventHandler` también\nse informa a `onError`.\n\n:::\n\n## Cubit vs. Bloc\n\nAhora que hemos cubierto los conceptos básicos de las clases `Cubit` y `Bloc`,\npodrías preguntarte cuándo deberías usar `Cubit` y cuándo deberías usar `Bloc`.\n\n### Ventajas de Cubit\n\n#### Simplicidad\n\nUna de las mayores ventajas de usar `Cubit` es la simplicidad. Al crear un\n`Cubit`, solo tenemos que definir el estado así como las funciones que queremos\nexponer para cambiar el estado. En comparación, al crear un `Bloc`, tenemos que\ndefinir los estados, eventos y la implementación del `EventHandler`. Esto hace\nque `Cubit` sea más fácil de entender y hay menos código involucrado.\n\nAhora echemos un vistazo a las dos implementaciones del contador:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nLa implementación de `Cubit` es más concisa y en lugar de definir eventos por\nseparado, las funciones actúan como eventos. Además, al usar un `Cubit`, podemos\nsimplemente llamar a `emit` desde cualquier lugar para desencadenar un cambio de\nestado.\n\n### Ventajas de Bloc\n\n#### Rastreabilidad\n\nUna de las mayores ventajas de usar `Bloc` es conocer la secuencia de cambios de\nestado así como exactamente qué desencadenó esos cambios. Para el estado que es\ncrítico para la funcionalidad de una aplicación, podría ser muy beneficioso usar\nun enfoque más basado en eventos para capturar todos los eventos además de los\ncambios de estado.\n\nUn caso de uso común podría ser gestionar el `AuthenticationState`. Para\nsimplificar, digamos que podemos representar el `AuthenticationState` a través\nde un `enum`:\n\n<AuthenticationStateSnippet />\n\nPodría haber muchas razones por las cuales el estado de la aplicación podría\ncambiar de `authenticated` a `unauthenticated`. Por ejemplo, el usuario podría\nhaber tocado un botón de cierre de sesión y solicitado ser desconectado de la\naplicación. Por otro lado, tal vez el token de acceso del usuario fue revocado y\nfue desconectado forzosamente. Al usar `Bloc` podemos rastrear claramente cómo\nel estado de la aplicación llegó a un cierto estado.\n\n<AuthenticationTransitionSnippet />\n\nLa `Transition` anterior nos da toda la información que necesitamos para\nentender por qué cambió el estado. Si hubiéramos usado un `Cubit` para gestionar\nel `AuthenticationState`, nuestros registros se verían así:\n\n<AuthenticationChangeSnippet />\n\nEsto nos dice que el usuario fue desconectado pero no explica por qué, lo cual\npodría ser crítico para depurar y entender cómo está cambiando el estado de la\naplicación con el tiempo.\n\n#### Transformaciones Avanzadas de Eventos\n\nOtra área en la que `Bloc` sobresale sobre `Cubit` es cuando necesitamos\naprovechar operadores reactivos como `buffer`, `debounceTime`, `throttle`, etc.\n\n:::tip\n\nConsulta [`package:stream_transform`](https://pub.dev/packages/stream_transform)\ny [`package:rxdart`](https://pub.dev/packages/rxdart) para transformadores de\nstreams.\n\n:::\n\n`Bloc` tiene un sink de eventos que nos permite controlar y transformar el flujo\nentrante de eventos.\n\nPor ejemplo, si estuviéramos construyendo una búsqueda en tiempo real,\nprobablemente querríamos aplicar debounce a las solicitudes al backend para\nevitar ser limitados en la tasa de solicitudes, así como para reducir el\ncosto/carga en el backend.\n\nCon `Bloc` podemos proporcionar un `EventTransformer` personalizado para cambiar\nla forma en que los eventos entrantes son procesados por el `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nCon el código anterior, podemos aplicar fácilmente debounce a los eventos\nentrantes con muy poco código adicional.\n\n:::tip\n\nConsulta [`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)\npara un conjunto de transformadores de eventos con una opinión definida.\n\n:::\n\nSi no estás seguro de cuál usar, comienza con `Cubit` y luego puedes\nrefactorizar o escalar a un `Bloc` según sea necesario.\n"
  },
  {
    "path": "docs/src/content/docs/es/faqs.mdx",
    "content": "---\ntitle: Preguntas Frecuentes\ndescription: Respuestas a preguntas frecuentes sobre la biblioteca bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## Estado No Actualizado\n\n❔ **Pregunta**: Estoy emitiendo un estado en mi bloc pero la interfaz de\nusuario no se actualiza. ¿Qué estoy haciendo mal?\n\n💡 **Respuesta**: Si estás usando Equatable, asegúrate de pasar todas las\npropiedades al getter props.\n\n✅ **BUENO**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **MALO**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nAdemás, asegúrate de emitir una nueva instancia del estado en tu bloc.\n\n✅ **BUENO**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **MALO**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nLas propiedades de `Equatable` siempre deben ser copiadas en lugar de\nmodificadas. Si una clase `Equatable` contiene una `Lista` o `Mapa` como\npropiedades, asegúrate de usar `List.of` o `Map.of` respectivamente para\ngarantizar que la igualdad se evalúe en función de los valores de las\npropiedades en lugar de la referencia.\n\n:::\n\n## Cuándo usar Equatable\n\n❔**Pregunta**: ¿Cuándo debo usar Equatable?\n\n💡**Respuesta**:\n\n<EquatableEmitSnippet />\n\nEn el escenario anterior, si `StateA` extiende `Equatable`, solo ocurrirá un\ncambio de estado (el segundo emit será ignorado). En general, debes usar\n`Equatable` si deseas optimizar tu código para reducir el número de\nreconstrucciones. No debes usar `Equatable` si deseas que el mismo estado\nconsecutivo desencadene múltiples transiciones.\n\nAdemás, usar `Equatable` facilita mucho las pruebas de blocs, ya que podemos\nesperar instancias específicas de estados de bloc en lugar de usar `Matchers` o\n`Predicates`.\n\n<EquatableBlocTestSnippet />\n\nSin `Equatable`, la prueba anterior fallaría y necesitaría ser reescrita así:\n\n<NoEquatableBlocTestSnippet />\n\n## Manejo de Errores\n\n❔ **Pregunta**: ¿Cómo puedo manejar un error mientras sigo mostrando datos\nanteriores?\n\n💡 **Respuesta**:\n\nEsto depende en gran medida de cómo se haya modelado el estado del bloc. En\ncasos donde los datos deben mantenerse incluso en presencia de un error,\nconsidera usar una sola clase de estado.\n\n<SingleStateSnippet />\n\nEsto permitirá que los widgets tengan acceso a las propiedades `data` y `error`\nsimultáneamente y el bloc puede usar `state.copyWith` para mantener los datos\nantiguos incluso cuando ocurra un error.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Pregunta**: ¿Cuál es la diferencia entre Bloc y Redux?\n\n💡 **Respuesta**:\n\nBLoC es un patrón de diseño que se define por las siguientes reglas:\n\n1. La entrada y salida del BLoC son Streams y Sinks simples.\n2. Las dependencias deben ser inyectables y agnósticas de la plataforma.\n3. No se permite la bifurcación de la plataforma.\n4. La implementación puede ser lo que quieras siempre que sigas las reglas\n   anteriores.\n\nLas pautas de la interfaz de usuario son:\n\n1. Cada componente \"lo suficientemente complejo\" tiene un BLoC correspondiente.\n2. Los componentes deben enviar entradas \"tal como están\".\n3. Los componentes deben mostrar salidas lo más cerca posible de \"tal como\n   están\".\n4. Toda la bifurcación debe basarse en salidas booleanas simples del BLoC.\n\nLa biblioteca Bloc implementa el patrón de diseño BLoC y tiene como objetivo\nabstraer RxDart para simplificar la experiencia del desarrollador.\n\nLos tres principios de Redux son:\n\n1. Fuente única de verdad\n2. El estado es de solo lectura\n3. Los cambios se realizan con funciones puras\n\nLa biblioteca bloc viola el primer principio; con bloc, el estado se distribuye\na través de múltiples blocs. Además, no hay concepto de middleware en bloc y\nbloc está diseñado para facilitar los cambios de estado asincrónicos,\npermitiéndote emitir múltiples estados para un solo evento.\n\n## Bloc vs. Provider\n\n❔ **Pregunta**: ¿Cuál es la diferencia entre Bloc y Provider?\n\n💡 **Respuesta**: `provider` está diseñado para la inyección de dependencias\n(envuelve `InheritedWidget`). Aún necesitas averiguar cómo gestionar tu estado\n(a través de `ChangeNotifier`, `Bloc`, `Mobx`, etc...). La biblioteca Bloc usa\n`provider` internamente para facilitar la provisión y acceso a blocs a lo largo\ndel árbol de widgets.\n\n## BlocProvider.of() No Encuentra el Bloc\n\n❔ **Pregunta**: Cuando uso `BlocProvider.of(context)` no puede encontrar el\nbloc. ¿Cómo puedo solucionar esto?\n\n💡 **Respuesta**: No puedes acceder a un bloc desde el mismo contexto en el que\nfue proporcionado, por lo que debes asegurarte de que `BlocProvider.of()` se\nllame dentro de un `BuildContext` hijo.\n\n✅ **BUENO**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **MALO**\n\n<BlocProviderBad1Snippet />\n\n## Estructura del Proyecto\n\n❔ **Pregunta**: ¿Cómo debo estructurar mi proyecto?\n\n💡 **Respuesta**: Aunque realmente no hay una respuesta correcta/incorrecta a\nesta pregunta, algunas referencias recomendadas son:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nLo más importante es tener una estructura de proyecto **consistente** e\n**intencional**.\n\n## Agregar Eventos dentro de un Bloc\n\n❔ **Pregunta**: ¿Está bien agregar eventos dentro de un bloc?\n\n💡 **Respuesta**: En la mayoría de los casos, los eventos deben agregarse\nexternamente, pero en algunos casos selectos puede tener sentido que los eventos\nse agreguen internamente.\n\nLa situación más común en la que se utilizan eventos internos es cuando los\ncambios de estado deben ocurrir en respuesta a actualizaciones en tiempo real\ndesde un repositorio. En estas situaciones, el repositorio es el estímulo para\nel cambio de estado en lugar de un evento externo como un toque de botón.\n\nEn el siguiente ejemplo, el estado de `MyBloc` depende del usuario actual que se\nexpone a través del `Stream<User>` del `UserRepository`. `MyBloc` escucha los\ncambios en el usuario actual y agrega un evento interno `_UserChanged` cada vez\nque se emite un usuario desde el flujo de usuarios.\n\n<BlocInternalAddEventSnippet />\n\nAl agregar un evento interno, también podemos especificar un `transformer`\npersonalizado para el evento para determinar cómo se procesarán múltiples\neventos `_UserChanged` -- por defecto se procesarán concurrentemente.\n\nSe recomienda encarecidamente que los eventos internos sean privados. Esta es\nuna forma explícita de señalar que un evento específico se usa solo dentro del\nbloc y evita que los componentes externos conozcan el evento.\n\n<BlocInternalEventSnippet />\n\nAlternativamente, podemos definir un evento externo `Started` y usar la API\n`emit.forEach` para manejar la reacción a las actualizaciones de usuarios en\ntiempo real:\n\n<BlocExternalForEachSnippet />\n\nLos beneficios del enfoque anterior son:\n\n- No necesitamos un evento interno `_UserChanged`\n- No necesitamos gestionar manualmente la `StreamSubscription`\n- Tenemos control total sobre cuándo el bloc se suscribe al flujo de\n  actualizaciones de usuarios\n\nLas desventajas del enfoque anterior son:\n\n- No podemos `pausar` o `reanudar` fácilmente la suscripción\n- Necesitamos exponer un evento público `Started` que debe agregarse\n  externamente\n- No podemos usar un `transformer` personalizado para ajustar cómo reaccionamos\n  a las actualizaciones de usuarios\n\n## Exponer Métodos Públicos\n\n❔ **Pregunta**: ¿Está bien exponer métodos públicos en mis instancias de bloc y\ncubit?\n\n💡 **Respuesta**\n\nAl crear un cubit, se recomienda exponer solo métodos públicos con el propósito\nde desencadenar cambios de estado. Como resultado, generalmente todos los\nmétodos públicos en una instancia de cubit deben devolver `void` o\n`Future<void>`.\n\nAl crear un bloc, se recomienda evitar exponer cualquier método público\npersonalizado y en su lugar notificar al bloc de eventos llamando a `add`.\n"
  },
  {
    "path": "docs/src/content/docs/es/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Conceptos de Flutter Bloc\ndescription:\n  Una visión general de los conceptos básicos para package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nPor favor, asegúrate de leer cuidadosamente las siguientes secciones antes de\ntrabajar con [`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nTodos los widgets exportados por el paquete `flutter_bloc` se integran tanto con\ninstancias de `Cubit` como de `Bloc`.\n\n:::\n\n## Widgets de Bloc\n\n### BlocBuilder\n\n**BlocBuilder** es un widget de Flutter que requiere un `Bloc` y una función\n`builder`. `BlocBuilder` maneja la construcción del widget en respuesta a nuevos\nestados. `BlocBuilder` es muy similar a `StreamBuilder` pero tiene una API más\nsimple para reducir la cantidad de código boilerplate necesario. La función\n`builder` potencialmente será llamada muchas veces y debe ser una\n[función pura](https://es.wikipedia.org/wiki/Funci%C3%B3n_pura) que devuelve un\nwidget en respuesta al estado.\n\nConsulta `BlocListener` si deseas \"hacer\" algo en respuesta a cambios de estado,\ncomo navegación, mostrar un diálogo, etc.\n\nSi se omite el parámetro `bloc`, `BlocBuilder` realizará automáticamente una\nbúsqueda usando `BlocProvider` y el `BuildContext` actual.\n\n<BlocBuilderSnippet />\n\nSolo especifica el bloc si deseas proporcionar un bloc que estará limitado a un\nsolo widget y no es accesible a través de un `BlocProvider` padre y el\n`BuildContext` actual.\n\n<BlocBuilderExplicitBlocSnippet />\n\nPara un control más detallado sobre cuándo se llama a la función `builder`, se\npuede proporcionar un `buildWhen` opcional. `buildWhen` toma el estado anterior\ndel bloc y el estado actual del bloc y devuelve un booleano. Si `buildWhen`\ndevuelve verdadero, se llamará a `builder` con `state` y el widget se\nreconstruirá. Si `buildWhen` devuelve falso, no se llamará a `builder` con\n`state` y no ocurrirá ninguna reconstrucción.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** es un widget de Flutter que es análogo a `BlocBuilder` pero\npermite a los desarrolladores filtrar actualizaciones seleccionando un nuevo\nvalor basado en el estado actual del bloc. Se previenen construcciones\ninnecesarias si el valor seleccionado no cambia. El valor seleccionado debe ser\ninmutable para que `BlocSelector` determine con precisión si se debe llamar\nnuevamente a `builder`.\n\nSi se omite el parámetro `bloc`, `BlocSelector` realizará automáticamente una\nbúsqueda usando `BlocProvider` y el `BuildContext` actual.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** es un widget de Flutter que proporciona un bloc a sus hijos a\ntravés de `BlocProvider.of<T>(context)`. Se utiliza como un widget de inyección\nde dependencias (DI) para que una sola instancia de un bloc pueda ser\nproporcionada a múltiples widgets dentro de un subárbol.\n\nEn la mayoría de los casos, `BlocProvider` debe usarse para crear nuevos blocs\nque estarán disponibles para el resto del subárbol. En este caso, dado que\n`BlocProvider` es responsable de crear el bloc, manejará automáticamente el\ncierre del bloc.\n\n<BlocProviderSnippet />\n\nPor defecto, `BlocProvider` creará el bloc de manera perezosa, lo que significa\nque `create` se ejecutará cuando se busque el bloc a través de\n`BlocProvider.of<BlocA>(context)`.\n\nPara anular este comportamiento y forzar que `create` se ejecute inmediatamente,\n`lazy` se puede establecer en `false`.\n\n<BlocProviderEagerSnippet />\n\nEn algunos casos, `BlocProvider` se puede usar para proporcionar un bloc\nexistente a una nueva porción del árbol de widgets. Esto se usará más comúnmente\ncuando un bloc existente necesite estar disponible para una nueva ruta. En este\ncaso, `BlocProvider` no cerrará automáticamente el bloc ya que no lo creó.\n\n<BlocProviderValueSnippet />\n\nentonces desde `ChildA` o `ScreenA` podemos recuperar `BlocA` con:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** es un widget de Flutter que fusiona múltiples widgets\n`BlocProvider` en uno solo. `MultiBlocProvider` mejora la legibilidad y elimina\nla necesidad de anidar múltiples `BlocProviders`. Usando `MultiBlocProvider`\npodemos pasar de:\n\n<NestedBlocProviderSnippet />\n\na:\n\n<MultiBlocProviderSnippet />\n\n### BlocListener\n\n**BlocListener** es un widget de Flutter que toma un `BlocWidgetListener` y un\n`Bloc` opcional e invoca el `listener` en respuesta a cambios de estado en el\nbloc. Debe usarse para funcionalidades que necesitan ocurrir una vez por cambio\nde estado, como navegación, mostrar un `SnackBar`, mostrar un `Dialog`, etc.\n\n`listener` solo se llama una vez por cada cambio de estado (**NO** incluyendo el\nestado inicial) a diferencia de `builder` en `BlocBuilder` y es una función\n`void`.\n\nSi se omite el parámetro `bloc`, `BlocListener` realizará automáticamente una\nbúsqueda usando `BlocProvider` y el `BuildContext` actual.\n\n<BlocListenerSnippet />\n\nSolo especifica el bloc si deseas proporcionar un bloc que no es accesible a\ntravés de `BlocProvider` y el `BuildContext` actual.\n\n<BlocListenerExplicitBlocSnippet />\n\nPara un control más detallado sobre cuándo se llama a la función `listener`, se\npuede proporcionar un `listenWhen` opcional. `listenWhen` toma el estado\nanterior del bloc y el estado actual del bloc y devuelve un booleano. Si\n`listenWhen` devuelve verdadero, se llamará a `listener` con `state`. Si\n`listenWhen` devuelve falso, no se llamará a `listener` con `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** es un widget de Flutter que fusiona múltiples widgets\n`BlocListener` en uno solo. `MultiBlocListener` mejora la legibilidad y elimina\nla necesidad de anidar múltiples `BlocListeners`. Usando `MultiBlocListener`\npodemos pasar de:\n\n<NestedBlocListenerSnippet />\n\na:\n\n<MultiBlocListenerSnippet />\n\n### BlocConsumer\n\n**BlocConsumer** expone un `builder` y un `listener` para reaccionar a nuevos\nestados. `BlocConsumer` es análogo a un `BlocListener` y `BlocBuilder` anidados,\npero reduce la cantidad de código boilerplate necesario. `BlocConsumer` solo\ndebe usarse cuando es necesario tanto reconstruir la UI como ejecutar otras\nreacciones a cambios de estado en el `bloc`. `BlocConsumer` toma un\n`BlocWidgetBuilder` y un `BlocWidgetListener` requeridos y un `bloc`,\n`BlocBuilderCondition` y `BlocListenerCondition` opcionales.\n\nSi se omite el parámetro `bloc`, `BlocConsumer` realizará automáticamente una\nbúsqueda usando `BlocProvider` y el `BuildContext` actual.\n\n<BlocConsumerSnippet />\n\nSe pueden implementar opcionalmente `listenWhen` y `buildWhen` para un control\nmás granular sobre cuándo se llaman `listener` y `builder`. `listenWhen` y\n`buildWhen` se invocarán en cada cambio de `estado` del `bloc`. Cada uno toma el\n`estado` anterior y el `estado` actual y debe devolver un `bool` que determina\nsi se invocará la función `builder` y/o `listener`. El `estado` anterior se\ninicializará al `estado` del `bloc` cuando se inicialice el `BlocConsumer`.\n`listenWhen` y `buildWhen` son opcionales y si no se implementan, su valor\npredeterminado será `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** es un widget de Flutter que proporciona un repositorio a\nsus hijos a través de `RepositoryProvider.of<T>(context)`. Se utiliza como un\nwidget de inyección de dependencias (DI) para que una sola instancia de un\nrepositorio pueda ser proporcionada a múltiples widgets dentro de un subárbol.\n`BlocProvider` debe usarse para proporcionar blocs, mientras que\n`RepositoryProvider` solo debe usarse para repositorios.\n\n<RepositoryProviderSnippet />\n\nentonces desde `ChildA` podemos recuperar la instancia del `Repository` con:\n\n<RepositoryProviderLookupSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** es un widget de Flutter que fusiona múltiples\nwidgets `RepositoryProvider` en uno solo. `MultiRepositoryProvider` mejora la\nlegibilidad y elimina la necesidad de anidar múltiples `RepositoryProvider`.\nUsando `MultiRepositoryProvider` podemos pasar de:\n\n<NestedRepositoryProviderSnippet />\n\na:\n\n<MultiRepositoryProviderSnippet />\n\n## Uso de BlocProvider\n\nVeamos cómo usar `BlocProvider` para proporcionar un `CounterBloc` a una\n`CounterPage` y reaccionar a los cambios de estado con `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nEn este punto, hemos separado con éxito nuestra capa de presentación de nuestra\ncapa de lógica de negocio. Observa que el widget `CounterPage` no sabe nada\nsobre lo que sucede cuando un usuario toca los botones. El widget simplemente le\ndice al `CounterBloc` que el usuario ha presionado el botón de incremento o\ndecremento.\n\n## Uso de RepositoryProvider\n\nVamos a ver cómo usar `RepositoryProvider` en el contexto del ejemplo\n[`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nDado que la aplicación tiene una dependencia explícita del `WeatherRepository`,\ninyectamos una instancia a través del constructor. Esto nos permite inyectar\ndiferentes instancias de `WeatherRepository` según el sabor de compilación o el\nentorno.\n\n<WeatherMainSnippet />\n\nDado que solo tenemos un repositorio en nuestra aplicación, lo inyectaremos en\nnuestro árbol de widgets a través de `RepositoryProvider.value`. Si tienes más\nde un repositorio, puedes usar `MultiRepositoryProvider` para proporcionar\nmúltiples instancias de repositorio al subárbol.\n\n<WeatherAppSnippet />\n\nEn la mayoría de los casos, el widget raíz de la aplicación expondrá uno o más\nrepositorios al subárbol a través de `RepositoryProvider`.\n\n<WeatherPageSnippet />\n\nAhora, al instanciar un bloc, podemos acceder a la instancia de un repositorio a\ntravés de `context.read` e inyectar el repositorio en el bloc a través del\nconstructor.\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Métodos de Extensión\n\n[Los métodos de extensión](https://dart.dev/guides/language/extension-methods),\nintroducidos en Dart 2.7, son una forma de agregar funcionalidad a las\nbibliotecas existentes. En esta sección, veremos los métodos de extensión\nincluidos en `package:flutter_bloc` y cómo se pueden usar.\n\n`flutter_bloc` tiene una dependencia de\n[package:provider](https://pub.dev/packages/provider) que simplifica el uso de\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nInternamente, `package:flutter_bloc` usa `package:provider` para implementar:\nlos widgets `BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` y\n`MultiRepositoryProvider`. `package:flutter_bloc` exporta las extensiones\n`ReadContext`, `WatchContext` y `SelectContext` de `package:provider`.\n\n:::note\n\nAprende más sobre [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` busca la instancia de ancestro más cercana del tipo `T` y es\nfuncionalmente equivalente a `BlocProvider.of<T>(context)`. `context.read` se\nusa más comúnmente para recuperar una instancia de bloc con el fin de agregar un\nevento dentro de las devoluciones de llamada `onPressed`.\n\n:::note\n\n`context.read<T>()` no escucha a `T` -- si el `Object` proporcionado del tipo\n`T` cambia, `context.read` no activará una reconstrucción del widget.\n\n:::\n\n#### Uso\n\n✅ **USA** `context.read` para agregar eventos en callbacks.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **EVITA** usar `context.read` para recuperar el estado dentro de un método\n`build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nEl uso anterior es propenso a errores porque el widget `Text` no se reconstruirá\nsi el estado del bloc cambia.\n\n:::caution\n\nUsa `BlocBuilder` o `context.watch` en su lugar para reconstruir en respuesta a\ncambios de estado.\n\n:::\n\n### context.watch\n\nAl igual que `context.read<T>()`, `context.watch<T>()` proporciona la instancia\nde ancestro más cercana del tipo `T`, sin embargo, también escucha los cambios\nen la instancia. Es funcionalmente equivalente a\n`BlocProvider.of<T>(context, listen: true)`.\n\nSi el `Object` proporcionado del tipo `T` cambia, `context.watch` activará una\nreconstrucción.\n\n:::caution\n\n`context.watch` solo es accesible dentro del método `build` de una clase\n`StatelessWidget` o `State`.\n\n:::\n\n#### Uso\n\n✅ **USA** `BlocBuilder` en lugar de `context.watch` para delimitar\nexplícitamente las reconstrucciones.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Siempre que el estado cambie, solo se reconstruirá el Text.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nAlternativamente, usa un `Builder` para delimitar las reconstrucciones.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Siempre que el estado cambie, solo se reconstruirá el Text.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **USA** `Builder` y `context.watch` como `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // devuelve un Widget que depende del estado de BlocA, BlocB y BlocC\n  }\n);\n```\n\n❌ **EVITA** usar `context.watch` cuando el widget padre en el método `build` no\ndepende del estado.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Siempre que el estado cambie, se reconstruirá el MaterialApp\n  // aunque solo se use en el widget Text.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsar `context.watch` en la raíz del método `build` resultará en que todo el\nwidget se reconstruya cuando el estado del bloc cambie.\n\n:::\n\n### context.select\n\nAl igual que `context.watch<T>()`, `context.select<T, R>(R function(T value))`\nproporciona la instancia de ancestro más cercana del tipo `T` y escucha los\ncambios en `T`. A diferencia de `context.watch`, `context.select` te permite\nescuchar cambios en una parte más pequeña de un estado.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nLo anterior solo reconstruirá el widget cuando la propiedad `name` del estado de\n`ProfileBloc` cambie.\n\n#### Uso\n\n✅ **USA** `BlocSelector` en lugar de `context.select` para delimitar\nexplícitamente las reconstrucciones.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Siempre que state.name cambie, solo se reconstruirá el Text.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nAlternativamente, usa un `Builder` para delimitar las reconstrucciones.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Siempre que state.name cambie, solo se reconstruirá el Text.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **EVITA** usar `context.select` cuando el widget padre en el método `build`\nno depende del estado.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Siempre que state.value cambie, se reconstruirá el MaterialApp\n  // aunque solo se use en el widget Text.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsar `context.select` en la raíz del método `build` resultará en que todo el\nwidget se reconstruya cuando la selección cambie.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/es/getting-started.mdx",
    "content": "---\ntitle: Empezando\ndescription: Todo lo que necesitas para empezar a construir con Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Paquetes\n\nEl ecosistema de bloc consiste en múltiples paquetes listados a continuación:\n\n| Paquete                                                                                    | Descripción                   | Enlace                                                                                                         |\n| ------------------------------------------------------------------------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | Componentes AngularDart       | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | APIs principales de Dart      | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Transformadores de eventos    | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter                 | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | APIs de prueba                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools            | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Widgets de Flutter            | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Soporte de caché/persistencia | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Soporte de deshacer/rehacer   | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Instalación\n\n<InstallationTabs />\n\n:::note\n\nPara empezar a usar bloc debes tener el [Dart SDK](https://dart.dev/get-dart)\ninstalado en tu máquina.\n\n:::\n\n## Importaciones\n\nAhora que hemos instalado bloc con éxito, podemos crear nuestro `main.dart` e\nimportar el paquete `bloc` correspondiente.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/es/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ Visite\n    <a href=\"https://shop.bloclibrary.dev\">la tienda de Bloc</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Una biblioteca de administración de estado predecible para Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Empezar\n      link: /es/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Vista sobre GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"Patrocinado con 💖 por\"\n\tbecomeASponsor=\"Convertirse en patrocinador\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Empezar\" icon=\"rocket\">\n\t```sh\n\t# Agregue bloc a su proyecto.\n\tdart pub add bloc\n\t```\n\nNuestra [guía de inicio](/es/getting-started) tiene instrucciones paso a paso\nsobre cómo comenzar a usar Bloc en solo unos minutos.\n\n</SplitCard>\n\n<Card title=\"Hacer una visita guiada\" icon=\"star\">\n\tComplete [los tutoriales oficiales](/es/tutorials/flutter-counter) para\n\taprender mejor Prácticas y construir una variedad de aplicaciones diferentes\n\talimentadas por Bloc.\n</Card>\n\n<Card title=\"Construir con Bloc\" icon=\"laptop\">\n\tExplore la alta calidad y totalmente probada [muestra\n\taplicaciones](https://github.com/felangel/bloc/tree/master/examples) como el\n\tmostrador, ¡Temporizador, lista infinita, clima, tarea y más!\n</Card>\n\n<ListCard title=\"Aprender\" icon=\"open-book\">\n\n    - [¿Por qué Bloc?](/es/why-bloc)\n    - [Conceptos Básicos](/es/bloc-concepts)\n    - [Arquitectura](/es/architecture)\n    - [Testeo](/es/testing)\n    - [Convención de Nombres](/es/naming-conventions)\n    - [FAQs](/es/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integraciones\" icon=\"puzzle\">\n    - [VSCode Integración](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ Integración](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim Integración](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI Integración](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Plantillas Personalizadas](https://brickhub.dev/search?q=bloc)\n    - [Herramientas de Desarrollo](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Únete a nosotros en Discord\" />\n"
  },
  {
    "path": "docs/src/content/docs/es/migration.mdx",
    "content": "---\ntitle: Guía de Migración\ndescription: Migra a la última versión estable de Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nPor favor, consulta el\n[registro de versiones](https://github.com/felangel/bloc/releases) para obtener\nmás información sobre los cambios en cada versión.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ Desacoplar `blocTest` de `BlocBase`\n\n:::note[¿Qué cambió?]\n\nEn bloc_test v10.0.0, la API `blocTest` ya no está estrechamente acoplada a\n`BlocBase`.\n\n:::\n\n##### Justificación\n\n`blocTest` debería usar las interfaces principales de bloc cuando sea posible\npara una mayor flexibilidad y reutilización. Anteriormente esto no era posible\nporque `BlocBase` implementaba `StateStreamableSource`, lo cual no era\nsuficiente para `blocTest` debido a la dependencia interna en la API `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Soporte para WebAssembly\n\n:::note[¿Qué cambió?]\n\nEn hydrated_bloc v10.0.0, se añadió soporte para compilar a WebAssembly (wasm).\n\n:::\n\n##### Justificación\n\nAnteriormente no era posible compilar aplicaciones a wasm cuando se usaba\n`hydrated_bloc`. En la versión v10.0.0, el paquete fue refactorizado para\npermitir la compilación a wasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Eliminar APIs Obsoletas\n\n:::note[¿Qué cambió?]\n\nEn bloc v9.0.0, todas las APIs previamente obsoletas fueron eliminadas.\n\n:::\n\n##### Resumen\n\n- `BlocOverrides` eliminado en favor de `Bloc.observer` y `Bloc.transformer`\n\n#### ❗✨ Introducir nueva Interfaz `EmittableStateStreamableSource`\n\n:::note[¿Qué cambió?]\n\nEn bloc v9.0.0, se introdujo una nueva interfaz central\n`EmittableStateStreamableSource`.\n\n:::\n\n##### Justificación\n\n`package:bloc_test` estaba previamente estrechamente acoplado a `BlocBase`. La\ninterfaz `EmittableStateStreamableSource` se introdujo para permitir que\n`blocTest` se desacople de la implementación concreta de `BlocBase`.\n\n### `package:hydrated_bloc`\n\n#### ✨ Reintroducir la API `HydratedBloc.storage`\n\n:::note[¿Qué cambió?]\n\nEn hydrated_bloc v9.0.0, `HydratedBlocOverrides` fue eliminado en favor de la\nAPI `HydratedBloc.storage`.\n\n:::\n\n##### Justificación\n\nConsulta la\n[justificación para reintroducir las anulaciones de Bloc.observer y Bloc.transformer](/es/migration#-reintroducir-las-apis-blocobserver-y-bloctransformer).\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ Reintroducir las APIs `Bloc.observer` y `Bloc.transformer`\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.1.0, `BlocOverrides` fue deprecado en favor de las APIs\n`Bloc.observer` y `Bloc.transformer`.\n\n:::\n\n##### Justificación\n\nLa API `BlocOverrides` se introdujo en v8.0.0 en un intento de soportar\nconfiguraciones específicas de bloc como `BlocObserver`, `EventTransformer` y\n`HydratedStorage`. En aplicaciones puras de Dart, los cambios funcionaron bien;\nsin embargo, en aplicaciones Flutter la nueva API causó más problemas de los que\nresolvió.\n\nLa API `BlocOverrides` se inspiró en APIs similares en Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**Problemas**\n\nAunque no fue la razón principal para estos cambios, la API `BlocOverrides`\nintrodujo complejidad adicional para los desarrolladores. Además de aumentar la\ncantidad de anidamiento y líneas de código necesarias para lograr el mismo\nefecto, la API `BlocOverrides` requería que los desarrolladores tuvieran un\nsólido entendimiento de\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) en Dart.\nLas `Zones` no son un concepto amigable para principiantes y el no entender cómo\nfuncionan podría llevar a la introducción de errores (como observadores,\ntransformadores o instancias de almacenamiento no inicializadas).\n\nPor ejemplo, muchos desarrolladores tendrían algo como:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nEl código anterior, aunque parece inofensivo, puede llevar a muchos errores\ndifíciles de rastrear. La zona desde la cual se llama inicialmente a\n`WidgetsFlutterBinding.ensureInitialized` será la zona en la que se manejan los\neventos de gestos (por ejemplo, callbacks `onTap`, `onPressed`) debido a\n`GestureBinding.initInstances`. Este es solo uno de los muchos problemas\ncausados por el uso de `zoneValues`.\n\nAdemás, Flutter hace muchas cosas detrás de escena que implican\nbifurcar/manipular Zonas (especialmente al ejecutar pruebas) lo que puede llevar\na comportamientos inesperados (y en muchos casos comportamientos que están fuera\ndel control del desarrollador -- ver problemas a continuación).\n\nDebido al uso de\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), la\ntransición a la API `BlocOverrides` llevó al descubrimiento de varios\nerrores/limitaciones en Flutter (específicamente alrededor de las Pruebas de\nWidgets e Integración):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nlo cual afectó a muchos desarrolladores que usaban la biblioteca bloc:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ Introducir nueva API `BlocOverrides`\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, `Bloc.observer` y `Bloc.transformer` fueron eliminados en favor\nde la API `BlocOverrides`.\n\n:::\n\n##### Justificación\n\nLa API anterior utilizada para sobrescribir el `BlocObserver` y\n`EventTransformer` predeterminados dependía de un singleton global tanto para el\n`BlocObserver` como para el `EventTransformer`.\n\nComo resultado, no era posible:\n\n- Tener múltiples implementaciones de `BlocObserver` o `EventTransformer`\n  limitadas a diferentes partes de la aplicación.\n- Tener sobrescrituras de `BlocObserver` o `EventTransformer` limitadas a un\n  paquete.\n  - Si un paquete dependía de `package:bloc` y registraba su propio\n    `BlocObserver`, cualquier consumidor del paquete tendría que sobrescribir el\n    `BlocObserver` del paquete o informar al `BlocObserver` del paquete.\n\nTambién era más difícil de probar debido al estado global compartido entre las\npruebas.\n\nBloc v8.0.0 introduce una clase `BlocOverrides` que permite a los\ndesarrolladores sobrescribir `BlocObserver` y/o `EventTransformer` para una\n`Zone` específica en lugar de depender de un singleton global mutable.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nLas instancias de `Bloc` usarán el `BlocObserver` y/o `EventTransformer` para la\n`Zone` actual a través de `BlocOverrides.current`. Si no hay `BlocOverrides`\npara la zona, usarán los valores predeterminados internos existentes (sin cambio\nen comportamiento/funcionalidad).\n\nEsto permite que cada `Zone` funcione de manera independiente con sus propios\n`BlocOverrides`.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA y eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Los Blocs en esta zona reportan a BlocObserverA\n    // y usan eventTransformerA como el transformador predeterminado.\n    // ...\n\n    // Más tarde...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB y eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Los Blocs en esta zona reportan a BlocObserverB\n        // y usan eventTransformerB como el transformador predeterminado.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Mejorar el Manejo y Reporte de Errores\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, `BlocUnhandledErrorException` se eliminó. Además, cualquier\nexcepción no capturada siempre se reporta a `onError` y se vuelve a lanzar\n(independientemente del modo de depuración o lanzamiento). La API `addError`\ninforma errores a `onError`, pero no trata los errores reportados como\nexcepciones no capturadas.\n\n:::\n\n##### Justificación\n\nEl objetivo de estos cambios es:\n\n- hacer que las excepciones internas no manejadas sean extremadamente obvias\n  mientras se preserva la funcionalidad del bloc\n- soportar `addError` sin interrumpir el flujo de control\n\nAnteriormente, el manejo y reporte de errores variaba dependiendo de si la\naplicación se ejecutaba en modo de depuración o lanzamiento. Además, los errores\nreportados a través de `addError` se trataban como excepciones no capturadas en\nmodo de depuración, lo que llevaba a una mala experiencia de desarrollador al\nusar la API `addError` (específicamente al escribir pruebas unitarias).\n\nEn v8.0.0, `addError` se puede usar de manera segura para reportar errores y\n`blocTest` se puede usar para verificar que los errores se reporten. Todos los\nerrores aún se reportan a `onError`, sin embargo, solo las excepciones no\ncapturadas se vuelven a lanzar (independientemente del modo de depuración o\nlanzamiento).\n\n#### ❗🧹 Hacer `BlocObserver` abstracto\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, `BlocObserver` se convirtió en una clase `abstract`, lo que\nsignifica que no se puede instanciar una instancia de `BlocObserver`.\n\n:::\n\n##### Justificación\n\n`BlocObserver` estaba destinado a ser una interfaz. Dado que la implementación\npredeterminada de la API son operaciones nulas, `BlocObserver` es ahora una\nclase `abstract` para comunicar claramente que la clase está destinada a ser\nextendida y no instanciada directamente.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // Era posible crear una instancia de la clase base.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // No se puede instanciar la clase base.\n  final observer = BlocObserver(); // ERROR\n\n  // Extiende `BlocObserver` en su lugar.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ `add` lanza `StateError` si el Bloc está cerrado\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, llamar a `add` en un bloc cerrado resultará en un `StateError`.\n\n:::\n\n##### Justificación\n\nAnteriormente, era posible llamar a `add` en un bloc cerrado y el error interno\nse tragaba, lo que dificultaba depurar por qué el evento añadido no se estaba\nprocesando. Para hacer este escenario más visible, en v8.0.0, llamar a `add` en\nun bloc cerrado lanzará un `StateError` que se informará como una excepción no\ncapturada y se propagará a `onError`.\n\n#### ❗✨ `emit` lanza `StateError` si el Bloc está cerrado\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, llamar a `emit` dentro de un bloc cerrado resultará en un\n`StateError`.\n\n:::\n\n##### Justificación\n\nAnteriormente, era posible llamar a `emit` dentro de un bloc cerrado y no\nocurría ningún cambio de estado, pero tampoco había una indicación de lo que\nsalió mal, lo que dificultaba la depuración. Para hacer este escenario más\nvisible, en v8.0.0, llamar a `emit` dentro de un bloc cerrado lanzará un\n`StateError` que se informará como una excepción no capturada y se propagará a\n`onError`.\n\n#### ❗🧹 Eliminar APIs Obsoletas\n\n:::note[¿Qué cambió?]\n\nEn bloc v8.0.0, todas las APIs previamente obsoletas fueron eliminadas.\n\n:::\n\n##### Resumen\n\n- `mapEventToState` eliminado en favor de `on<Event>`\n- `transformEvents` eliminado en favor de la API `EventTransformer`\n- `TransitionFunction` typedef eliminado en favor de la API `EventTransformer`\n- `listen` eliminado en favor de `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` y `MockCubit` ya no requieren `registerFallbackValue`\n\n:::note[¿Qué cambió?]\n\nEn bloc_test v9.0.0, los desarrolladores ya no necesitan llamar explícitamente a\n`registerFallbackValue` al usar `MockBloc` o `MockCubit`.\n\n:::\n\n##### Resumen\n\n`registerFallbackValue` solo es necesario cuando se usa el matcher `any()` de\n`package:mocktail` para un tipo personalizado. Anteriormente,\n`registerFallbackValue` era necesario para cada `Event` y `State` al usar\n`MockBloc` o `MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Tests...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Tests...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Introducir nueva API `HydratedBlocOverrides`\n\n:::note[¿Qué cambió?]\n\nEn hydrated_bloc v8.0.0, `HydratedBloc.storage` fue eliminado en favor de la API\n`HydratedBlocOverrides`.\n\n:::\n\n##### Justificación\n\nAnteriormente, se utilizaba un singleton global para sobrescribir la\nimplementación de `Storage`.\n\nComo resultado, no era posible tener múltiples implementaciones de `Storage`\nlimitadas a diferentes partes de la aplicación. También era más difícil de\nprobar debido al estado global compartido entre las pruebas.\n\n`HydratedBloc` v8.0.0 introduce una clase `HydratedBlocOverrides` que permite a\nlos desarrolladores sobrescribir `Storage` para una `Zone` específica en lugar\nde depender de un singleton global mutable.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\n`HydratedBloc` usará el `Storage` para la `Zone` actual a través de\n`HydratedBlocOverrides.current`.\n\nEsto permite que cada `Zone` funcione de manera independiente con sus propios\n`BlocOverrides`.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ Introducir nueva API `on<Event>`\n\n:::note[¿Qué cambió?]\n\nEn bloc v7.2.0, `mapEventToState` fue deprecado en favor de `on<Event>`.\n`mapEventToState` será eliminado en bloc v8.0.0.\n\n:::\n\n##### Justificación\n\nLa API `on<Event>` se introdujo como parte de\n[[Propuesta] Reemplazar mapEventToState con on\\<Event\\> en Bloc](https://github.com/felangel/bloc/issues/2526).\nDebido a [un problema en Dart](https://github.com/dart-lang/sdk/issues/44616) no\nsiempre es obvio cuál será el valor de `state` cuando se trata de generadores\nasincrónicos anidados (`async*`). Aunque hay formas de solucionar el problema,\nuno de los principios fundamentales de la biblioteca bloc es ser predecible. La\nAPI `on<Event>` se creó para hacer que la biblioteca sea lo más segura posible\nde usar y para eliminar cualquier incertidumbre en lo que respecta a los cambios\nde estado.\n\n:::tip\n\nPara más información,\n[lee la propuesta completa](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**Resumen**\n\n`on<E>` te permite registrar un manejador de eventos para todos los eventos del\ntipo `E`. Por defecto, los eventos se procesarán concurrentemente cuando se use\n`on<E>` en lugar de `mapEventToState`, que procesa los eventos\n`secuencialmente`.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nCada `EventHandler` registrado funciona de manera independiente, por lo que es\nimportante registrar manejadores de eventos según el tipo de transformador que\ndesees aplicar.\n\n:::\n\nSi deseas mantener el mismo comportamiento exacto que en la versión v7.1.0,\npuedes registrar un solo manejador de eventos para todos los eventos y aplicar\nun transformador `sequential`:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: logic goes here...\n  }\n}\n```\n\nTambién puedes sobrescribir el `EventTransformer` predeterminado para todos los\nblocs en tu aplicación:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ Introducir nueva API `EventTransformer`\n\n:::note[¿Qué cambió?]\n\nEn bloc v7.2.0, `transformEvents` fue deprecado en favor de la API\n`EventTransformer`. `transformEvents` será eliminado en bloc v8.0.0.\n\n:::\n\n##### Justificación\n\nLa API `on<Event>` abrió la puerta para poder proporcionar un transformador de\neventos personalizado por manejador de eventos. Se introdujo un nuevo typedef\n`EventTransformer` que permite a los desarrolladores transformar el flujo de\neventos entrantes para cada manejador de eventos en lugar de tener que\nespecificar un único transformador de eventos para todos los eventos.\n\n**Resumen**\n\nUn `EventTransformer` es responsable de tomar el flujo entrante de eventos junto\ncon un `EventMapper` (tu manejador de eventos) y devolver un nuevo flujo de\neventos.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nEl `EventTransformer` predeterminado procesa todos los eventos concurrentemente\ny se ve algo así:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nConsulta [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency)\npara un conjunto de transformadores de eventos personalizados y con opiniones.\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Define un `EventTransformer` personalizado\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Aplica el `EventTransformer` personalizado al `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ Marcar como obsoleta la API `transformTransitions`\n\n:::note[¿Qué cambió?]\n\nEn bloc v7.2.0, `transformTransitions` fue deprecado en favor de sobrescribir la\nAPI `stream`. `transformTransitions` será eliminado en bloc v8.0.0.\n\n:::\n\n##### Justificación\n\nEl getter `stream` en `Bloc` facilita la sobrescritura del flujo de estados\nsalientes, por lo tanto, ya no es valioso mantener una API\n`transformTransitions` separada.\n\n**Resumen**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc y Cubit extienden BlocBase\n\n##### Justificación\n\nComo desarrollador, la relación entre blocs y cubits era un poco incómoda.\nCuando se introdujo cubit por primera vez, comenzó como la clase base para\nblocs, lo cual tenía sentido porque tenía un subconjunto de la funcionalidad y\nlos blocs simplemente extenderían Cubit y definirían APIs adicionales. Esto\ntenía algunos inconvenientes:\n\n- Todas las APIs tendrían que ser renombradas para aceptar un cubit por\n  precisión o tendrían que mantenerse como bloc por consistencia, aunque\n  jerárquicamente no fuera preciso\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- Cubit tendría que extender Stream e implementar EventSink para tener una base\n  común sobre la cual se puedan implementar widgets como BlocBuilder,\n  BlocListener, etc. ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nMás tarde, experimentamos con invertir la relación y hacer que bloc fuera la\nclase base, lo que resolvió parcialmente el primer punto anterior pero introdujo\notros problemas:\n\n- La API de cubit está sobrecargada debido a las APIs subyacentes de bloc como\n  mapEventToState, add, etc.\n  ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - Los desarrolladores técnicamente pueden invocar estas APIs y romper cosas.\n- Todavía tenemos el mismo problema de cubit exponiendo toda la API de stream\n  como antes ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nPara abordar estos problemas, introdujimos una clase base tanto para `Bloc` como\npara `Cubit` llamada `BlocBase` para que los componentes upstream puedan seguir\ninteroperando con instancias de bloc y cubit sin exponer toda la API de `Stream`\ny `EventSink` directamente.\n\n**Resumen**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed devuelve una función para soportar valores dinámicos\n\n##### Justificación\n\nPara soportar tener un valor de semilla mutable que se pueda actualizar\ndinámicamente en `setUp`, `seed` devuelve una función.\n\n**Resumen**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect devuelve una función para soportar valores dinámicos y soporte de matchers\n\n##### Justificación\n\nPara soportar tener una expectativa mutable que se pueda actualizar\ndinámicamente en `setUp`, `expect` devuelve una función. `expect` también\nsoporta `Matchers`.\n\n**Resumen**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors devuelve una función para soportar valores dinámicos y soporte de matchers\n\n##### Justificación\n\nPara soportar tener un valor de errores mutable que se pueda actualizar\ndinámicamente en `setUp`, `errors` devuelve una función. `errors` también\nsoporta `Matchers`.\n\n**Resumen**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc y MockCubit\n\n##### Justificación\n\nPara soportar la simulación de varias APIs centrales, `MockBloc` y `MockCubit`\nse exportan como parte del paquete `bloc_test`. Anteriormente, `MockBloc` tenía\nque ser utilizado tanto para instancias de `Bloc` como de `Cubit`, lo cual no\nera intuitivo.\n\n**Resumen**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Integración con Mocktail\n\n##### Justificación\n\nDebido a varias limitaciones de la versión null-safe del paquete\n[package:mockito](https://pub.dev/packages/mockito) descritas\n[aquí](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\nel paquete [package:mocktail](https://pub.dev/packages/mocktail) es utilizado\npor `MockBloc` y `MockCubit`. Esto permite a los desarrolladores continuar\nusando una API de simulación familiar sin la necesidad de escribir stubs\nmanualmente o depender de la generación de código.\n\n**Resumen**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Please refer to [#347](https://github.com/dart-lang/mockito/issues/347) as\n> well as the\n> [mocktail documentation](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> for more information.\n\n### `package:flutter_bloc`\n\n#### ❗ renombrar el parámetro `cubit` a `bloc`\n\n##### Justificación\n\nComo resultado de la refactorización en `package:bloc` para introducir\n`BlocBase`, que extiende `Bloc` y `Cubit`, los parámetros de `BlocBuilder`,\n`BlocConsumer` y `BlocListener` se renombraron de `cubit` a `bloc` porque los\nwidgets operan sobre el tipo `BlocBase`. Esto también se alinea aún más con el\nnombre de la biblioteca y, con suerte, mejora la legibilidad.\n\n**Resumen**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory es requerido al llamar a HydratedStorage.build\n\n##### Justificación\n\nPara hacer que `package:hydrated_bloc` sea un paquete puro de Dart, se eliminó\nla dependencia de\n[package:path_provider](https://pub.dev/packages/path_provider) y el parámetro\n`storageDirectory` al llamar a `HydratedStorage.build` es requerido y ya no\ntiene como valor predeterminado `getTemporaryDirectory`.\n\n**Resumen**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc y context.repository están obsoletos en favor de context.read y context.watch\n\n##### Justificación\n\n`context.read`, `context.watch` y `context.select` se añadieron para alinearse\ncon la API existente de [provider](https://pub.dev/packages/provider) con la que\nmuchos desarrolladores están familiarizados y para abordar problemas planteados\npor la comunidad. Para mejorar la seguridad del código y mantener la\nconsistencia, `context.bloc` se deprecó porque puede ser reemplazado por\n`context.read` o `context.watch` dependiendo de si se usa directamente dentro de\n`build`.\n\n**context.watch**\n\n`context.watch` aborda la solicitud de tener un\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) porque podemos\nobservar varios blocs dentro de un solo `Builder` para renderizar la UI basada\nen múltiples estados:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` permite a los desarrolladores renderizar/actualizar la UI\nbasada en una parte del estado de un bloc y aborda la solicitud de tener un\n[buildWhen más simple](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nEl fragmento anterior nos permite acceder y reconstruir el widget solo cuando\ncambia el nombre del usuario actual.\n\n**context.read**\n\nAunque parece que `context.read` es idéntico a `context.bloc`, hay algunas\ndiferencias sutiles pero significativas. Ambos permiten acceder a un bloc con un\n`BuildContext` y no resultan en reconstrucciones; sin embargo, `context.read` no\nse puede llamar directamente dentro de un método `build`. Hay dos razones\nprincipales para usar `context.bloc` dentro de `build`:\n\n1. **Para acceder al estado del bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nEl uso anterior es propenso a errores porque el widget `Text` no se reconstruirá\nsi el estado del bloc cambia. En este escenario, se debe usar un `BlocBuilder` o\n`context.watch`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nor\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nUsar `context.watch` en la raíz del método `build` resultará en que todo el\nwidget se reconstruya cuando cambie el estado del bloc. Si no es necesario\nreconstruir todo el widget, usa `BlocBuilder` para envolver las partes que deben\nreconstruirse, usa un `Builder` con `context.watch` para delimitar las\nreconstrucciones, o descompón el widget en widgets más pequeños.\n\n:::\n\n2. **Para acceder al bloc y poder agregar un evento**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nEl uso anterior es ineficiente porque resulta en una búsqueda del bloc en cada\nreconstrucción cuando el bloc solo es necesario cuando el usuario toca el\n`ElevatedButton`. En este escenario, es preferible usar `context.read` para\nacceder al bloc directamente donde se necesita (en este caso, en el callback\n`onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Resumen**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> Si accedes a un bloc para agregar un evento, realiza el acceso al bloc usando\n`context.read` en el callback donde se necesita.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Usa `context.watch` cuando accedas al estado del bloc para asegurar que el\nwidget se reconstruya cuando el estado cambie.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError toma Cubit\n\n##### Justificación\n\nDebido a la integración de `Cubit`, `onError` ahora se comparte entre las\ninstancias de `Bloc` y `Cubit`. Dado que `Cubit` es la base, `BlocObserver`\naceptará un tipo `Cubit` en lugar de un tipo `Bloc` en la sobrescritura de\n`onError`.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc no emite el último estado en la suscripción\n\n##### Justificación\n\nEste cambio se realizó para alinear `Bloc` y `Cubit` con el comportamiento\nincorporado de `Stream` en `Dart`. Además, conformar este comportamiento antiguo\nen el contexto de `Cubit` llevó a muchos efectos secundarios no deseados y, en\ngeneral, complicó innecesariamente las implementaciones internas de otros\npaquetes como `flutter_bloc` y `bloc_test` (requiriendo `skip(1)`, etc...).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nAnteriormente, el fragmento anterior mostraría el estado inicial del bloc\nseguido de los cambios de estado posteriores.\n\n**v6.x.x**\n\nEn v6.0.0, el fragmento anterior no muestra el estado inicial y solo muestra los\ncambios de estado posteriores. El comportamiento anterior se puede lograr con lo\nsiguiente:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Nota**: Este cambio solo afectará al código que dependa de suscripciones\ndirectas a blocs. Al usar `BlocBuilder`, `BlocListener` o `BlocConsumer` no\nhabrá ningún cambio notable en el comportamiento.\n\n### `package:bloc_test`\n\n#### ❗MockBloc solo requiere el tipo de Estado\n\n##### Justificación\n\nNo es necesario y elimina código adicional, además de hacer que `MockBloc` sea\ncompatible con `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen solo requiere el tipo de Estado\n\n##### Justificación\n\nNo es necesario y elimina código adicional, además de hacer que `whenListen` sea\ncompatible con `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest does not require Event type\n\n##### Justificación\n\nNo es necesario y elimina código adicional, además de hacer que `blocTest` sea\ncompatible con `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip por defecto es 0\n\n##### Justificación\n\nDado que las instancias de `bloc` y `cubit` ya no emitirán el último estado para\nnuevas suscripciones, ya no era necesario que `skip` tuviera un valor\npredeterminado de `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nEl estado inicial de un bloc o cubit se puede probar con lo siguiente:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest hacer que build sea síncrono\n\n##### Justificación\n\nAnteriormente, `build` se hizo `async` para que se pudieran realizar varias\npreparaciones para poner el bloc bajo prueba en un estado específico. Ya no es\nnecesario y también resuelve varios problemas debido a la latencia añadida entre\nla construcción y la suscripción internamente. En lugar de hacer una preparación\nasincrónica para poner un bloc en un estado deseado, ahora podemos establecer el\nestado del bloc encadenando `emit` con el estado deseado.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` solo es visible para pruebas y nunca debe usarse fuera de las pruebas.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗El parámetro `bloc` de BlocBuilder se renombró a `cubit`\n\n##### Justificación\n\nPara que `BlocBuilder` pueda interoperar con instancias de `bloc` y `cubit`, el\nparámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener parámetro bloc renombrado a cubit\n\n##### Justificación\n\nPara que `BlocListener` pueda interoperar con instancias de `bloc` y `cubit`, el\nparámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗BlocConsumer parámetro bloc renombrado a cubit\n\n##### Justificación\n\nPara que `BlocConsumer` pueda interoperar con instancias de `bloc` y `cubit`, el\nparámetro `bloc` se renombró a `cubit` (ya que `Cubit` es la clase base).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState ha sido eliminado\n\n##### Justificación\n\nComo desarrollador, tener que sobrescribir `initialState` al crear un bloc\npresenta dos problemas principales:\n\n- El `initialState` del bloc puede ser dinámico y también puede ser referenciado\n  en un momento posterior (incluso fuera del propio bloc). De alguna manera,\n  esto puede verse como una filtración de información interna del bloc a la capa\n  de UI.\n- Es verboso.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> Para más información, consulta\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate renombrado a BlocObserver\n\n##### Justificación\n\nEl nombre `BlocDelegate` no era una descripción precisa del papel que\ndesempeñaba la clase. `BlocDelegate` sugiere que la clase juega un papel activo,\nmientras que en realidad el papel previsto del `BlocDelegate` era ser un\ncomponente pasivo que simplemente observa todos los blocs en una aplicación.\n\n:::note\n\nIdealmente, no debería haber ninguna funcionalidad o características orientadas\nal usuario manejadas dentro de `BlocObserver`.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor ha sido eliminado\n\n##### Justificación\n\n`BlocSupervisor` era otro componente que los desarrolladores debían conocer e\ninteractuar con el único propósito de especificar un `BlocDelegate`\npersonalizado. Con el cambio a `BlocObserver`, sentimos que mejoraba la\nexperiencia del desarrollador al establecer el observador directamente en el\npropio bloc.\n\n?> Este cambio también nos permitió desacoplar otros complementos de bloc como\n`HydratedStorage` del `BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder condición renombrada a buildWhen\n\n##### Justificación\n\nCuando se usa `BlocBuilder`, anteriormente podíamos especificar una `condición`\npara determinar si el `builder` debería reconstruirse.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nEl nombre `condition` no es muy autoexplicativo u obvio y, más importante aún,\ncuando se interactúa con un `BlocConsumer`, la API se vuelve inconsistente\nporque los desarrolladores pueden proporcionar dos condiciones (una para\n`builder` y otra para `listener`). Como resultado, la API de `BlocConsumer`\nexpone un `buildWhen` y `listenWhen`.\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nPara alinear la API y proporcionar una experiencia de desarrollador más\nconsistente, `condition` fue renombrado a `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener condición renombrada a listenWhen\n\n##### Justificación\n\nPor las mismas razones descritas anteriormente, la condición de `BlocListener`\ntambién fue renombrada.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (anterior, actual) {\n    // devuelve true/false para determinar si se debe llamar al listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage y HydratedBlocStorage renombrados\n\n##### Justificación\n\nPara mejorar la reutilización del código entre\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) y\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), la implementación\nconcreta predeterminada de almacenamiento se renombró de `HydratedBlocStorage` a\n`HydratedStorage`. Además, la interfaz `HydratedStorage` se renombró de\n`HydratedStorage` a `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage desacoplado de BlocDelegate\n\n##### Justificación\n\nComo se mencionó anteriormente, `BlocDelegate` fue renombrado a `BlocObserver` y\nse estableció directamente como parte del `bloc` a través de:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nEl siguiente cambio se realizó para:\n\n- Mantener la consistencia con la nueva API de observador de bloc\n- Mantener el almacenamiento limitado solo a `HydratedBloc`\n- Desacoplar el `BlocObserver` del `Storage`\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Inicialización Simplificada\n\n##### Justificación\n\nAnteriormente, los desarrolladores tenían que llamar manualmente a\n`super.initialState ?? DefaultInitialState()` para configurar sus instancias de\n`HydratedBloc`. Esto es torpe y verboso y también incompatible con los cambios\nimportantes en `initialState` en `bloc`. Como resultado, en la versión v5.0.0,\nla inicialización de `HydratedBloc` es idéntica a la inicialización normal de\n`Bloc`.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/es/modeling-state.mdx",
    "content": "---\ntitle: Modelado de Estado\ndescription:\n  Una visión general de varias formas de modelar estados al usar package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nHay muchos enfoques diferentes cuando se trata de estructurar el estado de la\naplicación. Cada uno tiene sus propias ventajas y desventajas. En esta sección,\nveremos varios enfoques, sus pros y contras, y cuándo usar cada uno.\n\nLos siguientes enfoques son simplemente recomendaciones y son completamente\nopcionales. Siéntase libre de usar el enfoque que prefiera. Puede encontrar que\nalgunos de los ejemplos/documentación no siguen los enfoques principalmente por\nsimplicidad/concisión.\n\n:::tip\n\nLos siguientes fragmentos de código están enfocados en la estructura del estado.\nEn la práctica, también puede querer:\n\n- Extender `Equatable` de\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- Anotar la clase con `@Data()` de\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- Anotar la clase con **@immutable** de\n  [`package:meta`](https://pub.dev/packages/meta)\n- Implementar un método `copyWith`\n- Usar la palabra clave `const` para los constructores\n\n:::\n\n## Clase Concreta y Enum de Estado\n\nEste enfoque consiste en una **clase concreta única** para todos los estados\njunto con un `enum` que representa diferentes estados. Las propiedades son\nanulables y se manejan según el estado actual. Este enfoque funciona mejor para\nestados que no son estrictamente exclusivos y/o contienen muchas propiedades\ncompartidas.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Pros\n\n- **Simple**: Fácil de gestionar una sola clase y un enum de estado y todas las\n  propiedades son fácilmente accesibles.\n- **Conciso**: Generalmente requiere menos líneas de código en comparación con\n  otros enfoques.\n\n#### Contras\n\n- **No es Seguro en Tipos**: Requiere verificar el `estado` antes de acceder a\n  las propiedades. Es posible emitir un estado malformado que puede llevar a\n  errores. Las propiedades para estados específicos son anulables, lo que puede\n  ser engorroso de manejar y requiere ya sea desenvolvimiento forzado o realizar\n  verificaciones de nulidad. Algunos de estos contras pueden mitigarse\n  escribiendo pruebas unitarias y constructores especializados y nombrados.\n- **Inflado**: Resulta en un solo estado que puede volverse inflado con muchas\n  propiedades con el tiempo.\n\n#### Veredicto\n\nEste enfoque funciona mejor para estados simples o cuando los requisitos llaman\na estados que no son exclusivos (por ejemplo, mostrar un snackbar cuando ocurre\nun error mientras se sigue mostrando datos antiguos del último estado exitoso).\nEste enfoque proporciona flexibilidad y concisión a costa de la seguridad en\ntipos.\n\n## Clase Sellada y Subclases\n\nEste enfoque consiste en una **clase sellada** que contiene cualquier propiedad\ncompartida y múltiples subclases para los estados separados. Este enfoque es\nideal para estados separados y exclusivos.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Pros\n\n- **Seguro en Tipos**: El código es seguro en tiempo de compilación y no es\n  posible acceder accidentalmente a una propiedad no válida. Cada subclase\n  contiene sus propias propiedades, lo que hace claro qué propiedades pertenecen\n  a qué estado.\n- **Explícito**: Separa las propiedades compartidas de las propiedades\n  específicas del estado.\n- **Exhaustivo**: Usar una declaración `switch` para verificaciones exhaustivas\n  asegura que cada estado sea manejado explícitamente.\n  - Si no desea\n    [verificaciones exhaustivas](https://dart.dev/language/branches#exhaustiveness-checking)\n    o desea poder agregar subtipos más tarde sin romper la API, use el\n    modificador [final](https://dart.dev/language/class-modifiers#final).\n  - Consulte la\n    [documentación de clases selladas](https://dart.dev/language/class-modifiers#sealed)\n    para más detalles.\n\n#### Contras\n\n- **Verborrea**: Requiere más código (una clase base y una subclase por estado).\n  También puede requerir código duplicado para propiedades compartidas entre\n  subclases.\n- **Complejidad**: Agregar nuevas propiedades requiere actualizar cada subclase\n  y la clase base, lo que puede ser engorroso y llevar a un aumento en la\n  complejidad del estado. Además, puede requerir verificaciones de tipo\n  innecesarias/excesivas para acceder a propiedades.\n\n#### Veredicto\n\nEste enfoque funciona mejor para estados bien definidos y exclusivos con\npropiedades únicas. Este enfoque proporciona seguridad en tipos y verificaciones\nexhaustivas y enfatiza la seguridad sobre la concisión y simplicidad.\n"
  },
  {
    "path": "docs/src/content/docs/es/naming-conventions.mdx",
    "content": "---\ntitle: Convenciones de Nombres\ndescription:\n  Descripción general de las convenciones de nombres recomendadas al usar bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nLas siguientes convenciones de nombres son simplemente recomendaciones y son\ncompletamente opcionales. Siéntase libre de usar las convenciones de nombres que\nprefiera. Puede encontrar algunos de los ejemplos/documentación que no siguen\nlas convenciones de nombres principalmente por simplicidad/concisión. Estas\nconvenciones son fuertemente recomendadas para proyectos grandes con múltiples\ndesarrolladores.\n\n## Convenciones de Eventos\n\nLos eventos deben ser nombrados en **pasado** porque los eventos son cosas que\nya han ocurrido desde la perspectiva del bloc.\n\n### Anatomía\n\n`BlocSubject` + `Sustantivo (opcional)` + `Verbo (evento)`\n\nLos eventos de carga inicial deben seguir la convención: `BlocSubject` +\n`Started`\n\n:::note\n\nLa clase base del evento debe llamarse: `BlocSubject` + `Event`.\n\n:::\n\n### Ejemplos\n\n✅ **Bueno**\n\n<EventExamplesGood1 />\n\n❌ **Malo**\n\n<EventExamplesBad1 />\n\n## Convenciones de Estado\n\nLos estados deben ser sustantivos porque un estado es simplemente una\ninstantánea en un momento particular en el tiempo. Hay dos formas comunes de\nrepresentar el estado: usando subclases o usando una sola clase.\n\n### Anatomía\n\n#### Subclases\n\n`BlocSubject` + `Verbo (acción)` + `State`\n\nCuando se representa el estado como múltiples subclases, `State` debe ser uno de\nlos siguientes:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nLos estados iniciales deben seguir la convención: `BlocSubject` + `Initial`.\n\n:::\n\n#### Clase Única\n\n`BlocSubject` + `State`\n\nCuando se representa el estado como una sola clase base, se debe usar un enum\nllamado `BlocSubject` + `Status` para representar el estado:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nLa clase base del estado siempre debe llamarse: `BlocSubject` + `State`.\n\n:::\n\n### Ejemplos\n\n✅ **Bueno**\n\n##### Subclases\n\n<StateExamplesGood1Snippet />\n\n##### Clase Única\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Malo**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/es/testing.mdx",
    "content": "---\ntitle: Pruebas\ndescription: Los conceptos básicos de cómo escribir pruebas para tus blocs.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc fue diseñado para ser extremadamente fácil de probar. En esta sección,\nrepasaremos cómo hacer pruebas unitarias a un bloc.\n\nPara simplificar, escribamos pruebas para el `CounterBloc` que creamos en\n[Conceptos Básicos](/es/bloc-concepts).\n\nPara recapitular, la implementación de `CounterBloc` se ve así:\n\n<CounterBlocSnippet />\n\n## Configuración\n\nAntes de comenzar a escribir nuestras pruebas, vamos a necesitar agregar un\nmarco de pruebas a nuestras dependencias.\n\nNecesitamos agregar [test](https://pub.dev/packages/test) y\n[bloc_test](https://pub.dev/packages/bloc_test) a nuestro proyecto.\n\n<AddDevDependenciesSnippet />\n\n## Pruebas\n\nComencemos creando el archivo para nuestras pruebas de `CounterBloc`,\n`counter_bloc_test.dart` e importando el paquete de pruebas.\n\n<CounterBlocTestImportsSnippet />\n\nA continuación, necesitamos crear nuestro `main` así como nuestro grupo de\npruebas.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nLos grupos son para organizar pruebas individuales así como para crear un\ncontexto en el que se puede compartir un `setUp` y `tearDown` común en todas las\npruebas individuales.\n\n:::\n\nComencemos creando una instancia de nuestro `CounterBloc` que se utilizará en\ntodas nuestras pruebas.\n\n<CounterBlocTestSetupSnippet />\n\nAhora podemos comenzar a escribir nuestras pruebas individuales.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nPodemos ejecutar todas nuestras pruebas con el comando `dart test`.\n\n:::\n\n¡En este punto deberíamos tener nuestra primera prueba aprobada! Ahora\nescribamos una prueba más compleja usando el paquete\n[bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nDeberíamos poder ejecutar las pruebas y ver que todas están aprobadas.\n\nEso es todo, las pruebas deberían ser fáciles y deberíamos sentirnos seguros al\nhacer cambios y refactorizar nuestro código.\n\nPuedes consultar la\n[Aplicación del Clima](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\npara un ejemplo de una aplicación completamente probada.\n"
  },
  {
    "path": "docs/src/content/docs/es/why-bloc.mdx",
    "content": "---\ntitle: ¿Por qué Bloc?\ndescription:\n  Una visión general de lo que hace de Bloc una solución sólida para la gestión\n  de estado.\nsidebar:\n  order: 1\n---\n\nBloc facilita la separación de la presentación de la lógica de negocio, haciendo\nque tu código sea _rápido_, _fácil de probar_ y _reutilizable_.\n\nAl construir aplicaciones de calidad para producción, la gestión del estado se\nvuelve crítica.\n\nComo desarrolladores queremos:\n\n- saber en qué estado se encuentra nuestra aplicación en cualquier momento.\n- probar fácilmente cada caso para asegurarnos de que nuestra aplicación\n  responde adecuadamente.\n- registrar cada interacción del usuario en nuestra aplicación para poder tomar\n  decisiones basadas en datos.\n- trabajar de la manera más eficiente posible y reutilizar componentes tanto\n  dentro de nuestra aplicación como en otras aplicaciones.\n- tener muchos desarrolladores trabajando sin problemas dentro de una sola base\n  de código siguiendo los mismos patrones y convenciones.\n- desarrollar aplicaciones rápidas y reactivas.\n\nBloc fue diseñado para satisfacer todas estas necesidades y muchas más.\n\nExisten muchas soluciones para la gestión del estado y decidir cuál usar puede\nser una tarea desalentadora. ¡No hay una solución perfecta para la gestión del\nestado! Lo importante es que elijas la que mejor funcione para tu equipo y tu\nproyecto.\n\nBloc fue diseñado con tres valores fundamentales en mente:\n\n- **Simple:** Fácil de entender y puede ser utilizado por desarrolladores con\n  diferentes niveles de habilidad.\n- **Poderoso:** Ayuda a crear aplicaciones increíbles y complejas componiéndolas\n  de componentes más pequeños.\n- **Probable:** Probar fácilmente cada aspecto de una aplicación para que\n  podamos iterar con confianza.\n\nEn general, Bloc intenta hacer que los cambios de estado sean predecibles\nregulando cuándo puede ocurrir un cambio de estado y haciendo cumplir una única\nforma de cambiar el estado en toda la aplicación.\n"
  },
  {
    "path": "docs/src/content/docs/fa/architecture.mdx",
    "content": "---\ntitle: معماری\ndescription: نمای کلی از الگوهای معماری پیشنهادی هنگام استفاده از Bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nاستفاده از کتابخانه Bloc به ما اجازه می‌دهد برنامه خود را به سه لایه جدا کنیم:\n\n- رابط کاربری Presentation\n- منطق کسب و کار Business Logic\n- داده Data\n  - Repository\n  - Data Provider\n\nما از پایین‌ترین لایه (دورترین از رابط کاربری) شروع می‌کنیم و به سمت لایه رابط\nکاربری پیش می‌رویم.\n\n## لایه داده\n\nمسئولیت لایه داده، بازیابی/دستکاری داده‌ها از یک یا چند منبع است.\n\nلایه داده می‌تواند به دو قسمت تقسیم شود:\n\n- Repository\n- Data Provider\n\nاین لایه پایین‌ترین سطح برنامه است و با پایگاه داده‌ها، درخواست‌های شبکه و سایر\nمنابع داده ناهمگام تعامل دارد.\n\n### Data Provider\n\nمسئولیت data provider، ارائه داده‌های خام است. Data provider باید عمومی و همه\nمنظوره باشد.\n\nData provider معمولاً APIهای ساده‌ای برای انجام عملیات\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) ارائه\nمی‌دهد. ممکن است متدهایی مانند `createData`, `readData`, `updateData` و\n`deleteData` به عنوان بخشی از لایه داده داشته باشیم.\n\n<DataProviderSnippet />\n\n### Repository\n\nلایه repository یک پوشش در اطراف یک یا چند data provider است که لایه Bloc با آن\nارتباط برقرار می‌کند.\n\n<RepositorySnippet />\n\nهمانطور که می‌بینید، لایه repository می‌تواند با چندین data provider تعامل کند و\nتبدیلاتی روی داده‌ها انجام دهد قبل از اینکه نتیجه را به لایه منطق کسب‌وکار تحویل\nدهد.\n\n## لایه منطق کسب‌وکار\n\nمسئولیت لایه منطق کسب‌وکار، پاسخ به ورودی از لایه رابط کاربری با حالت‌های جدید\nاست. این لایه می‌تواند به یک یا چند repository وابسته باشد تا داده‌های مورد نیاز\nبرای ساخت حالت برنامه را بازیابی کند.\n\nلایه منطق کسب‌وکار را به عنوان پل بین رابط کاربری (لایه رابط کاربری) و لایه داده\nدر نظر بگیرید. لایه منطق کسب‌وکار از رویدادها/اقدامات از لایه رابط کاربری مطلع\nمی‌شود و سپس با repository ارتباط برقرار می‌کند تا یک حالت جدید برای لایه رابط\nکاربری بسازد.\n\n<BusinessLogicComponentSnippet />\n\n### ارتباط Bloc به Bloc\n\nاز آنجایی که blocها streamها را نمایش می‌دهند، ممکن است وسوسه شوید که یک bloc\nبسازید که به bloc دیگری گوش دهد. شما **نباید** این کار را بکنید. گزینه‌های بهتری\nنسبت به استفاده از کد زیر وجود دارد:\n\n<BlocTightCouplingSnippet />\n\nدر حالی که کد بالا بدون خطا است (و حتی خودش را پاکسازی می‌کند)، مشکل بزرگ‌تری\nدارد: وابستگی بین دو bloc ایجاد می‌کند.\n\nبه طور کلی، وابستگی‌های خواهر و برادر بین دو موجودیت در یک لایه معماری باید به\nهر قیمتی اجتناب شود، زیرا اتصال محکم ایجاد می‌کند که نگهداری آن سخت است. از\nآنجایی که blocها در لایه معماری منطق کسب‌وکار قرار دارند، هیچ bloc نباید از هیچ\nbloc دیگری اطلاع داشته باشد.\n\n![Application Architecture Layers](~/assets/architecture/architecture.png)\n\nیک bloc باید فقط از طریق رویدادها و از repositoryهای تزریق شده (یعنی\nrepositoryهایی که در سازنده bloc به آن داده می‌شود) اطلاعات دریافت کند.\n\nاگر در موقعیتی هستید که یک bloc نیاز به پاسخ به bloc دیگری دارد، دو گزینه دیگر\nدارید. می‌توانید مشکل را به لایه بالاتر (به لایه رابط کاربری) ببرید، یا به لایه\nپایین‌تر (به لایه domain).\n\n#### اتصال Blocها از طریق رابط کاربری\n\nمی‌توانید از `BlocListener` استفاده کنید تا به یک bloc گوش دهید و هر زمان که\nbloc اول تغییر کند، یک رویداد به bloc دیگری اضافه کنید.\n\n<BlocLooseCouplingPresentationSnippet />\n\nکد بالا از نیاز `SecondBloc` به دانستن درباره `FirstBloc` جلوگیری می‌کند و اتصال\nشل را تشویق می‌کند. برنامه [flutter_weather](/fa/tutorials/flutter-weather) از\n[این تکنیک](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nاستفاده می‌کند تا تم برنامه را بر اساس اطلاعات آب و هوایی که دریافت می‌شود تغییر\nدهد.\n\nدر برخی موقعیت‌ها، ممکن است نخواهید دو bloc را در لایه رابط کاربری جفت کنید. در\nعوض، اغلب منطقی است که دو bloc منبع داده یکسانی داشته باشند و هر زمان که داده‌ها\nتغییر کنند، به‌روزرسانی شوند.\n\n#### اتصال Blocها از طریق Domain\n\nدو bloc می‌توانند به یک stream از repository گوش دهند و حالت‌های خود را مستقل از\nیکدیگر هر زمان که داده‌های repository تغییر کند، به‌روزرسانی کنند. استفاده از\nrepositoryهای واکنشی برای نگه داشتن حالت همگام‌سازی شده در برنامه‌های بزرگ\nسازمانی رایج است.\n\nابتدا، یک repository ایجاد کنید یا از repository موجودی استفاده کنید که یک\n`Stream` داده ارائه می‌دهد. برای مثال، repository زیر یک stream بی‌پایان از چند\nایده برنامه یکسانی را نمایش می‌دهد:\n\n<AppIdeasRepositorySnippet />\n\nهمان repository می‌تواند به هر bloc که نیاز به واکنش به ایده‌های برنامه جدید\nدارد، تزریق شود. در مثال زیر یک `AppIdeaRankingBloc` وجود دارد که برای هر ایده\nبرنامه ورودی از repository بالا، یک حالت تولید می‌کند:\n\n<AppIdeaRankingBlocSnippet />\n\nبرای اطلاعات بیشتر درباره استفاده از streamها با Bloc، این مقاله را مطالعه کنید\n[How to use Bloc with streams and concurrency](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## لایه رابط کاربری\n\nمسئولیت لایه رابط کاربری، تصمیم‌گیری درباره نحوه رندر خود بر اساس یک یا چند حالت\nbloc است. علاوه بر این، باید ورودی کاربر و رویدادهای چرخه حیات برنامه را مدیریت\nکند.\n\nاکثر streamهای برنامه با یک رویداد `AppStart` شروع می‌شود که برنامه را برای\nواکشی برخی داده‌ها برای نمایش به کاربر راه‌اندازی می‌کند.\n\nدر این سناریو، لایه رابط کاربری یک رویداد `AppStart` اضافه می‌کند.\n\nعلاوه بر این، لایه رابط کاربری باید تصمیم بگیرد که چه چیزی را روی صفحه بر اساس\nحالت از لایه bloc رندر کند.\n\n<PresentationComponentSnippet />\n\nتا اینجا، حتی با وجود داشتن چند قطعه کد، همه چیز نسبتاً سطح بالا بوده است. در\nبخش آموزش، همه این‌ها را کنار هم می‌گذاریم زیرا چندین برنامه نمونه مختلف\nمی‌سازیم.\n"
  },
  {
    "path": "docs/src/content/docs/fa/bloc-concepts.mdx",
    "content": "---\ntitle: مفاهیم Bloc\ndescription: An overview of the core concepts for package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nلطفاً قبل از کار با[`package:bloc`](https://pub.dev/packages/bloc) بخش‌های زیر\nرا به‌دقت مطالعه کنید.\n\n:::\n\nچندین مفهوم اصلی وجود دارد که درک صحیح آن‌ها برای استفاده از پکیج bloc ضروری\nاست.\n\nدر بخش‌های بعدی، هر کدام از این مفاهیم را به‌طور مفصل بررسی خواهیم کرد و همچنین\nنحوه اعمال آن‌ها را در یک اپلیکیشن شمارنده (counter app) مثال می‌زنیم.\n\n## استریم‌ها (Streams)\n\n:::note\n\nبرای اطلاعات بیشتر در مورد `Streams` به مستندات رسمی زبان دارت\n[Dart Documentation](https://dart.dev/tutorials/language/streams)  \nمراجعه کنید.\n\n:::\n\nیک استریم، دنباله‌ای از داده‌های ناهمزمان (asynchronous) است.\n\nبرای استفاده از کتابخانه bloc، داشتن درک پایه‌ای از `Streams` و نحوه عملکرد\nآن‌ها بسیار مهم است.\n\nاگر با `Streams` آشنا نیستید، آن را مانند یک لوله آب تصور کنید که آب از داخل آن\nجریان دارد. لوله همان `Stream` است و آب، داده‌های ناهمزمان هستند.\n\nمی‌توانیم یک `Stream` در زبان دارت ایجاد کنیم با نوشتن یک تابع `async*` (تولید\nکننده ناهمزمان).\n\n<CountStreamSnippet />\n\nبا علامت‌گذاری یک تابع به صورت `async*`، می‌توانیم از کلمه کلیدی `yield` استفاده\nکنیم و یک `Stream` از داده‌ها برگردانیم. در مثال بالا، ما یک `Stream` از اعداد\nصحیح (integers) تا مقدار پارامتر `max` برمی‌گردانیم.\n\nهر بار که در یک تابع `async*` از `yield` استفاده می‌کنیم، آن قطعه داده را از\nطریق `Stream` به جلو می‌فرستیم (push می‌کنیم). می‌توانیم `Stream` بالا را به\nچندین روش مصرف کنیم.\n\nاگر بخواهیم تابعی بنویسیم که مجموع یک `Stream` از اعداد صحیح را برگرداند،\nمی‌تواند چیزی شبیه به این باشد:\n\n<SumStreamSnippet />\n\nبا علامت‌گذاری تابع بالا به صورت `async`، می‌توانیم از کلمه کلیدی `await`\nاستفاده کنیم و یک `Future` از اعداد صحیح برگردانیم. در این مثال، ما روی هر مقدار\nدر استریم `await` می‌کنیم و مجموع تمام اعداد صحیح موجود در استریم را\nبرمی‌گردانیم.\n\nمی‌توانیم همه چیز را به این شکل کنار هم بگذاریم:\n\n<StreamsMainSnippet />\n\nحالا که درک پایه‌ای از نحوه کار `Streams` در زبان دارت داریم، آماده‌ایم تا\nدرباره مؤلفه اصلی پکیج bloc یاد بگیریم: یک `Cubit`.\n\n## Cubit\n\nیک Cubit کلاسی است که از `BlocBase` ارث‌بری می‌کند و می‌توان آن را گسترش داد تا\nهر نوعی از `حالت (state)` را مدیریت کند.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nیک `Cubit` می‌تواند توابعی را در معرض دید قرار دهد که با فراخوانی آن‌ها می‌توان\nتغییرات حالت را تحریک کرد.\n\nحالت‌ها (States) خروجی یک `Cubit` هستند و بخشی از حالت برنامه شما را نشان\nمی‌دهند.\n\nکامپوننت‌های رابط کاربری می‌توانند از حالت‌ها مطلع شوند و بخش‌هایی از خود را بر\nاساس حالت فعلی دوباره رسم کنند.\n\n:::note\n\nبرای کسب اطلاعات بیشتر در مورد ریشه‌های `Cubit`این\n[مسئله](https://github.com/felangel/cubit/issues/69) را بررسی کنید.\n\n:::\n\n### ساخت یک Cubit\n\nما می‌توانیم یک `CounterCubit` را به شکل زیر بسازیم:\n\n<CounterCubitSnippet />\n\nهنگام ساخت یک `Cubit` باید نوع حالتی (State) را تعریف کنیم که `Cubit` مدیریت\nخواهد کرد. در مورد `CounterCubit` بالا، وضعیت می‌تواند با یک `int` نمایش داده\nشود، اما در موارد پیچیده‌تر، ممکن است لازم باشد به جای یک نوع اولیه ، از یک\n`class` استفاده شود.\n\nدومین کاری که هنگام ساخت یک `Cubit` باید انجام دهیم، مشخص کردن حالت اولیه\n(initial state) است.\n\nمی‌توانیم این کار را با فراخوانی `super` به همراه مقدار حالت اولیه انجام دهیم.\n\nدر قطعه کد بالا، ما به صورت داخلی، حالت اولیه را `0` قرار می‌دهیم، اما همچنین\nمی‌توانیم با پذیرش یک مقدار بیرونی، `Cubit` را انعطاف‌پذیرتر کنیم:\n\n<CounterCubitInitialStateSnippet />\n\nاین امر به ما اجازه می‌دهد تا نمونه‌های `CounterCubit` را با حالت‌های اولیه\nمختلفی ایجاد کنیم، مانند:\n\n<CounterCubitInstantiationSnippet />\n\n### تغییرات حالت Cubit\n\nهر `Cubit` این قابلیت را دارد که با استفاده از `emit،` یک حالت جدید را خروجی\nدهد.\n\n<CounterCubitIncrementSnippet />\n\nدر قطعه کد بالا، `CounterCubit` یک متد عمومی به نام `increment` را در معرض دید\nقرار می‌دهد که می‌توان آن را از بیرون فراخوانی کرد تا به `CounterCubit` اطلاع\nدهد که حالت خود را افزایش دهد. هنگامی که `increment` فراخوانی می‌شود، می‌توانیم\nاز طریق ویژگی (getter) `state` به حالت فعلی `Cubit` دسترسی پیدا کرده و با اضافه\nکردن ۱ به حالت فعلی، یک حالت جدید را `emit` کنیم.\n\n:::caution\n\nمتد `emit` محافظت‌شده (protected) است، به این معنی که تنها باید در داخل یک\n`Cubit` استفاده شود\n\n:::\n\n### استفاده از یک Cubit\n\nحالا می‌توانیم `CounterCubit` که پیاده‌سازی کرده‌ایم را برداشته و از آن استفاده\nکنیم!\n\n#### استفاده پایه\n\n<CounterCubitBasicUsageSnippet />\n\nدر قطعه کد بالا، ابتدا یک نمونه از `CounterCubit` ایجاد می‌کنیم. سپس، حالت فعلی\n`Cubit` را که همان حالت اولیه است (زیرا هنوز هیچ حالت جدیدی انتشار/emit نشده\nاست)، چاپ می‌کنیم. در مرحله بعد، تابع `increment` را برای راه‌اندازی یک تغییر\nحالت فراخوانی می‌کنیم. در نهایت، مجدداً حالت `Cubit` را که از `0` به `1` تغییر\nکرده است، چاپ کرده و تابع `close` را روی `Cubit` فراخوانی می‌کنیم تا استریم\nداخلی حالت بسته شود.\n\n#### استفاده از استریم\n\n`Cubit` یک `استریم` را در معرض دید قرار می‌دهد که به ما امکان می‌دهد\nبه‌روزرسانی‌های حالت را به صورت همزمان دریافت کنیم:\n\n<CounterCubitStreamUsageSnippet />\n\nدر قطعه کد بالا، ما در حال مشترک شدن در `CounterCubit` هستیم و هنگام هر تغییر\nحالت، پرینت را فراخوانی می‌کنیم. سپس، تابع `increment` را فراخوانی می‌کنیم که یک\nحالت جدید را `emit` خواهد کرد. در پایان، زمانی که دیگر نمی‌خواهیم به‌روزرسانی‌ها\nرا دریافت کنیم، `cancel` را روی `subscription` فراخوانی کرده و `Cubit` را\nمی‌بندیم.\n\n:::note\n\n`await Future.delayed(Duration.zero)` برای این مثال اضافه شده است تا از لغو فوری\nاشتراک جلوگیری شود.\n\n:::\n\n:::caution\n\nفقط تغییرات حالت متعاقب هنگام فراخوانی `listen` روی یک `Cubit` دریافت خواهند شد.\n\n:::\n\n### مشاهده Cubit\n\nهنگامی که یک `Cubit` حالت جدیدی را `emit` می‌کند، یک `Change` (تغییر) رخ می‌دهد.\nما می‌توانیم تمام تغییرات یک `Cubit` مشخص را با بازنویسی متد `onChange` مشاهده\nکنیم.\n\n<CounterCubitOnChangeSnippet />\n\nسپس می‌توانیم با `Cubit` تعامل داشته باشیم و تمام تغییرات خروجی داده شده در\nکنسول را مشاهده کنیم.\n\n<CounterCubitOnChangeUsageSnippet />\n\nمثال بالا خروجی زیر را خواهد داشت:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nیک `Change` درست قبل از به‌روزرسانی حالت `Cubit` رخ می‌دهد. یک `Change` شامل\n`currentState` (حالت فعلی) و `nextState` (حالت بعدی) است.\n\n:::\n\n#### BlocObserver\n\nیکی از مزایای اضافی استفاده از کتابخانه `bloc` این است که می‌توانیم به تمام\n`تغییرات` در یک مکان دسترسی داشته باشیم. حتی اگر در این برنامه فقط یک `Cubit`\nداشته باشیم، در برنامه‌های بزرگ‌تر معمول است که بسیاری از `Cubits` بخش‌های مختلف\nحالت برنامه را مدیریت کنند.\n\nاگر بخواهیم در پاسخ به تمام `تغییرات` کاری انجام دهیم، می‌توانیم به سادگی\n`BlocObserver` خودمان را ایجاد کنیم\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nتنها کاری که باید انجام دهیم این است که `BlocObserver` را گسترش دهیم و متد\n`onChange` را بازنویسی کنیم.\n\n:::\n\nبرای استفاده از `SimpleBlocObserver`، فقط نیاز داریم تابع `main` را کمی تغییر\nدهیم:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nقطعه کد بالا خروجی زیر را خواهد داشت:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nبازنویسی داخلی `onChange` ابتدا فراخوانی می‌شود، که `super.onChange` را فراخوانی\nمی‌کند و `onChange` در `BlocObserver` را مطلع می‌کند.\n\n:::\n\n:::tip\n\nدر `BlocObserver` ما به نمونه `Cubit` علاوه بر خود `Change` دسترسی داریم.\n\n:::\n\n### مدیریت خطا Cubit\n\nهر `Cubit` دارای متد `addError` است که می‌توان از آن برای نشان دادن اینکه خطایی\nرخ داده است استفاده کرد.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` می‌تواند در داخل `Cubit` بازنویسی شود تا تمام خطاهای یک `Cubit` خاص را\nمدیریت کند.\n\n:::\n\n`onError` همچنین می‌تواند در `BlocObserver` بازنویسی شود تا تمام خطاهای\nگزارش‌شده را به صورت جهانی مدیریت کند.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nاگر همان برنامه را دوباره اجرا کنیم، باید خروجی زیر را ببینیم:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nیک `Bloc` کلاسی پیشرفته‌تر است که به جای توابع، از `events` برای تحریک تغییرات\n`state` استفاده می‌کند. `Bloc` همچنین از `BlocBase` ارث‌بری می‌کند، به این معنی\nکه API عمومی مشابهی با `Cubit` دارد. با این حال، به جای فراخوانی یک `function`\nروی یک `Bloc` و انتشار مستقیم یک `state` جدید، `Blocs` `events` را دریافت\nمی‌کنند و `events` ورودی را به `states` خروجی تبدیل می‌کنند.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### ایجاد یک Bloc\n\nساخت یک `Bloc` مشابه ساخت یک `Cubit` است، به جز اینکه علاوه بر تعریف `state` که\nمدیریت خواهیم کرد، باید `event` که `Bloc` قادر به پردازش آن خواهد بود را نیز\nتعریف کنیم.\n\nEvent ها ورودی های به یک Bloc هستند. آن‌ها معمولاً در پاسخ به تعاملات کاربر\nمانند فشار دکمه یا رویدادهای چرخه حیات مانند بارگذاری صفحه اضافه می‌شوند.\n\n<CounterBlocSnippet />\n\nدقیقاً مانند هنگام ساخت `CounterCubit`، باید یک حالت اولیه را با ارسال آن به\nsuperclass از طریق `super` مشخص کنیم.\n\n### تغییرات حالت Bloc\n\n`Bloc` از ما می‌خواهد که مدیریت کنندگان رویداد(event) را از طریق API `on<Event>`\nثبت کنیم، بر خلاف توابع در `Cubit`. یک مدیریت کننده رویداد مسئول تبدیل هر event\nورودی به صفر یا بیشتر states خروجی است.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nیک `EventHandler` به رویداد اضافه‌شده و همچنین یک `Emitter` دسترسی دارد که\nمی‌توان از آن برای انتشار صفر یا بیشتر states در پاسخ به رویداد ورودی استفاده\nکرد.\n\n:::\n\nسپس می‌توانیم `EventHandler` را به‌روزرسانی کنیم تا رویداد\n`CounterIncrementPressed` را مدیریت کند:\n\n<CounterBlocIncrementSnippet />\n\nدر قطعه کد بالا، ما یک `EventHandler` را برای مدیریت تمام رویدادها\n`CounterIncrementPressed` ثبت کرده‌ایم. برای هر رویداد ورودی\n`CounterIncrementPressed`، می‌توانیم از طریق getter `state` به حالت فعلی bloc\nدسترسی پیدا کنیم و `emit(state + 1)` کنیم.\n\n:::note\n\nاز آنجایی که کلاس `Bloc` از `BlocBase` ارث‌بری می‌کند، ما به حالت فعلی bloc در\nهر زمان از طریق getter `state` دسترسی داریم، دقیقاً مانند `Cubit`.\n\n:::\n\n:::caution\n\nBlocs هرگز نباید مستقیماً states جدید را `emit` کنند. در عوض، هر تغییر حالت باید\nدر پاسخ به یک رویداد ورودی در داخل یک `EventHandler` خروجی داده شود.\n\n:::\n\n:::caution\n\nهر دو blocs و cubits حالت های تکراری را نادیده می‌گیرند. اگر `State nextState`\nرا emit کنیم که در آن `state == nextState` باشد، هیچ تغییر حالتی رخ نخواهد داد.\n\n:::\n\n### استفاده از Bloc\n\nدر این مرحله، می‌توانیم یک نمونه از `CounterBloc` خود ایجاد کنیم و از آن استفاده\nکنیم!\n\n#### استفاده پایه\n\n<CounterBlocUsageSnippet />\n\nدر قطعه کد بالا، ابتدا یک نمونه از `CounterBloc` ایجاد می‌کنیم. سپس حالت فعلی\n`Bloc` را که حالت اولیه است (زیرا هنوز هیچ حالت جدیدی emit نشده است) چاپ\nمی‌کنیم.\n\nدر مرحله بعد، رویداد `CounterIncrementPressed` را برای تحریک یک تغییر حالت اضافه\nمی‌کنیم. در نهایت، حالت `Bloc` را دوباره چاپ می‌کنیم که از `0` به `1` تغییر کرده\nاست و `close` را روی `Bloc` فراخوانی می‌کنیم تا استریم داخلی حالت بسته شود.\n\n:::note\n\n`await Future.delayed(Duration.zero)` اضافه شده است تا اطمینان حاصل شود که منتظر\nتکرار بعدی event-loop هستیم (اجازه می‌دهد `EventHandler` رویداد را پردازش کند).\n\n:::\n\n#### استفاده Stream\n\nدقیقاً مانند `Cubit`، یک `Bloc` یک نوع خاصی از `Stream` است، به این معنی که\nمی‌توانیم برای به‌روزرسانی‌های real-time به حالت آن مشترک شویم:\n\n<CounterBlocStreamUsageSnippet />\n\nدر قطعه کد بالا، ما در حال مشترک شدن در `CounterBloc` هستیم و پرینت را روی هر\nتغییر حالت فراخوانی می‌کنیم. سپس رویداد `CounterIncrementPressed` را اضافه\nمی‌کنیم که `EventHandler` `on<CounterIncrementPressed>` را تحریک می‌کند و یک\nحالت جدید emit می‌کند.\n\nدر نهایت، `cancel` را روی subscription فراخوانی می‌کنیم وقتی که دیگر نمی‌خواهیم\nبه‌روزرسانی‌ها را دریافت کنیم و `Bloc` را می‌بندیم.\n\n:::note\n\n`await Future.delayed(Duration.zero)` برای این مثال اضافه شده است تا از لغو فوری\nsubscription جلوگیری شود.\n\n:::\n\n### مشاهده یک Bloc\n\nاز آنجایی که `Bloc` از `BlocBase` ارث‌بری می‌کند، می‌توانیم تمام تغییرات حالت\nبرای یک `Bloc` را با استفاده از `onChange` مشاهده کنیم.\n\n<CounterBlocOnChangeSnippet />\n\nسپس می‌توانیم `main.dart` را به‌روزرسانی کنیم به:\n\n<CounterBlocOnChangeUsageSnippet />\n\nحالا اگر قطعه کد بالا را اجرا کنیم، خروجی به شکل زیر خواهد بود:\n\n<CounterBlocOnChangeOutputSnippet />\n\nیکی از عوامل کلیدی تمایز بین `Bloc` و `Cubit` این است که چون `Bloc` event-driven\nاست، ما همچنین می‌توانیم اطلاعاتی درباره آنچه که تغییر حالت را تحریک کرده است،\nثبت کنیم.\n\nمی‌توانیم این کار را با بازنویسی `onTransition` انجام دهیم.\n\nتغییر از یک حالت به حالت دیگر یک `Transition` نامیده می‌شود. یک `Transition`\nشامل حالت فعلی، event، و حالت بعدی است.\n\n<CounterBlocOnTransitionSnippet />\n\nاگر سپس همان قطعه `main.dart` را از قبل دوباره اجرا کنیم، باید خروجی زیر را\nببینیم:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` قبل از `onChange` فراخوانی می‌شود و شامل event است که تغییر از\n`currentState` به `nextState` را تحریک کرده است.\n\n:::\n\n#### BlocObserver\n\nدقیقاً مانند قبل، می‌توانیم `onTransition` را در یک `BlocObserver` سفارشی\nبازنویسی کنیم تا تمام transitions که از یک مکان رخ می‌دهند را مشاهده کنیم.\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` ابتدا فراخوانی می‌شود (local قبل از global) و سپس `onChange`.\n\n:::\n\nویژگی منحصر به فرد دیگری از نمونه‌های `Bloc` این است که به ما اجازه می‌دهند\n`onEvent` را بازنویسی کنیم که هر زمان یک رویداد جدید به `Bloc` اضافه شود\nفراخوانی می‌شود. دقیقاً مانند `onChange` و `onTransition`، `onEvent` می‌تواند\nlocal و همچنین global بازنویسی شود.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nمی‌توانیم همان `main.dart` را از قبل اجرا کنیم و باید خروجی زیر را ببینیم:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` به محض اضافه شدن event فراخوانی می‌شود. `onEvent` local قبل از\n`onEvent` global در `BlocObserver` فراخوانی می‌شود.\n\n:::\n\n### مدیریت خطا Bloc\n\nدقیقاً مانند `Cubit`، هر `Bloc` دارای متدهای `addError` و `onError` است.\nمی‌توانیم نشان دهیم که خطایی رخ داده است با فراخوانی `addError` از هر جایی داخل\n`Bloc` خود. سپس می‌توانیم به تمام خطاها با بازنویسی `onError` واکنش نشان دهیم،\nدقیقاً مانند `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nاگر همان `main.dart` را از قبل دوباره اجرا کنیم، می‌توانیم ببینیم وقتی خطایی\nگزارش می‌شود چگونه به نظر می‌رسد:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\n`onError` local ابتدا فراخوانی می‌شود و سپس `onError` global در `BlocObserver`.\n\n:::\n\n:::note\n\n`onError` و `onChange` دقیقاً به همان شکل برای نمونه‌های `Bloc` و `Cubit` کار\nمی‌کنند.\n\n:::\n\n:::caution\n\nهر استثنای مدیریت نشده‌ای که در داخل یک `EventHandler` رخ دهد نیز به `onError`\nگزارش می‌شود.\n\n:::\n\n## Cubit در مقابل Bloc\n\nحالا که مبانی کلاس‌های `Cubit` و `Bloc` را پوشش داده‌ایم، ممکن است در حال فکر\nکردن باشید که چه زمانی باید از `Cubit` استفاده کنید و چه زمانی از `Bloc`.\n\n### Cubit مزایای\n\n#### سادگی\n\nیکی از بزرگترین مزایای استفاده از `Cubit` سادگی است. هنگام ساخت یک `Cubit`، فقط\nباید `state` و همچنین توابعی که می‌خواهیم برای تغییر حالت در معرض دید قرار دهیم\nرا تعریف کنیم. در مقایسه، هنگام ساخت یک `Bloc`، باید states، events، و\nپیاده‌سازی `EventHandler` را تعریف کنیم. این باعث می‌شود `Cubit` آسان‌تر درک شود\nو کد کمتری درگیر باشد.\n\nحالا بیایید به دو پیاده‌سازی counter نگاه کنیم:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nپیاده‌سازی `Cubit` مختصرتر است و به جای تعریف رویدادهای جداگانه، توابع مانند\nرویدادها عمل می‌کنند. علاوه بر این، هنگام استفاده از `Cubit`، می‌توانیم به سادگی\n`emit` را از هر جایی فراخوانی کنیم تا یک تغییر حالت تحریک کنیم.\n\n### مزایای Bloc\n\n#### قابلیت ردیابی\n\nیکی از بزرگترین مزایای استفاده از `Bloc` دانستن توالی تغییرات حالت و همچنین\nدقیقاً آنچه که آن تغییرات را تحریک کرده است. برای حالتی که برای عملکرد یک برنامه\nحیاتی است، ممکن است بسیار مفید باشد که از رویکرد event-driven بیشتری استفاده\nکنیم تا تمام رویدادها علاوه بر تغییرات حالت را ثبت کنیم.\n\nیک مورد استفاده رایج ممکن است مدیریت `AuthenticationState` باشد. برای سادگی،\nبیایید بگوییم می‌توانیم `AuthenticationState` را از طریق یک `enum` نمایش دهیم:\n\n<AuthenticationStateSnippet />\n\nمی‌تواند دلایل زیادی وجود داشته باشد که چرا حالت برنامه از `authenticated` به\n`unauthenticated` تغییر کند. برای مثال، کاربر ممکن است دکمه logout را زده باشد و\nدرخواست کرده باشد از برنامه خارج شود. از طرف دیگر، شاید token دسترسی کاربر لغو\nشده باشد و او به زور logout شده باشد. هنگام استفاده از `Bloc` می‌توانیم به وضوح\nردگیری کنیم که چگونه حالت برنامه به یک حالت خاص رسیده است.\n\n<AuthenticationTransitionSnippet />\n\n`Transition` بالا تمام اطلاعاتی را که نیاز داریم برای درک اینکه چرا حالت تغییر\nکرده است به ما می‌دهد. اگر از `Cubit` برای مدیریت `AuthenticationState` استفاده\nکرده بودیم، logs ما به شکل زیر می‌بود:\n\n<AuthenticationChangeSnippet />\n\nاین به ما می‌گوید که کاربر logout شده است اما توضیح نمی‌دهد چرا که ممکن است برای\nدیباگ و درک اینکه چگونه حالت برنامه در طول زمان تغییر می‌کند حیاتی باشد.\n\n#### تبدیل رویدادهای پیشرفته\n\nبخش دیگری که `Bloc` نسبت به `Cubit` برتری دارد وقتی است که نیاز داریم از\nعملگرهای reactive مانند `buffer`، `debounceTime`، `throttle` و غیره استفاده\nکنیم.\n\n:::tip\n\nبرای stream transformers به\n[`package:stream_transform`](https://pub.dev/packages/stream_transform) و\n[`package:rxdart`](https://pub.dev/packages/rxdart) نگاه کنید.\n\n:::\n\n`Bloc` دارای یک event sink است که به ما اجازه می‌دهد جریان ورودی رویدادها را\nکنترل و تبدیل کنیم.\n\nبرای مثال، اگر در حال ساخت یک جستجوی real-time هستیم، احتمالاً می‌خواهیم\nدرخواست‌ها به backend را debounce کنیم تا از rate-limited شدن جلوگیری کنیم و\nهمچنین هزینه/بار روی backend را کاهش دهیم.\n\nبا `Bloc` می‌توانیم یک `EventTransformer` سفارشی ارائه دهیم تا نحوه پردازش\nرویدادها ورودی توسط `Bloc` را تغییر دهیم.\n\n<DebounceEventTransformerSnippet />\n\nبا کد بالا، می‌توانیم به راحتی رویدادها ورودی را با کد اضافی بسیار کمی debounce\nکنیم.\n\n:::tip\n\nبرای مجموعه‌ای opinionated از event transformers\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) را بررسی\nکنید.\n\n:::\n\nاگر مطمئن نیستید از کدام استفاده کنید، با `Cubit` شروع کنید و می‌توانید بعداً در\nصورت نیاز به `Bloc` refactor یا scale-up کنید.\n"
  },
  {
    "path": "docs/src/content/docs/fa/faqs.mdx",
    "content": "---\ntitle: پرسش‌های متداول\ndescription: پاسخ به پرسش‌های متداول در مورد کتابخانه bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## حالت به‌روزرسانی نمی‌شود\n\n❔ **پرسش**: من یک حالت را در bloc خود emit می‌کنم اما UI به‌روزرسانی نمی‌شود.\nچه اشتباهی کرده‌ام؟\n\n💡 **پاسخ**: اگر از Equatable استفاده می‌کنید، مطمئن شوید که همه ویژگی‌ها را به\nprops getter ارسال کنید.\n\n✅ **خوب**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **بد**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nعلاوه بر این، مطمئن شوید که یک نمونه جدید از حالت را در bloc خود emit می‌کنید.\n\n✅ **خوب**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **بد**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nویژگی‌های `Equatable` همیشه باید کپی شوند نه اینکه تغییر داده شوند. اگر یک کلاس\n`Equatable` شامل `List` یا `Map` به عنوان ویژگی‌ها باشد، مطمئن شوید از `List.of`\nیا `Map.of` به ترتیب استفاده کنید تا اطمینان حاصل شود که برابری بر اساس مقادیر\nویژگی‌ها ارزیابی می‌شود نه مرجع.\n\n:::\n\n## زمان استفاده از Equatable\n\n❔**پرسش**: چه زمانی باید از Equatable استفاده کنم؟\n\n💡**پاسخ**:\n\n<EquatableEmitSnippet />\n\nدر سناریوی بالا اگر `StateA` از `Equatable` ارث‌بری کند، فقط یک تغییر حالت رخ\nخواهد داد (emit دوم نادیده گرفته خواهد شد). به طور کلی، باید از `Equatable`\nاستفاده کنید اگر بخواهید کد خود را برای کاهش تعداد rebuildها بهینه کنید. نباید\nاز `Equatable` استفاده کنید اگر بخواهید همان حالت پشت سر هم چندین انتقال را\nراه‌اندازی کند.\n\nعلاوه بر این، استفاده از `Equatable` تست blocها را بسیار آسان‌تر می‌کند زیرا\nمی‌توانیم نمونه‌های خاصی از حالت‌های bloc را انتظار داشته باشیم نه اینکه از\n`Matchers` یا `Predicates` استفاده کنیم.\n\n<EquatableBlocTestSnippet />\n\nبدون `Equatable` تست بالا شکست خواهد خورد و نیاز به بازنویسی خواهد داشت مانند:\n\n<NoEquatableBlocTestSnippet />\n\n## مدیریت خطاها\n\n❔ **پرسش**: چگونه می‌توانم یک خطا را مدیریت کنم در حالی که هنوز داده‌های قبلی\nرا نشان می‌دهم؟\n\n💡 **پاسخ**:\n\nاین سوال کاملا به نحوه مدل‌سازی حالت bloc بستگی دارد. در مواردی که داده‌ها باید\nحتی در حضور خطا حفظ شوند، از یک کلاس حالت واحد استفاده کنید.\n\n<SingleStateSnippet />\n\nاین کار اجازه می‌دهد تا ویجت‌ها همزمان به ویژگی‌های `data` و `error` دسترسی\nداشته باشند و bloc می‌تواند از `state.copyWith` برای حفظ داده‌های قدیمی حتی\nزمانی که خطایی رخ داده استفاده کند.\n\n<SingleStateUsageSnippet />\n\n## Bloc در برابر Redux\n\n❔ **پرسش**: تفاوت بین Bloc و Redux چیست؟\n\n💡 **پاسخ**:\n\nBLoC یک الگوی طراحی است که توسط قوانین زیر تعریف می‌شود:\n\n1. ورودی و خروجی BLoC جریان‌ها و sinkهای ساده هستند.\n2. وابستگی‌ها باید قابل تزریق و مستقل از پلتفرم باشند.\n3. هیچ شاخه‌بندی پلتفرم مجاز نیست.\n4. پیاده‌سازی می‌تواند هر چیزی باشد تا زمانی که قوانین بالا را دنبال کنید.\n\nراهنمایی‌های UI عبارتند از:\n\n1. هر کامپوننت \"به اندازه کافی پیچیده\" یک BLoC مربوطه دارد.\n2. کامپوننت‌ها باید ورودی‌ها را \"همانطور که هست\" ارسال کنند.\n3. کامپوننت‌ها باید خروجی‌ها را تا حد امکان نزدیک به \"همانطور که هست\" نشان دهند.\n4. همه شاخه‌بندی باید بر اساس خروجی‌های boolean ساده BLoC باشد.\n\nکتابخانه Bloc الگوی طراحی BLoC را پیاده‌سازی می‌کند و هدف آن انتزاع RxDart برای\nساده‌سازی تجربه توسعه‌دهنده است.\n\nسه اصل Redux عبارتند از:\n\n1. منبع حقیقت واحد\n2. حالت فقط خواندنی است\n3. تغییرات با توابع خالص انجام می‌شوند\n\nکتابخانه bloc اصل اول را نقض می‌کند؛ با bloc حالت در چندین bloc توزیع می‌شود.\nعلاوه بر این، مفهومی از middleware در bloc وجود ندارد و bloc طراحی شده تا\nتغییرات حالت async را بسیار آسان کند، اجازه می‌دهد چندین حالت برای یک رویداد\nemit شود.\n\n## Bloc در برابر Provider\n\n❔ **پرسش**: تفاوت بین Bloc و Provider چیست؟\n\n💡 **پاسخ**: `provider` برای تزریق وابستگی طراحی شده است (پوششی برای\nInheritedWidget). هنوز نیاز دارید که نحوه مدیریت حالت خود را مشخص کنید (از طریق\n`ChangeNotifier`, `Bloc`, `Mobx`, و غیره...). کتابخانه Bloc از `provider` به طور\nداخلی استفاده می‌کند تا ارائه و دسترسی به blocها در درخت ویجت آسان شود.\n\n## BlocProvider.of() نمی‌تواند Bloc را پیدا کند\n\n❔ **پرسش**: هنگام استفاده از `BlocProvider.of(context)` نمی‌تواند bloc را پیدا\nکند. چگونه می‌توانم این را برطرف کنم؟\n\n💡 **پاسخ**: نمی‌توانید به bloc از همان context که در آن ارائه شده دسترسی پیدا\nکنید بنابراین باید اطمینان حاصل کنید که `BlocProvider.of()` در یک `BuildContext`\nفرزند فراخوانی شود.\n\n✅ **خوب**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **بد**\n\n<BlocProviderBad1Snippet />\n\n## ساختار پروژه\n\n❔ **پرسش**: چگونه باید پروژه خود را ساختاردهی کنم؟\n\n💡 **پاسخ**: در حالی که واقعاً پاسخ درست/غلطی برای این پرسش وجود ندارد، برخی\nمراجع توصیه‌شده عبارتند از\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nمهم‌ترین چیز داشتن یک ساختار پروژه **ثابت** و **عمدی** است.\n\n## افزودن رویدادها در داخل یک Bloc\n\n❔ **پرسش**: آیا افزودن رویدادها در داخل یک bloc خوب است؟\n\n💡 **پاسخ**: در اکثر موارد، رویدادها باید از خارج اضافه شوند اما در برخی موارد\nانتخابی، ممکن است افزودن رویدادها از داخل منطقی باشد.\n\nشایع‌ترین موقعیت که رویدادهای داخلی استفاده می‌شوند، زمانی است که تغییرات حالت\nباید در پاسخ به به‌روزرسانی‌های real-time از یک repository رخ دهند. در این\nموقعیت‌ها، repository محرک تغییر حالت است نه یک رویداد خارجی مانند ضربه زدن به\nدکمه.\n\nدر مثال زیر، حالت `MyBloc` به کاربر فعلی بستگی دارد که از طریق `Stream<User>` از\n`UserRepository` نمایش داده می‌شود. `MyBloc` برای تغییرات در کاربر فعلی گوش\nمی‌دهد و هر زمان که یک کاربر از جریان کاربر emit شود، یک رویداد داخلی\n`_UserChanged` اضافه می‌کند.\n\n<BlocInternalAddEventSnippet />\n\nبا افزودن یک رویداد داخلی، همچنین می‌توانیم یک `transformer` سفارشی برای رویداد\nمشخص کنیم تا تعیین کنیم چگونه چندین رویداد `_UserChanged` پردازش خواهند شد -- به\nطور پیش‌فرض آنها همزمان پردازش خواهند شد.\n\nبسیار توصیه می‌شود که رویدادهای داخلی خصوصی باشند. این یک راه صریح برای نشان\nدادن است که یک رویداد خاص فقط در داخل bloc استفاده می‌شود و از دانستن\nکامپوننت‌های خارجی درباره رویداد جلوگیری می‌کند.\n\n<BlocInternalEventSnippet />\n\nما می‌توانیم به طور جایگزین یک رویداد خارجی `Started` تعریف کنیم و از API\n`emit.forEach` برای مدیریت واکنش به به‌روزرسانی‌های real-time کاربر استفاده\nکنیم:\n\n<BlocExternalForEachSnippet />\n\nمزایای رویکرد بالا عبارتند از:\n\n- نیازی به رویداد داخلی `_UserChanged` نداریم\n- نیازی به مدیریت دستی `StreamSubscription` نداریم\n- کنترل کامل بر زمانی که bloc به جریان به‌روزرسانی‌های کاربر subscribe می‌کند\n  داریم\n\nمعایب رویکرد بالا عبارتند از:\n\n- نمی‌توانیم به راحتی subscription را `pause` یا `resume` کنیم\n- نیاز داریم که یک رویداد عمومی `Started` را نمایش دهیم که باید از خارج اضافه\n  شود\n- نمی‌توانیم از یک `transformer` سفارشی برای تنظیم نحوه واکنش به به‌روزرسانی‌های\n  کاربر استفاده کنیم\n\n## نمایش متدهای عمومی\n\n❔ **پرسش**: آیا نمایش متدهای عمومی در نمونه‌های bloc و cubit من خوب است؟\n\n💡 **پاسخ**\n\nوقتی یک Cubit ایجاد می‌کنید، توصیه می‌شود فقط متدهای عمومی را برای ایجاد تغییر\nدر حالت در دسترس قرار دهید. در نتیجه، به طور کلی همه متدهای عمومی در یک نمونه\ncubit باید `void` یا `Future<void>` را برگردانند.\n\nهنگام ایجاد یک bloc، توصیه می‌شود که از نمایش هر متد عمومی سفارشی اجتناب کنید و\nدر عوض با فراخوانی `add` bloc را از رویدادها مطلع کنید.\n"
  },
  {
    "path": "docs/src/content/docs/fa/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: مفاهیم بلوک فلاتر\ndescription: An overview of the core concepts for package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nلطفاً اطمینان حاصل کنید که بخش‌های زیر را با دقت مطالعه کنید قبل از کار با\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nتمام ویجت‌های منتشر شده توسط پکیج `flutter_bloc` با نمونه‌های `Cubit` و `Bloc`\nیکپارچه می‌شوند.\n\n:::\n\n## ویجت‌های Bloc\n\n### BlocBuilder\n\n**BlocBuilder** یک ویجت فلاتر است که نیاز به یک `Bloc` و یک تابع `builder` دارد.\n`BlocBuilder` ساخت ویجت را در پاسخ به حالت‌های جدید مدیریت می‌کند. `BlocBuilder`\nبسیار شبیه به `StreamBuilder` است اما API ساده‌تری دارد تا مقدار کد اضافی را\nکاهش دهد. تابع `builder` ممکن است چندین بار فراخوانی شود و باید یک\n[تابع خالص](https://en.wikipedia.org/wiki/Pure_function) باشد که یک ویجت را در\nپاسخ به حالت برمی‌گرداند.\n\nاگر می‌خواهید در پاسخ به تغییرات حالت کاری انجام دهید مانند ناوبری، نمایش دیالوگ\nو غیره، به `BlocListener` نگاه کنید...\n\nاگر پارامتر `bloc` حذف شود، `BlocBuilder` به طور خودکار جستجویی با استفاده از\n`BlocProvider` و `BuildContext` فعلی انجام می‌دهد.\n\n<BlocBuilderSnippet />\n\nفقط در صورتی bloc را مشخص کنید که بخواهید یک bloc ارائه دهید که به یک ویجت واحد\nمحدود شود و از طریق `BlocProvider` والد و `BuildContext` فعلی قابل دسترسی نباشد.\n\n<BlocBuilderExplicitBlocSnippet />\n\nبرای کنترل دقیق‌تر بر زمانی که تابع `builder` فراخوانی می‌شود، می‌توان یک\nپارامتر اختیاری `buildWhen` ارائه داد. `buildWhen` حالت قبلی bloc و حالت فعلی\nbloc را دریافت می‌کند و یک مقدار بولی برمی‌گرداند. اگر `buildWhen` مقدار true\nبرگرداند، `builder` با `state` فراخوانی می‌شود و ویجت دوباره ساخته می‌شود. اگر\n`buildWhen` مقدار false برگرداند، `builder` با `state` فراخوانی نمی‌شود و\nدوباره‌سازی رخ نخواهد داد.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** یک ویجت فلاتر است که مشابه `BlocBuilder` است اما به\nتوسعه‌دهندگان اجازه می‌دهد تا به‌روزرسانی‌ها را با انتخاب یک مقدار جدید بر اساس\nحالت فعلی bloc فیلتر کنند. اگر مقدار یا نمونه انتخاب شده تغییر نکند, از\nبروزرسانی ویجت یا ساخت های جدید جلوگیری میشود. مقدار انتخاب‌شده باید تغییرناپذیر\nباشد تا `BlocSelector` بتواند به طور دقیق تعیین کند که آیا `builder` دوباره\nفراخوانی شود یا نه.\n\nاگر پارامتر `bloc` حذف شود، `BlocSelector` به طور خودکار جستجویی با استفاده از\n`BlocProvider` و `BuildContext` فعلی انجام می‌دهد.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** یک ویجت فلاتر است که یک bloc را از طریق\n`BlocProvider.of<T>(context)` به فرزندان خود ارائه می‌دهد. این ویجت به عنوان یک\nویجت تزریق وابستگی (DI) استفاده می‌شود تا یک نمونه واحد از یک bloc بتواند به\nچندین ویجت در یک زیردرخت ارائه شود.\n\nدر بیشتر موارد، `BlocProvider` باید برای ایجاد bloc‌های جدید استفاده شود که برای\nبقیه زیردرخت در دسترس خواهند بود. در این حالت، از آنجایی که `BlocProvider` مسئول\nایجاد bloc است، به طور خودکار بسته شدن bloc را مدیریت خواهد کرد.\n\n<BlocProviderSnippet />\n\nبه طور پیش‌فرض، `BlocProvider` bloc را به صورت تنبل ایجاد می‌کند، به این معنی که\n`create` زمانی اجرا می‌شود که bloc از طریق `BlocProvider.of<BlocA>(context)`\nجستجو شود.\n\nبرای لغو این رفتار و اجبار به اجرای فوری `create`، می‌توان `lazy` را روی `false`\nتنظیم کرد.\n\n<BlocProviderEagerSnippet />\n\nدر برخی موارد، `BlocProvider` می‌تواند برای ارائه یک bloc موجود به بخشی جدید از\nدرخت ویجت استفاده شود. این معمولاً زمانی استفاده می‌شود که یک bloc موجود نیاز به\nدر دسترس بودن برای یک مسیر جدید داشته باشد. در این حالت، `BlocProvider` به طور\nخودکار bloc را نمیبندد زیرا آن را ایجاد نکرده است.\n\n<BlocProviderValueSnippet />\n\nسپس از ChildA یا ScreenA می‌توانیم BlocA را بازیابی کنیم, با استفاده از:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** یک ویجت فلاتر است که چندین ویجت `BlocProvider` را به یک\nویجت ادغام می‌کند. `MultiBlocProvider` خوانایی را بهبود می‌بخشد و نیاز به تودرتو\nکردن چندین `BlocProvider` را از بین می‌برد. با استفاده از `MultiBlocProvider`\nمی‌توانیم از:\n\n<NestedBlocProviderSnippet />\n\nبرویم به:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nزمانی که یک `BlocProvider` در زمینه یک `MultiBlocProvider` تعریف شود، هر `child`\nنادیده گرفته می‌شود.\n\n:::\n\n### BlocListener\n\n**BlocListener** یک ویجت فلاتر است که یک `BlocWidgetListener` و یک `Bloc`\nاختیاری دریافت می‌کند و `listener` را در پاسخ به تغییرات حالت در bloc فراخوانی\nمی‌کند. باید برای عملکردهایی استفاده شود که نیاز به رخ دادن یک بار در هر تغییر\nحالت دارند مانند ناوبری، نمایش `SnackBar`، نمایش `Dialog` و غیره...\n\n`listener` فقط یک بار برای هر تغییر حالت فراخوانی می‌شود (**نه** شامل حالت\nاولیه) بر خلاف `builder` در `BlocBuilder` و یک تابع `void` است.\n\nاگر پارامتر `bloc` حذف شود، `BlocListener` به طور خودکار جستجویی با استفاده از\n`BlocProvider` و `BuildContext` فعلی انجام می‌دهد.\n\n<BlocListenerSnippet />\n\nفقط در صورتی bloc را مشخص کنید که بخواهید یک bloc ارائه دهید که از طریق\n`BlocProvider` والد و `BuildContext` فعلی قابل دسترسی نباشد.\n\n<BlocListenerExplicitBlocSnippet />\n\nبرای کنترل دقیق‌تر بر زمانی که تابع `listener` فراخوانی می‌شود، می‌توان یک\nپارامتر اختیاری `listenWhen` ارائه داد. `listenWhen` حالت قبلی bloc و حالت فعلی\nbloc را دریافت می‌کند و یک مقدار بولی برمی‌گرداند. اگر `listenWhen` مقدار true\nبرگرداند، `listener` با `state` فراخوانی می‌شود. اگر `listenWhen` مقدار false\nبرگرداند، `listener` با `state` فراخوانی نخواهد شد.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** یک ویجت فلاتر است که چندین ویجت `BlocListener` را به یک\nویجت ادغام می‌کند. `MultiBlocListener` خوانایی را بهبود می‌بخشد و نیاز به تودرتو\nکردن چندین `BlocListener` را از بین می‌برد. با استفاده از `MultiBlocListener`\nمی‌توانیم از:\n\n<NestedBlocListenerSnippet />\n\nبرسیم, به:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nزمانی که یک `BlocListener` در زمینه یک `MultiBlocListener` تعریف شود، هر `child`\nنادیده گرفته می‌شود.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** یک `builder` و `listener` را برای واکنش به حالت‌های جدید نمایش\nمی‌دهد. `BlocConsumer` مشابه یک `BlocListener` و `BlocBuilder` تودرتو است اما\nمقدار کد اضافی مورد نیاز را کاهش می‌دهد. `BlocConsumer` باید فقط زمانی استفاده\nشود که لازم باشد هم UI دوباره ساخته شود و هم واکنش‌های دیگری به تغییرات حالت در\n`bloc` اجرا شوند. `BlocConsumer` یک `BlocWidgetBuilder` و `BlocWidgetListener`\nمورد نیاز و یک `bloc`، `BlocBuilderCondition` و `BlocListenerCondition` اختیاری\nدریافت می‌کند.\n\nاگر پارامتر `bloc` حذف شود، `BlocConsumer` به طور خودکار جستجویی با استفاده از\n`BlocProvider` و `BuildContext` فعلی انجام می‌دهد.\n\n<BlocConsumerSnippet />\n\nیک `listenWhen` و `buildWhen` اختیاری می‌توانند برای کنترل دانه‌ای‌تر بر زمانی\nکه `listener` و `builder` فراخوانی شوند پیاده‌سازی شوند. `listenWhen` و\n`buildWhen` در هر تغییر `state` `bloc` فراخوانی خواهند شد. هر کدام حالت قبلی و\nحالت فعلی را دریافت می‌کنند و باید یک `bool` برگردانند که تعیین کند آیا تابع\n`builder` و/یا `listener` فراخوانی شود یا نه. حالت قبلی به حالت `bloc` هنگام\nمقداردهی اولیه `BlocConsumer` مقداردهی خواهد شد. `listenWhen` و `buildWhen`\nاختیاری هستند و اگر پیاده‌سازی نشوند، به طور پیش‌فرض `true` خواهند بود.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** یک ویجت فلاتر است که یک repository را از طریق\n`RepositoryProvider.of<T>(context)` به فرزندان خود ارائه می‌دهد. این ویجت به\nعنوان یک ویجت تزریق وابستگی (DI) استفاده می‌شود تا یک نمونه واحد از یک\nrepository بتواند به چندین ویجت در یک زیردرخت ارائه شود. `BlocProvider` باید\nبرای ارائه bloc‌ها استفاده شود در حالی که `RepositoryProvider` فقط باید برای\nrepository‌ها استفاده شود.\n\n<RepositoryProviderSnippet />\n\nسپس از `ChildA` می‌توانیم نمونه `Repository` را با استفاده از:\n\n<RepositoryProviderLookupSnippet />\n\nبازیابی کنیم.\n\nRepository‌هایی که منابع را مدیریت می‌کنند که باید dispose شوند می‌توانند این\nکار را از طریق callback `dispose` انجام دهند:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** یک ویجت فلاتر است که چندین ویجت `RepositoryProvider`\nرا به یک ویجت ادغام می‌کند. `MultiRepositoryProvider` خوانایی را بهبود می‌بخشد و\nنیاز به تودرتو کردن چندین `RepositoryProvider` را از بین می‌برد. با استفاده از\n`MultiRepositoryProvider` می‌توانیم از:\n\n<NestedRepositoryProviderSnippet />\n\nبرسیم, به:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nزمانی که یک `RepositoryProvider` در زمینه یک `MultiRepositoryProvider` تعریف\nشود، هر `child` نادیده گرفته می‌شود.\n\n:::\n\n## کاربرد BlocProvider\n\nبیایید نگاهی بیندازیم به اینکه چگونه از `BlocProvider` برای ارائه یک\n`CounterBloc` به یک `CounterPage` استفاده کنیم و با `BlocBuilder` به تغییرات\nحالت واکنش نشان دهیم.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nدر این نقطه، ما با موفقیت لایه نمایشی خود را از لایه منطق کسب‌وکار جدا کرده‌ایم.\nتوجه کنید که ویجت `CounterPage` هیچ اطلاعی از اینکه هنگام فشار دادن دکمه‌ها توسط\nکاربر چه اتفاقی می‌افتد ندارد. ویجت به سادگی به `CounterBloc` می‌گوید که کاربر\nدکمه افزایش یا کاهش را فشار داده است.\n\n## کاربرد RepositoryProvider\n\nما قصد داریم نگاهی به نحوه استفاده از `RepositoryProvider` در زمینه مثال\n[`flutter_weather`][flutter_weather_link] بیندازیم.\n\n<WeatherRepositorySnippet />\n\nدر `main.dart` ما، `runApp` را با ویجت `WeatherApp` خود فراخوانی می‌کنیم.\n\n<WeatherMainSnippet />\n\nما نمونه `WeatherRepository` خود را از طریق `RepositoryProvider` به درخت ویجت\nتزریق خواهیم کرد.\n\nهنگام نمونه‌سازی یک bloc، می‌توانیم به نمونه یک repository از طریق\n`context.read` دسترسی پیدا کنیم و repository را از طریق سازنده به bloc تزریق\nکنیم.\n\n<WeatherAppSnippet />\n\n:::tip\n\nاگر بیش از یک repository دارید، می‌توانید از `MultiRepositoryProvider` برای\nارائه چندین نمونه repository به زیردرخت استفاده کنید.\n\n:::\n\n:::note\n\nاز callback `dispose` برای مدیریت آزادسازی هر منبع هنگام unmount شدن\n`RepositoryProvider` استفاده کنید.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension Methods\n\n[متدهای توسعه](https://dart.dev/guides/language/extension-methods)، که در Dart\n2.7 معرفی شدند، راهی برای افزودن عملکرد به کتابخانه‌های موجود هستند. در این بخش،\nنگاهی به متدهای توسعه موجود در `package:flutter_bloc` و نحوه استفاده از آنها\nخواهیم داشت.\n\n`flutter_bloc` وابستگی به [package:provider](https://pub.dev/packages/provider)\nدارد که استفاده از\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)\nرا ساده می‌کند.\n\nداخل پکیج `package:flutter_bloc` از `package:provider` برای پیاده‌سازی ویجت‌های\n`BlocProvider`، `MultiBlocProvider`، `RepositoryProvider` و\n`MultiRepositoryProvider` استفاده می‌کند. `package:flutter_bloc` توسعه‌های\n`ReadContext`، `WatchContext` و `SelectContext` را از `package:provider` صادر\nمی‌کند.\n\n:::note\n\nدرباره [`package:provider`](https://pub.dev/packages/provider) بیشتر بیاموزید.\n\n:::\n\n### context.read\n\n`context.read<T>()` نزدیک‌ترین نمونه اجداد از نوع `T` را جستجو می‌کند و عملکردی\nمعادل `BlocProvider.of<T>(context)` دارد. `context.read` معمولاً برای بازیابی\nنمونه یک bloc به منظور افزودن یک رویداد در callback‌های `onPressed` استفاده\nمی‌شود.\n\n:::note\n\n`context.read<T>()` به `T` گوش نمی‌دهد -- اگر شیء ارائه‌شده از نوع `T` تغییر\nکند، `context.read` باعث دوباره‌سازی ویجت نخواهد شد.\n\n:::\n\n#### کاربرد\n\n✅ **انجام دهید:** از `context.read` برای افزودن رویدادها در callback‌ها استفاده\nکنید.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **پرهیز کنید:** استفاده از `context.read` برای بازیابی حالت در یک متد\n`build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nمثال بالا مستعد خطا است زیرا ویجت Text دوباره ساخته نخواهد شد اگر حالت bloc\nتغییر کند.\n\n:::caution\n\nبه جای آن از `BlocBuilder` یا `context.watch` استفاده کنید تا در پاسخ به تغییرات\nحالت دوباره ساخته شوند.\n\n:::\n\n### context.watch\n\nمانند `context.read<T>()`، `context.watch<T>`() نزدیک‌ترین نمونه اجداد از نوع\n`T` را ارائه می‌دهد، اما همچنین به تغییرات در نمونه گوش می‌دهد. عملکردی معادل\n`BlocProvider.of<T>(context, listen: true)` دارد. اگر شیء ارائه‌شده از نوع `T`\nتغییر کند، `context.watch` باعث دوباره‌سازی خواهد شد\n\n:::caution\n\n`context.watch` فقط در متد `build` یک `StatelessWidget` یا کلاس `State` قابل\nدسترسی است.\n\n:::\n\n#### کاربرد\n\n✅ **انجام دهید:** از `BlocBuilder` به جای `context.watch` برای تعیین محدوده\nدوباره‌سازی‌ها استفاده کنید.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Whenever the state changes, only the Text is rebuilt.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nبه طور جایگزین، از `Builder` برای تعیین محدوده دوباره‌سازی‌ها استفاده کنید.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever the state changes, only the Text is rebuilt.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **انجام دهید:** از `Builder` و `context.watch` به عنوان `MultiBlocBuilder`\nاستفاده کنید.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n❌ **پرهیز کنید:** استفاده از `context.watch` وقتی ویجت والد در متد `build` به\nحالت وابسته نیست.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nاستفاده از `context.watch` در ریشه متد `build` باعث می‌شود کل ویجت دوباره ساخته\nشود وقتی حالت bloc تغییر کند.\n\n:::\n\n### context.select\n\nمانند `context.watch<T>()`، `context.select<T, R>(R function(T value))`\nنزدیک‌ترین نمونه اجداد از نوع `T` را ارائه می‌دهد و به تغییرات در `T` گوش\nمی‌دهد. بر خلاف `context.watch`، `context.select` به شما اجازه می‌دهد تا به\nتغییرات در بخشی کوچک‌تر از حالت گوش دهید.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nبالا فقط ویجت را دوباره می‌سازد وقتی ویژگی `name` از حالت `ProfileBloc` تغییر\nکند.\n\n#### کاربرد\n\n✅ **انجام دهید:** از `BlocSelector` به جای `context.select` برای تعیین محدوده\nدوباره‌سازی‌ها استفاده کنید.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Whenever the state.name changes, only the Text is rebuilt.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nبه طور جایگزین، از `Builder` برای تعیین محدوده دوباره‌سازی‌ها استفاده کنید.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever state.name changes, only the Text is rebuilt.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **پرهیز کنید:** استفاده از `context.select` وقتی ویجت والد در متد build به\nحالت وابسته نیست.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state.value changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nاستفاده از `context.select` در ریشه متد `build` باعث می‌شود کل ویجت دوباره ساخته\nشود وقتی انتخاب تغییر کند.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/fa/getting-started.mdx",
    "content": "---\ntitle: شروع شدن\ndescription: همه چیزی که برای شروع ساختن با استفاده از Bloc نیاز دارید.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## بسته‌ها\n\nاکوسیستم Bloc از بسته های متعددی تشکیل شده است که در زیر فهرست شده اند:\n\n| بسته                                                                                       | توصیف                                | لینک                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | اجزای AngularDart                    | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | API‌های اصلی Dart                    | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | تبدیل‌کننده‌های رویداد               | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter                        | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | API های تست                          | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools                   | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | ویجت ها فلاتر                        | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | پشتیبانی از حافظه پنهان/ماندگاری     | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | پشتیبانی از واگَرد/اَزنو (Undo/Redo) | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## نصب\n\n<InstallationTabs />\n\n:::note\n\nبرای شروع استفاده از Bloc، باید [SDK دارت](https://dart.dev/get-dart) را در\nدستگاه خود نصب کنید.\n\n:::\n\n## وارد کردن (Imports)\n\nحالا که با موفقیت bloc را نصب کردیم، می‌توانیم `main.dart` خود را ایجاد کنیم و\nبسته `bloc` مربوطه را وارد کنیم.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/fa/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ از\n    <a href=\"https://shop.bloclibrary.dev\">فروشگاه Bloc</a> دیدن کنید✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: یک کتابخانه مدیریت وضعیت قابل پیش بینی برای دارت.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: شروع کنید\n      link: /fa/getting-started/\n      variant: primary\n      icon: rocket\n    - text: نمایش در گیت‌هاب\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"با 💖 حمایت شده توسط\"\n\tbecomeASponsor=\"اسپانسر شوید\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"شروع کنید\" icon=\"rocket\">\n\t```sh\n\t# Bloc را به پروژه خود اضافه کنید.\n\tdart pub add bloc\n\t```\n\n[راهنمای شروع کار](/fa/getting-started) ما, دستورالعمل‌های گام به گامی را برای\nشروع استفاده از Bloc در عرض چند دقیقه ارائه می‌دهد.\n\n</SplitCard>\n\n<Card title=\"با یک تور راهنمایی همراه باشید\" icon=\"star\">\n\t[آموزش های رسمی](/fa/tutorials/flutter-counter) را تکمیل کنید تا بهترین روش ها\n\t(Best practices) را بیاموزید و انواع برنامه های مختلف را با پشتیبانی Bloc\n\tبسازید.\n</Card>\n\n<Card title=\"ساخت با Bloc\" icon=\"laptop\">\n\t[برنامه های نمونه](https://github.com/felangel/bloc/tree/master/examples) با\n\tکیفیت بالا و کاملاً آزمایش شده مانند شمارنده، تایمر، لیست بی نهایت، آب و هوا،\n\tانجام کار و موارد دیگر را بررسی کنید!\n</Card>\n\n<ListCard title=\"یادگیری\" icon=\"open-book\">\n\n    - [چرا Bloc؟](/fa/why-bloc)\n    - [مفاهیم اصلی](/fa/bloc-concepts)\n    - [معماری](/fa/architecture)\n    - [تست کردن](/fa/testing)\n    - [قراردادهای نامگذاری](/fa/naming-conventions)\n    - [FAQs](/fa/faqs)\n\n</ListCard>\n\n  <ListCard title=\"یکپارچه سازی ها\" icon=\"puzzle\">\n    - [یکپارچگی با VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [یکپارچگی با IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim Integration](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [یکپارچگی با ابزار خط فرمان Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [قالب‌های سفارشی](https://brickhub.dev/search?q=bloc)\n    - [ابزارهای توسعه‌دهندگان](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"به Discord ما بپیوندید\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint/configuration.mdx",
    "content": "---\ntitle: پیکربندی لینتر\ndescription: پیکربندی لینتر bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nبه‌طور پیش‌فرض، لینتر bloc هیچ تشخیصی گزارش نمی‌دهد مگر اینکه گزینه‌های تحلیل\n(analysis options) پروژه را به‌طور صریح پیکربندی کرده باشید.\n\nبرای شروع، یک فایل `analysis_options.yaml` در ریشهٔ پروژه ایجاد کنید یا فایل\nموجود را ویرایش نمایید و فهرستی از قوانین را زیر کلید سطح‌بالای `bloc` قرار\nدهید:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nلینتر را با دستور زیر در ترمینال اجرا کنید:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nدستور بالا تمام فایل‌ها در پوشهٔ جاری و زیرپوشه‌های آن را تحلیل خواهد کرد، اما\nمی‌توانید با ارسال آرگومان‌های خط فرمان، تنها فایل‌ها یا پوشه‌های خاصی را هم نیز\nلینت کنید:\n\n<RunBlocLintInSrcTestSnippet />\n\nدستور بالا تمام کدهای موجود در پوشه‌های `src` و `test` را تحلیل خواهد کرد.\n\nاگر قانون `avoid_flutter_imports` فعال باشد، هر فایل bloc یا cubit که شامل یک\nimport مربوط به فلاتر باشد به‌صورت هشدار گزارش خواهد شد:\n\n<AvoidFlutterImportsWarningSnippet />\n\nمی‌توانید این هشدار را با اجرای دستور `bloc lint` مشاهده کنید:\n\n<RunBlocLintCounterCubitSnippet />\n\nخروجی باید چیزی مانند زیر باشد:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nدر اینجا فهرست تمام قوانین لینت پشتیبانی‌شده قرار دارد:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint/customizing-rules.mdx",
    "content": "---\ntitle: سفارشی‌سازی قواعد لینت\ndescription: سفارشی‌سازی قواعد لینتر bloc\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nمی‌توانید رفتار لینتر bloc را با تغییر سطح شدت (severity) قواعد، فعال یا\nغیرفعال‌سازی قواعد به‌صورت تکی، و یا استثنا کردن فایل‌ها از تحلیل ایستا،\nشخصی‌سازی کنید.\n\n## فعال و غیرفعال کردن قواعد\n\nلینتر bloc مجموعه‌ای در حال رشد از قواعد لینت را پشتیبانی می‌کند. توجه کنید که\nقواعد لینت لزوماً با هم سازگار نیستند. برای مثال بعضی از توسعه‌دهندگان ممکن است\nترجیح دهند از بلوک‌ها (blocs) استفاده کنند (`prefer_bloc`) در حالی که دیگران\nممکن است cubit را ترجیح دهند (`prefer_cubit`).\n\n:::note\n\nبرخلاف تحلیل ایستا، قواعد لینت ممکن است مثبت کاذب (false positive) تولید کنند.\nدر صورت مشاهدهٔ چنین مواردی یا هر مشکل دیگر، لطفاً با باز کردن یک\n[issue](https://github.com/felangel/bloc/issues/new/choose) ما را مطلع کنید.\n\n:::\n\n### فعال‌سازی قواعد پیشنهادی\n\nکتابخانهٔ bloc مجموعه‌ای از قواعد پیشنهادی لینت را در بستهٔ\n[`bloc_lint`](https://pub.dev/packages/bloc_lint) ارائه می‌دهد.\n\nبرای فعال‌سازی مجموعهٔ پیشنهادی، پکیج `bloc_lint` را به‌عنوان یک وابستگی dev\nاضافه کنید:\n\n<InstallBlocLintSnippet />\n\nسپس `analysis_options.yaml` خود را ویرایش کنید تا مجموعهٔ قواعد را وارد کنید:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nوقتی نسخهٔ جدیدی از `bloc_lint` منتشر شود، ممکن است کدی که قبلاً از تحلیل ایستا\nعبور می‌کرد اکنون با قواعد جدید مشکل پیدا کند. توصیه می‌کنیم کد خود را برای\nسازگاری با قواعد جدید به‌روزرسانی کنید؛ یا در صورت نیاز می‌توانید به صورت\nاختیاری برخی قواعد را فعال یا غیرفعال کنید.\n\n:::\n\n### فعال‌سازی قواعد به‌صورت تکی\n\nبرای فعال‌سازی قواعد به‌صورت تکی، کلید `bloc:` را به‌عنوان کلید سطح بالا در فایل\n`analysis_options.yaml` اضافه کنید و `rules:` را به‌عنوان کلید سطح دوم قرار\nدهید. در خطوط بعدی، قواعد موردنظر را به‌صورت یک لیست YAML (با پیشوند خط تیره)\nمشخص کنید.\n\nبرای مثال:\n\n<BlocLintEnablingRulesSnippet />\n\n### غیرفعال کردن قواعد به‌صورت تکی\n\nاگر مجموعهٔ قواعد موجودی مانند مجموعهٔ `recommended` را وارد کرده‌اید، ممکن است\nبخواهید یک یا چند قاعدهٔ واردشده را غیرفعال کنید. غیرفعال‌سازی قواعد مشابه\nفعال‌سازی است، اما به‌جای لیست از یک نقشهٔ YAML استفاده می‌شود.\n\nبرای مثال، نمونهٔ زیر مجموعهٔ قواعد پیشنهادی را (recommended) وارد می‌کند اما\nقاعدهٔ `avoid_public_bloc_methods` را حذف می‌کند و در عوض قاعدهٔ `prefer_bloc`\nرا فعال می‌نماید:\n\n<BlocLintDisablingRulesSnippet />\n\n## سفارشی‌سازی شدت قواعد\n\nمی‌توانید شدت هر قاعده را به این صورت تغییر دهید:\n\n<BlocLintChangingSeveritySnippet />\n\nدر این حالت همان قاعدهٔ لینت با شدت `info` به‌جای `warning` گزارش خواهد شد:\n\n<ImportFlutterInfoSnippet />\n\nخروجی دستور `bloc lint` باید چیزی شبیه زیر باشد:\n\n<ImportFlutterInfoOutputSnippet />\n\nگزینه‌های سطح شدتِ پشتیبانی‌شده عبارت‌اند از:\n\n| شدت       | توضیحات                                                   |\n| --------- | --------------------------------------------------------- |\n| `error`   | نشان می‌دهد که این الگو اجازه‌پذیر نیست.                  |\n| `warning` | نشان می‌دهد که الگو مشکوک است اما قابل‌قبول است.          |\n| `info`    | اطلاعاتی به کاربر می‌دهد اما مشکل محسوب نمی‌شود.          |\n| `hint`    | راهی بهتر یا بهینه‌تر برای رسیدن به نتیجه پیشنهاد می‌کند. |\n\n## استثنا کردن فایل‌ها\n\nگاهی اوقات پذیرفتنی است که تحلیل ایستا شکست بخورد. برای مثال ممکن است بخواهید\nهشدارها یا خطاهایی که در کد تولیدشده (generated code) گزارش شده و توسط شما نوشته\nنشده را نادیده بگیرید. همانند قواعد رسمی Dart lint، می‌توانید از گزینهٔ\n`exclude:` در analyzer برای استثنا کردن فایل‌ها از تحلیل ایستا استفاده کنید.\n\nمی‌توانید نام فایل‌های جداگانه را لیست کنید یا از الگوهای\n[`glob`](https://pub.dev/packages/glob) استفاده نمایید.\n\n:::note\n\nتمام استفاده‌ها از الگوهای glob باید نسبت به پوشه‌ای باشد که فایل\n`analysis_options.yaml` مربوطه در آن قرار دارد.\n\n:::\n\nبرای مثال، می‌توانیم تمام کد Dart تولیدشده را با استفاده از گزینه‌های زیر از\nتحلیل مستثنی کنیم:\n\n<BlocLintExcludingFilesSnippet />\n\n## نادیده گرفتن قواعد\n\nمشابه قواعد رسمی Dart lint، می‌توانید قواعد bloc lint را برای یک فایل یا یک خط\nخاص با استفاده از `// ignore_for_file` و `// ignore` نادیده بگیرید.\n\n:::note\n\nبرای نادیده گرفتن چند قاعده برای یک خط یا فایل، فهرستی از قواعد را با کاما جدا\nکنید.\n\n:::\n\n### نادیده گرفتن خطوط\n\nمی‌توانیم وقوع‌های خاص نقض قواعد را با افزودن یک کامنت `ignore` درست بالای خط\nمشکل‌ساز یا الحاق آن به انتهای همان خط نادیده بگیریم.\n\nبرای مثال، می‌توانیم موارد خاص `prefer_file_naming_conventions` را در یک فایل\nمشخص نادیده بگیریم:\n\n<BlocLintIgnoreForLineSnippet />\n\n### نادیده گرفتن فایل‌ها\n\nمی‌توانیم تمام وقوع‌های نقض قواعد داخل یک فایل را با افزودن کامنت\n`ignore_for_file` در هر نقطه از فایل نادیده بگیریم.\n\nبرای مثال، می‌توانیم تمام وقوع‌های `prefer_file_naming_conventions` را در یک\nفایل مشخص نادیده بگیریم:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint/index.mdx",
    "content": "---\ntitle: مروری بر لینتر\ndescription: معرفی لینتر bloc.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nلینتینگ فرایند تحلیل ایستأ کد برای شناسایی باگ‌ها و خطاهای منطقی و نگارشی است.\n\nBloc دارای یک لینتر داخلی است که می‌توان از طریق IDE یا ابزار خط فرمان\n[`bloc command-line tools`](https://pub.dev/packages/bloc_tools) با دستور\n`bloc lint` از آن استفاده کرد.\n\nبا کمک لینتر bloc می‌توانید کیفیت کد و یک‌دستی (consistency) در کدبیس خود را\nبدون اجرای هیچ‌کدام از خطوط کد بهبود دهید.\n\nبرای مثال، ممکن است به‌صورت سهوی یک وابستگی فلاتر را در cubit خود وارد کرده\nباشید:\n\n<AvoidFlutterImportsWarningSnippet />\n\nدر صورت پیکربندی صحیح، لینتر bloc به آن import اشاره کرده و هشدار زیر را تولید\nخواهد کرد:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nدر بخش‌های بعدی، نحوه نصب، پیکربندی و سفارشی‌سازی لینتر bloc را بررسی خواهیم کرد\nتا بتوانید از تحلیل ایستا در کد خود بهره ببرید.\n\n## شروع سریع\n\nبا چند گام ساده می‌توانید استفاده از لینتر bloc را آغاز کنید.\n\n:::note\n\nبرای شروع کار با bloc باید [Dart SDK](https://dart.dev/get-dart) را روی ماشین\nخود نصب داشته باشید.\n\n:::\n\n1. ابزارهای خط فرمان [bloc](https://pub.dev/packages/bloc_tools) را نصب کنید\n\n   <InstallBlocToolsSnippet />\n\n1. پکیج [bloc_lint](https://pub.dev/packages/bloc_lint) را نصب کنید\n\n   <InstallBlocLintSnippet />\n\n1. یک فایل `analysis_options.yaml` به ریشه پروژه اضافه کنید و قوانین پیشنهادی را\n   قرار دهید\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. لینتر را اجرا کنید\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nتمام شد 🎉\n\nبرای بررسی عمیق‌تر در مورد پیکربندی و سفارشی‌سازی لینتر bloc، ادامه مطلب را\nمطالعه کنید.\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint/installation.mdx",
    "content": "---\ntitle: نصب لینتر\ndescription: نصب لینتر bloc.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## ابزارهای خط فرمان\n\nبرای استفاده از لینتر از خط فرمان، پکیج\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) را با دستور زیر نصب\nکنید:\n\n<InstallBlocToolsSnippet />\n\nپس از نصب ابزارهای خط فرمان bloc، می‌توانید لینتر bloc را با دستور `bloc lint`\nاجرا کنید:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## مجموعه قوانین پیشنهادی\n\nبرای نصب مجموعه قوانین لینت پیشنهادی، پکیج\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) را به‌عنوان یک وابستگی\ndev با دستور زیر نصب کنید:\n\n<InstallBlocLintSnippet />\n\nسپس یک فایل `analysis_options.yaml` در ریشهٔ پروژه اضافه کنید و مجموعه قوانین\nپیشنهادی را در آن قرار دهید:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nدر صورت نیاز می‌توانید چند مجموعه قانون را با تعریف آن‌ها به‌صورت یک لیست اضافه\nکنید:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## ادغام با IDE\n\nIDEهای زیر به‌صورت رسمی از لینتر bloc و language server آن پشتیبانی می‌کنند تا\nتشخیص‌های آنی (diagnostics) را مستقیماً درون محیط توسعه‌تان نمایش دهند.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tپشتیبانی از [Bloc VSCode\n\t\tExtension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tاز نسخهٔ v6.8.0 در دسترس است.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tپشتیبانی از [Bloc IntelliJ\n\t\tPlugin](https://plugins.jetbrains.com/plugin/12129-bloc) از نسخهٔ v4.1.0 در\n\t\tدسترس است.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: از اکستنشن‌های BuildContext اجتناب کنید\ndescription: قانون avoid_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nاز استفاده از اکستنشن‌های `BuildContext` برای دسترسی به نمونه‌های `Bloc` یا\n`Cubit` خودداری کنید.\n\n:::note\n\nاین قانون lint در نسخهٔ `0.3.0` از\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) معرفی شده است.\n\n:::\n\n## دلیل\n\nبرای یکپارچگی و برای صراحت بیشتر، به‌جای استفاده از اکستنشن‌های `BuildContext`\nمستقیم از روش‌های زیرین استفاده کنید. این رویکرد برای تست نیز مفید است، زیرا\nامکان موک کردن یک متد اکستنشن وجود ندارد.\n\n| اکستنشن          | روش صریح                                                            |\n| ---------------- | ------------------------------------------------------------------- |\n| `context.read`   | `BlocProvider.of<Bloc>(context, listen: false)`                     |\n| `context.watch`  | `BlocBuilder<Bloc, State>(...)` یا `BlocProvider.of<Bloc>(context)` |\n| `context.select` | `BlocSelector<Bloc, State>(...)`                                    |\n\n## مثال‌ها\n\nاز استفاده از اکستنشن‌های `BuildContext` برای تعامل با نمونه‌های `Bloc` یا\n`Cubit` خودداری کنید.\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `avoid_build_context_extensions`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: از وارد کردن Flutter خودداری کنید\ndescription: قانون lint avoid_flutter_imports.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nاز وارد کردن وابستگی به فلاتر داخل کامپوننت‌های لایهٔ منطق کسب‌وکار (نمونه‌های\n`Bloc` یا `Cubit`) خودداری کنید.\n\n## دلیل\n\nلایه‌بندی یک برنامه بخش مهمی از ساخت یک کدبیس قابل نگهداری است و به\nتوسعه‌دهندگان کمک می‌کند تا سریع و با اطمینان تغییرات را اعمال کنند. هر لایه\nباید مسئولیت مجزایی داشته باشد و قادر باشد به‌طور جداگانه کار کند و تست شود. این\nامکان را فراهم می‌کند تا تغییرات را در لایه‌های مشخص محدود کنید و تأثیر آن‌ها بر\nکل برنامه را کاهش دهید.\n\nبنابراین، کامپوننت‌های منطق کسب‌وکار معمولاً باید مدیریت وضعیت ویژگی‌ها را\nبه‌عهده داشته باشند و از لایهٔ رابط کاربری جدا نگه داشته شوند. رویدادها باید از\nلایهٔ رابط کاربری وارد کامپوننت‌های منطق کسب‌وکار شوند و وضعیت از لایهٔ منطق\nکسب‌وکار به لایهٔ رابط کاربری جریان یابد.\n\nجدا نگه داشتن منطق کسب‌وکار از فلاتر این امکان را می‌دهد که منطق کسب‌وکار را در\nپلتفرم‌ها یا فریم‌ورک‌های مختلف دوباره استفاده کنید (مثلاً Flutter، AngularDart،\nJaspr و غیره).\n\n## مثال‌ها\n\n**از وارد کردن Flutter در کامپوننت‌های منطق کسب‌وکار خود خودداری کنید.**\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `avoid_flutter_imports`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: از متدهای عمومی Bloc خودداری کنید\ndescription: قانون avoid_public_bloc_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nاز در معرض قرار دادن متدهای عمومی در نمونه‌های `Bloc` خودداری کنید.\n\n## دلیل\n\nBlocها به رویدادهای ورودی واکنش نشان می‌دهند و وضعیت‌های خروجی را منتشر می‌کنند.\nبنابراین روش پیشنهادی برای ارتباط با یک نمونهٔ bloc استفاده از متد `add` است. در\nاکثر موارد نیازی به ایجاد انتزاع‌های اضافی روی API `add` نیست.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n## مثال‌ها\n\n**اجتناب کنید** از در معرض قرار دادن متدهای عمومی روی نمونه‌های bloc\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `avoid_public_bloc_methods`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: از فیلدهای عمومی خودداری کنید\ndescription: قانون avoid_public_fields.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nاز در معرض قرار دادن فیلدهای عمومی در نمونه‌های `Bloc` و `Cubit` خودداری کنید.\n\n## دلیل\n\nکامپوننت‌های منطق کسب‌وکار وضعیت `state` خود را مدیریت می‌کنند و تغییرات حالت را\nاز طریق API `emit` منتشر می‌کنند. بنابراین تمام وضعیت‌هایی که قصد دارید در دسترس\nعموم قرار گیرند باید از طریق شیء `state` در دسترس باشند.\n\n## مثال‌ها\n\n**اجتناب کنید:** از در معرض قرار دادن فیلدهای عمومی بر روی نمونه‌های bloc و\ncubit\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `avoid_public_fields`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: Bloc را ترجیح دهید\ndescription: قانون prefer_bloc.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nاستفاده از نمونه‌های `Bloc` را به‌جای نمونه‌های `Cubit` ترجیح دهید.\n\n## دلیل\n\nاین قانون صرفاً یک قاعدهٔ سبک است. در برخی موارد تیم‌ها ممکن است ترجیح دهند تا\nبرای حفظ یکپارچگی و سازگاری، در سراسر برنامه فقط از نمونه‌های `Bloc` استفاده\nکنند.\n\n:::tip\n\nبرای آشنایی بیشتر با مزایای `Bloc` به\n[Core Concepts](/fa/bloc-concepts/#مزایای-bloc) مراجعه کنید.\n\n:::\n\n## مثال‌ها\n\n**اجتناب کنید** از به‌کارگیری نمونه‌های `Cubit`\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `prefer_bloc`، آن را به فایل `analysis_options.yaml` خود\nتحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: اکستنشن‌های BuildContext را ترجیح دهید\ndescription: قانون prefer_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nترجیح دهید از اکستنشن‌های `BuildContext` برای دسترسی به یک نمونهٔ `Bloc` یا\n`Repository` استفاده کنید.\n\n:::note\n\nاین قانون lint در نسخهٔ `0.3.2` از\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) معرفی شده است.\n\n:::\n\n## دلیل\n\nبرای حفظ یکپارچگی، ترجیح دهید از اکستنشن‌های `BuildContext` مانند\n`context.read`، `context.watch` و `context.select` به‌جای `BlocProvider.of`،\n`RepositoryProvider.of`، `BlocBuilder` یا `BlocSelector` استفاده کنید.\n\n| روش صریح                                                            | اکستنشن               |\n| ------------------------------------------------------------------- | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                     | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` یا `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                    | `context.select`      |\n\n## مثال‌ها\n\n**اجتناب کنید** از استفاده از `BlocProvider.of<T>(context)` برای دسترسی به یک\nنمونهٔ `Bloc`\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `prefer_build_context_extensions`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: Cubit را ترجیح دهید\ndescription: قانون prefer_cubit.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nاستفاده از نمونه‌های `Cubit` را به‌جای نمونه‌های `Bloc` ترجیح دهید.\n\n## دلیل\n\nاین قانون صرفاً یک قاعدهٔ سبک است. در برخی موارد، تیم‌ها ممکن است ترجیح دهند\nبرای حفظ یکپارچگی و سازگاری، در سراسر برنامه فقط از نمونه‌های `Cubit` استفاده\nکنند.\n\n:::tip\n\nبرای آشنایی بیشتر با مزایای `Cubit` به\n[Core Concepts](/fa/bloc-concepts/#cubit-مزایای) مراجعه کنید.\n\n:::\n\n## مثال‌ها\n\n**اجتناب کنید** از به‌کارگیری نمونه‌های `Bloc`\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `prefer_cubit`، آن را به فایل `analysis_options.yaml` خود\nتحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: از قراردادهای نام‌گذاری فایل پیروی کنید\ndescription: قانون prefer_file_naming_conventions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nترجیح داده می‌شود از قراردادهای نام‌گذاری فایل پیروی کنید.\n\n:::note\n\nاین قانون lint در نسخهٔ `0.3.0` از\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) معرفی شده است\n\n:::\n\n## دلیل\n\nبرای حفظ یکپارچگی، سهولت نگهداری و جداسازی مسئولیت‌ها، ترجیح دهید نمونه‌های\n`bloc` و `cubit` را در فایل‌های Dart مربوط به خودشان تعریف کنید به‌جای آنکه\nآن‌ها را به‌صورت درون‌خطی قرار دهید.\n\n:::tip\n\nدر نظر داشته باشید از دستور `bloc new <component>` از\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) برای تولید سریع و\nیکنواخت نمونه‌های جدید bloc/cubit استفاده کنید.\n\n:::\n\n## مثال‌ها\n\n**ترجیح دهید** نمونه‌های bloc/cubit را در فایل‌های مربوطهٔ خودشان اعلام کنید.\n\n**خوب**:\n\n<GoodSnippet />\n\n**بد**:\n\n<BadSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `prefer_file_naming_conventions`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: ترجیح دهید متدهای عمومی Cubit از نوع void باشند\ndescription: قانون prefer_void_public_cubit_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nترجیح داده می‌شود متدهای عمومی در نمونه‌های `Cubit` از نوع `void` باشند.\n\n:::note\n\nاین قانون lint در نسخهٔ `0.2.0-dev.2` از\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) معرفی شده است\n\n:::\n\n## دلیل\n\nمتدهای عمومی روی نمونه‌های `Cubit` باید برای اطلاع‌رسانی به `Cubit` و آغاز\nتغییرات وضعیت از طریق متد `emit` استفاده شوند. اگر فراخواننده نیاز به دسترسی به\nاطلاعات وضعیت داشته باشد، باید آن را از طریق شیء `state` دریافت کند.\n\n:::note\n\nقوانین زیر مرتبط هستند و معمولاً همراه با `prefer_void_public_cubit_methods`\nفعال می‌شوند:\n\n- [`avoid_public_bloc_methods`](/fa/lint-rules/avoid_public_bloc_methods)\n- [`avoid_public_fields`](/fa/lint-rules/avoid_public_fields)\n\n:::\n\n## مثال‌ها\n\n**اجتناب کنید** از تعریف متدهای عمومی غیر-void روی نمونه‌های `Cubit`\n\n**بد**:\n\n<BadSnippet />\n\n**خوب**:\n\n<GoodSnippet />\n\n## فعال‌سازی\n\nبرای فعال کردن قانون `prefer_void_public_cubit_methods`، آن را به فایل\n`analysis_options.yaml` خود تحت `bloc` > `rules` اضافه کنید:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/fa/migration.mdx",
    "content": "---\ntitle: راهنمای مهاجرت\ndescription: مهاجرت به آخرین نسخه پایدار Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nلطفاً برای اطلاعات بیشتر در مورد آنچه در هر انتشار تغییر کرده، به\n[release log](https://github.com/felangel/bloc/releases) مراجعه کنید.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ جدا کردن `blocTest` از `BlocBase`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc_test v10.0.0، API `blocTest` دیگر به شدت به `BlocBase` متصل نیست.\n\n:::\n\n##### دلیل\n\n`blocTest` باید برای افزایش انعطاف پذیری و قابلیت استفاده مجدد تا حد امکان از\nرابط های اصلی bloc استفاده کند. قبلاً این امکان‌پذیر نبود زیرا `BlocBase`\n`StateStreamableSource` را پیاده‌سازی می‌کرد که برای `blocTest` کافی نبود به\nدلیل وابستگی داخلی به API `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ پشتیبانی از WebAssembly\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر hydrated_bloc v10.0.0، پشتیبانی از کامپایل به WebAssembly (wasm) اضافه شد.\n\n:::\n\n##### دلیل\n\nقبلاً امکان کامپایل برنامه‌ها به wasm هنگام استفاده از `hydrated_bloc` وجود\nنداشت. در v10.0.0، بسته بازسازی شد تا اجازه کامپایل به wasm را بدهد.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 حذف APIهای منسوخ شده\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v9.0.0، همه APIهای منسوخ شده قبلی حذف شدند.\n\n:::\n\n##### خلاصه\n\n- `BlocOverrides` حذف شد به نفع `Bloc.observer` و `Bloc.transformer`\n\n#### ❗✨ معرفی رابط جدید `EmittableStateStreamableSource`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v9.0.0، یک رابط اصلی جدید `EmittableStateStreamableSource` معرفی شد.\n\n:::\n\n##### دلیل\n\n`package:bloc_test` قبلاً به شدت به `BlocBase` متصل بود. رابط\n`EmittableStateStreamableSource` معرفی شد تا اجازه دهد `blocTest` از پیاده‌سازی\nپایه `BlocBase` جدا شود.\n\n### `package:hydrated_bloc`\n\n#### ✨ باز تعریف API `HydratedBloc.storage`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر hydrated_bloc v9.0.0، `HydratedBlocOverrides` حذف شد به نفع API\n`HydratedBloc.storage`.\n\n:::\n\n##### دلیل\n\nبه\n[دلیل باز تعریف overrides Bloc.observer و Bloc.transformer](/fa/migration#-باز-تعریف-apiهای-blocobserver-و-bloctransformer)\nمراجعه کنید.\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ باز تعریف APIهای `Bloc.observer` و `Bloc.transformer`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.1.0، `BlocOverrides` منسوخ شد به نفع APIهای `Bloc.observer` و\n`Bloc.transformer`.\n\n:::\n\n##### دلیل\n\nAPI `BlocOverrides` در v8.0.0 معرفی شد در تلاش برای پشتیبانی از scoping تنظیمات\nخاص bloc مانند `BlocObserver`, `EventTransformer`, و `HydratedStorage`. در\nبرنامه‌های دارت خالص، تغییرات خوب کار کردند؛ با این حال، در برنامه‌های فلاتر،\nAPI جدید مشکلات بیشتری نسبت به آنچه حل کرد ایجاد کرد.\n\nAPI `BlocOverrides` الهام گرفته از APIهای مشابه در Flutter/Dart بود:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**مشکلات**\n\nدر حالی که دلیل اصلی این تغییرات نبود، API `BlocOverrides` پیچیدگی اضافی برای\nتوسعه‌دهندگان معرفی کرد. علاوه بر افزایش مقدار nesting و خطوط کد مورد نیاز برای\nدستیابی به همان اثر، API `BlocOverrides` نیاز داشت توسعه‌دهندگان درک جامعی از\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) در زبان\nدارت داشته باشند. `Zones` مفهومی دوستانه برای مبتدیان نیست و شکست در درک نحوه\nکار Zones می‌تواند منجر به معرفی باگ شود (مانند observers، transformers،\nنمونه‌های storage غیر اولیه).\n\nبرای مثال، بسیاری از توسعه‌دهندگان چیزی مانند این داشتند:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nکد بالا، در حالی که بی‌ضرر به نظر می‌رسد، می‌تواند منجر به بسیاری از باگ‌های\nدشوار برای ردیابی شود. هر zone که `WidgetsFlutterBinding.ensureInitialized`\nابتدا از آن فراخوانی شود، zone خواهد بود که رویدادهای gesture در آن مدیریت\nمی‌شوند (مثلاً callbacks `onTap`, `onPressed`) به دلیل\n`GestureBinding.initInstances`. این فقط یکی از بسیاری از مسائل ناشی از استفاده\nاز `zoneValues` است.\n\nعلاوه بر این، فلاتر بسیاری از کارها را پشت صحنه انجام می‌دهد که شامل\nforking/manipulating Zones است (به ویژه هنگام اجرای تست‌ها) که می‌تواند منجر به\nرفتارهای غیرمنتظره شود (و در بسیاری از موارد رفتارهایی که خارج از کنترل\nتوسعه‌دهنده است -- مسائل زیر را ببینید).\n\nبه دلیل استفاده از\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), انتقال به\nAPI `BlocOverrides` منجر به کشف چندین باگ/محدودیت در فلاتر شد (به طور خاص در\nمورد Widget و Integration Tests):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nکه بسیاری از توسعه‌دهندگان استفاده‌کننده از کتابخانه bloc را تحت تأثیر قرار داد:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ معرفی API جدید `BlocOverrides`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، `Bloc.observer` و `Bloc.transformer` حذف شدند به نفع API\n`BlocOverrides`.\n\n:::\n\n##### دلیل\n\nAPI قبلی برای override کردن پیش‌فرض `BlocObserver` و `EventTransformer` به یک\nsingleton جهانی برای هر دو `BlocObserver` و `EventTransformer` متکی بود.\n\nدر نتیجه، امکان‌پذیر نبود:\n\n- داشتن چندین پیاده‌سازی `BlocObserver` یا `EventTransformer` scoped به بخش‌های\n  مختلف برنامه\n- داشتن overrides `BlocObserver` یا `EventTransformer` scoped به یک بسته\n  - اگر بسته‌ای به `package:bloc` وابسته بود و `BlocObserver` خود را ثبت می‌کرد،\n    هر مصرف‌کننده بسته یا باید `BlocObserver` بسته را overwrite می‌کرد یا به\n    `BlocObserver` بسته گزارش می‌کرد.\n\nهمچنین تست کردن دشوارتر بود به دلیل حالت جهانی مشترک در تست‌ها.\n\nBloc v8.0.0 کلاس `BlocOverrides` را معرفی می‌کند که به توسعه‌دهندگان اجازه\nمی‌دهد `BlocObserver` و/یا `EventTransformer` را برای یک `Zone` خاص override\nکنند به جای متکی بودن به یک singleton mutable جهانی.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nنمونه‌های `Bloc` از `BlocObserver` و/یا `EventTransformer` برای zone فعلی از\nطریق `BlocOverrides.current` استفاده خواهند کرد. اگر هیچ `BlocOverrides` برای\nzone وجود نداشته باشد، از پیش‌فرض‌های داخلی موجود استفاده خواهند کرد (هیچ تغییری\nدر رفتار/عملکرد).\n\nاین اجازه می‌دهد هر `Zone` مستقل با `BlocOverrides` خود عمل کند.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA and eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Blocs in this zone report to BlocObserverA\n    // and use eventTransformerA as the default transformer.\n    // ...\n\n    // Later...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB and eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Blocs in this zone report to BlocObserverB\n        // and use eventTransformerB as the default transformer.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ بهبود مدیریت خطا و گزارش\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، `BlocUnhandledErrorException` حذف شد. در عوض، هر استثنای catch\nنشده همیشه به `onError` گزارش می‌شود و rethrown می‌شود (بدون توجه به حالت debug\nیا release). API `addError` خطاها را به `onError` گزارش می‌دهد، اما خطاهای گزارش\nشده را به عنوان استثناهای catch نشده تلقی نمی‌کند.\n\n:::\n\n##### دلیل\n\nهدف این تغییرات:\n\n- استثناهای داخلی unhandled را بسیار واضح کند در حالی که عملکرد bloc را حفظ کند\n- پشتیبانی از `addError` بدون اختلال در جریان کنترل\n\nقبلاً، مدیریت خطا و گزارش بسته به اینکه برنامه در حالت debug یا release اجرا\nمی‌شد متفاوت بود. علاوه بر این، خطاهای گزارش شده از طریق `addError` در حالت\ndebug به عنوان استثناهای catch نشده تلقی می‌شدند که منجر به تجربه توسعه ضعیف\nهنگام استفاده از API `addError` می‌شد (به طور خاص هنگام نوشتن تست‌های واحد).\n\nدر v8.0.0، `addError` می‌تواند به شکل ایمن برای گزارش خطاها استفاده شود و\n`blocTest` می‌تواند برای تأیید گزارش خطاها استفاده شود. همه خطاها هنوز به\n`onError` گزارش می‌شوند، با این حال، فقط استثناهای catch نشده rethrown می‌شوند\n(بدون توجه به حالت debug یا release).\n\n#### ❗🧹 تبدیل `BlocObserver` به abstract\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، `BlocObserver` به یک کلاس `abstract` تبدیل شد که به معنای امکان\nنمونه‌سازی از `BlocObserver` نیست.\n\n:::\n\n##### دلیل\n\n`BlocObserver` قرار بود یک interface باشد. از آنجایی که پیاده‌سازی‌های API\nپیش‌فرض no-ops هستند، `BlocObserver` اکنون یک کلاس `abstract` است تا به وضوح\nارتباط دهد که کلاس قرار است extend شود نه مستقیماً نمونه‌سازی شود.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // امکان ایجاد نمونه از کلاس پایه وجود داشت.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // نمی‌توان نمونه از کلاس پایه ایجاد کرد.\n  final observer = BlocObserver(); // ERROR\n\n  // در عوض `BlocObserver` را extend کنید.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨متد `add` باعث ایجاد `StateError` میشود اگر Bloc بسته باشد\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، فراخوانی `add` روی یک bloc بسته منجر به `StateError` می‌شود.\n\n:::\n\n##### دلیل\n\nقبلاً امکان این وجود داشت که متد `add` را روی یک bloc بسته‌شده فراخوانی کنید و\nخطای داخلی نادیده گرفته می‌شد، که باعث می‌شد پیدا کردن دلیل پردازش نشدن رویداد\nاضافه‌شده سخت باشد. برای قابل‌مشاهده‌تر کردن این وضعیت، از نسخهٔ v8.0.0 به بعد،\nفراخوانی متد `add` روی یک bloc بسته‌شده باعث ایجاد شدن یک `StateError` می‌شود که\nبه‌عنوان یک استثنای گرفته‌نشده گزارش شده و به `onError` منتقل می‌گردد.\n\n#### ❗✨ `emit` باعث ایجاد `StateError` میشود اگر Bloc بسته باشد\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، فراخوانی `emit` در یک bloc بسته منجر به `StateError` می‌شود.\n\n:::\n\n##### دلیل\n\nقبلاً، امکان فراخوانی `emit` در یک bloc بسته وجود داشت و هیچ تغییر حالت رخ\nنمی‌داد اما هیچ نشانگری از آنچه اشتباه رفت وجود نداشت، که اشکال‌زدایی را دشوار\nمی‌کرد. برای اینکه این سناریو بیشتر قابل مشاهده باشد، در v8.0.0، فراخوانی `emit`\nدر یک bloc بسته `StateError` می‌اندازد که به عنوان یک استثنا catch نشده گزارش\nمی‌شود و به `onError` propagate می‌شود.\n\n#### ❗🧹 حذف APIهای منسوخ شده\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v8.0.0، همه APIهای منسوخ شده قبلی حذف شدند.\n\n:::\n\n##### خلاصه\n\n- `mapEventToState` حذف شد به نفع `on<Event>`\n- `transformEvents` حذف شد به نفع API `EventTransformer`\n- typedef `TransitionFunction` حذف شد به نفع API `EventTransformer`\n- `listen` حذف شد به نفع `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` و `MockCubit` دیگر نیاز به `registerFallbackValue` ندارند\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc_test v9.0.0، توسعه‌دهندگان دیگر نیازی به فراخوانی صریح\n`registerFallbackValue` هنگام استفاده از `MockBloc` یا `MockCubit` ندارند.\n\n:::\n\n##### خلاصه\n\n`registerFallbackValue` فقط زمانی نیاز است که از تطبیق دهنده `any()` از\n`package:mocktail` برای یک نوع سفارشی استفاده شود. قبلاً،\n`registerFallbackValue` برای هر `رویداد` و `حالت` هنگام استفاده از `MockBloc` یا\n`MockCubit` نیاز بود.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Tests...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Tests...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ معرفی API جدید `HydratedBlocOverrides`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر hydrated_bloc v8.0.0، `HydratedBloc.storage` حذف شد به نفع API\n`HydratedBlocOverrides`.\n\n:::\n\n##### دلیل\n\nقبلاً، یک singleton جهانی برای override پیاده‌سازی `Storage` استفاده می‌شد.\n\nدر نتیجه، امکان‌پذیر نبود داشتن چندین پیاده‌سازی `Storage` scoped به بخش‌های\nمختلف برنامه. همچنین تست کردن دشوارتر بود به دلیل حالت جهانی مشترک در تست‌ها.\n\n`HydratedBloc` v8.0.0 کلاس `HydratedBlocOverrides` را معرفی می‌کند که به\nتوسعه‌دهندگان اجازه می‌دهد `Storage` را برای یک `Zone` خاص override کنند به جای\nمتکی بودن به یک singleton قابل تغییر جهانی.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\nنمونه‌های `HydratedBloc` از `Storage` برای zone فعلی از طریق\n`HydratedBlocOverrides.current` استفاده خواهند کرد.\n\nاین اجازه می‌دهد هر `Zone` مستقل با `BlocOverrides` خود عمل کند.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ معرفی API جدید `on<Event>`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v7.2.0، `mapEventToState` منسوخ شد به نفع `on<Event>`. `mapEventToState`\nدر bloc v8.0.0 حذف خواهد شد.\n\n:::\n\n##### دلیل\n\nAPI `on<Event>` به عنوان بخشی از\n[[Proposal] Replace mapEventToState with on\\<Event\\> in Bloc](https://github.com/felangel/bloc/issues/2526)\nمعرفی شد. به دلیل\n[یک مشکل در زبان دارت](https://github.com/dart-lang/sdk/issues/44616) همیشه واضح\nنیست که مقدار `state` چه خواهد بود هنگام برخورد با async generators nested\n(`async*`). حتی اگر راه‌هایی برای کار کردن با مسئله وجود دارد، یکی از اصول اصلی\nکتابخانه bloc قابل پیش‌بینی بودن است. API `on<Event>` ایجاد شد تا کتابخانه را تا\nحد امکان ایمن برای استفاده کند و هر عدم قطعیتی در مورد تغییرات حالت را از بین\nببرد.\n\n:::tip\n\nبرای اطلاعات بیشتر،\n[پیشنهاد کامل را بخوانید](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**خلاصه**\n\n`on<E>` به شما اجازه می‌دهد تا یک هندلر رویداد برای تمام رویدادهای نوع `E` ثبت\nکنید. به طور پیش‌فرض، رویدادها هنگام استفاده از `on<E>` به طور همزمان پردازش\nمی‌شوند در حالی که `mapEventToState` که رویدادها را به صورت `سریالی` پردازش\nمی‌کند.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nهر تابع ثبت شده `EventHandler` به طور مستقل عمل می‌کند بنابراین مهم است که\nهندلرهای رویداد را بر اساس نوع ترنسفورمری که می‌خواهید اعمال شود ثبت کنید.\n\n:::\n\nاگر می‌خواهید همان رفتار دقیق را مانند v7.1.0 حفظ کنید می‌توانید یک هندلر رویداد\nواحد برای تمام رویدادها ثبت کنید و یک ترنسفورمر `سریالی` اعمال کنید:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: logic goes here...\n  }\n}\n```\n\nشما همچنین می‌توانید ترنسفورمر پیش‌فرض `EventTransformer` را برای تمام blocs در\nبرنامه خود override کنید:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ معرفی API جدید `EventTransformer`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v7.2.0، `transformEvents` منسوخ شد به نفع API `EventTransformer`.\n`transformEvents` در bloc v8.0.0 حذف خواهد شد.\n\n:::\n\n##### دلیل\n\nAPI `on<Event>` این امکان را فراهم کرد که بتوانید یک ترنسفورمر رویداد سفارشی\nبرای هر هندلر رویداد فراهم کنید. یک typedef جدید به نام `EventTransformer` معرفی\nشد که به توسعه‌دهندگان این امکان را می‌دهد که جریان ورودی رویدادها را برای هر\nهندلر رویداد تغییر دهند به جای اینکه مجبور باشند یک ترنسفورمر رویداد واحد برای\nتمام رویدادها مشخص کنند.\n\n**خلاصه**\n\nیک `EventTransformer` مسئول دریافت جریان ورودی رویدادها به همراه یک\n`EventMapper` (هندلر رویداد شما) و بازگرداندن یک جریان جدید رویدادها است.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nپیش‌فرض `EventTransformer` تمام رویدادها را به طور همزمان پردازش می‌کند و چیزی\nشبیه به این دارد:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nبرای مجموعه‌ای از ترنسفورمرهای رویداد سفارشی، به\n[package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) مراجعه\nکنید\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// تعریف یک `EventTransformer` سفارشی\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// اعمال `EventTransformer` سفارشی بر روی `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ منسوخ کردن API `transformTransitions`\n\n:::note[چه چیزی تغییر کرد؟]\n\nدر bloc v7.2.0، `transformTransitions` منسوخ شد به نفع override کردن API\n`stream`. `transformTransitions` در bloc v8.0.0 حذف خواهد شد.\n\n:::\n\n##### دلیل\n\ngetter `stream` در `Bloc` این امکان را می‌دهد که جریان خروجی حالات را به راحتی\noverride کنید، بنابراین دیگر ارزشمند نیست که یک API جداگانه\n`transformTransitions` نگه‌داری شود.\n\n**خلاصه**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc و Cubit از BlocBase ارث‌بری می‌کنند\n\n##### دلیل\n\nبه‌عنوان یک توسعه‌دهنده، رابطهٔ بین bloc ها و cubit ها کمی نامتعارف و عجیب بود.\nوقتی Cubit برای اولین بار معرفی شد، به‌عنوان کلاس پایه برای Bloc ها در نظر گرفته\nشد که منطقی به نظر می‌رسید، چون Cubit فقط زیرمجموعه‌ای از قابلیت‌ها را داشت و\nBloc ها صرفاً Cubit را گسترش می‌دادند و API های اضافی را تعریف می‌کردند. اما این\nرویکرد چند ایراد به همراه داشت:\n\n- یا باید تمام API ها تغییر نام داده می‌شدند تا از نظر مفهومی با Cubit سازگار\n  باشند، یا برای حفظ یکپارچگی، نام Bloc روی آن‌ها باقی می‌ماند، با اینکه از نظر\n  ساختار سلسله‌مراتبی نادرست بود\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- Cubit مجبور بود از Stream ارث‌بری کند و EventSink را پیاده‌سازی کند تا یک\n  پایهٔ مشترک داشته باشیم که ویجت‌هایی مثل BlocBuilder، BlocListener و غیره\n  بتوانند بر اساس آن پیاده‌سازی شوند\n  ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nبعدتر، ما رابطه را معکوس کردیم و Bloc را به‌عنوان کلاس پایه در نظر گرفتیم که تا\nحدی مشکل مورد اول را حل کرد، اما مسائل جدیدی را به وجود آورد:\n\n- API های Cubit به دلیل وجود API های داخلی Bloc مثل mapEventToState، add و غیره\n  بیش‌ازحد حجیم و شلوغ شد\n  ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - از نظر فنی، توسعه‌دهندگان می‌توانستند این API ها را فراخوانی کنند و باعث\n    بروز مشکلات ناخواسته شوند\n- همچنان همان مشکل قبلی وجود داشت و Cubit کل API مربوط به Stream را در معرض\n  دسترس قرار می‌داد ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nبرای حل این مشکلات، ما یک کلاس پایهٔ مشترک برای هر دو `Bloc` و `Cubit` با نام\n`BlocBase` معرفی کردیم تا اجزای بالادستی همچنان بتوانند با هر دو نمونهٔ bloc و\ncubit کار کنند، اما بدون اینکه کل API های `Stream` و `EventSink` به‌صورت مستقیم\nدر معرض دسترس قرار بگیرند.\n\n**خلاصه**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed یک تابع برای پشتیبانی از مقادیر پویا برمی‌گرداند\n\n##### دلیل\n\nبرای پشتیبانی از داشتن یک مقدار seed قابل تغییر که می‌تواند به طور پویا در\n`setUp` به‌روزرسانی شود، `seed` یک تابع برمی‌گرداند.\n\n**خلاصه**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect یک تابع برای پشتیبانی از مقادیر پویا و شامل پشتیبانی از تطبیق دهنده برمی‌گرداند\n\n##### دلیل\n\nبرای پشتیبانی از داشتن یک انتظار قابل تغییر که می‌تواند به طور پویا در `setUp`\nبه‌روزرسانی شود، `expect` یک تابع برمی‌گرداند. `expect` همچنین از `تطبیق دهنده`\nپشتیبانی می‌کند.\n\n**خلاصه**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// همچنین می‌تواند یک `تطبیق دهنده` باشد\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors یک تابع برای پشتیبانی از مقادیر پویا و شامل پشتیبانی از تطبیق دهنده برمی‌گرداند\n\n##### دلیل\n\nبرای پشتیبانی از داشتن خطاهای قابل تغییر که می‌توانند به طور پویا در `setUp`\nبه‌روزرسانی شوند، `errors` یک تابع برمی‌گرداند. `errors` همچنین از `تطبیق دهنده`\nپشتیبانی می‌کند.\n\n**خلاصه**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// همچنین می‌تواند یک `تطبیق دهنده` باشد\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc و MockCubit\n\n##### دلیل\n\nبرای پشتیبانی از شبیه سازی کردن APIهای مختلف هسته، `MockBloc` و `MockCubit` به\nعنوان بخشی از بسته `bloc_test` ارائه شده اند. قبلاً، `MockBloc` باید برای هر دو\nنمونه `Bloc` و `Cubit` استفاده می‌شد که شهودی نبود.\n\n**خلاصه**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗ادغام با Mocktail\n\n##### دلیل\n\nبه دلیل محدودیت‌های مختلف null-safe\n[package:mockito](https://pub.dev/packages/mockito) توصیف شده\n[اینجا](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) توسط `MockBloc` و\n`MockCubit` استفاده می‌شود. این به توسعه‌دهندگان اجازه می‌دهد تا بدون نیاز به\nنوشتن دستی stubs یا وابستگی به تولید کد، از یک API mocking آشنا استفاده کنند.\n\n**خلاصه**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> لطفاً برای اطلاعات بیشتر به\n> [#347](https://github.com/dart-lang/mockito/issues/347) و همچنین\n> [مستندات mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> مراجعه کنید.\n\n### `package:flutter_bloc`\n\n#### ❗ تغییر نام پارامتر `cubit` به `bloc`\n\n##### دلیل\n\nبه عنوان نتیجه بازسازی در `package:bloc` برای معرفی `BlocBase` که `Bloc` و\n`Cubit` از آن ارث‌بری می‌کنند، پارامترهای `BlocBuilder`، `BlocConsumer` و\n`BlocListener` از `cubit` به `bloc` تغییر نام یافتند زیرا ویجت‌ها بر روی نوع\n`BlocBase` عمل می‌کنند. این همچنین بیشتر با نام کتابخانه هماهنگ می‌شود و\nامیدواریم خوانایی را بهبود بخشد.\n\n**خلاصه**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory هنگام فراخوانی HydratedStorage.build الزامی است\n\n##### دلیل\n\nبه منظور تبدیل `package:hydrated_bloc` به یک بسته خالص زبان دارت، وابستگی به\n[package:path_provider](https://pub.dev/packages/path_provider) حذف شد و پارامتر\n`storageDirectory` هنگام فراخوانی `HydratedStorage.build` الزامی است و دیگر به\n`getTemporaryDirectory` پیش‌فرض نیست.\n\n**خلاصه**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc و context.repository به نفع context.read و context.watch منسوخ شده‌اند\n\n##### دلیل\n\n`context.read`، `context.watch` و `context.select` برای هماهنگی با API موجود\n[provider](https://pub.dev/packages/provider) که بسیاری از توسعه‌دهندگان با آن\nآشنا هستند و برای رفع مسائلی که توسط جامعه مطرح شده بود، اضافه شدند. به منظور\nبهبود ایمنی کد و حفظ سازگاری، `context.bloc` به دلیل اینکه می‌تواند با یکی از\n`context.read` یا `context.watch` بسته به اینکه آیا مستقیماً درون `build`\nاستفاده می‌شود، جایگزین شد.\n\n**context.watch**\n\n`context.watch` به درخواست داشتن یک\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) پاسخ می‌دهد زیرا\nمی‌توانیم چندین bloc را درون یک `Builder` واحد مشاهده کنیم تا UI را بر اساس\nچندین حالت رندر کنیم:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` به توسعه‌دهندگان این امکان را می‌دهد که UI را بر اساس یک بخش از\nحالت bloc رندر/به‌روزرسانی کنند و به درخواست داشتن یک\n[simpler buildWhen](https://github.com/felangel/bloc/issues/1521) پاسخ می‌دهد.\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nقطعه کد بالا به ما این امکان را می‌دهد که به کاربر فعلی دسترسی پیدا کنیم و فقط\nزمانی که نام کاربر تغییر می‌کند، ویجت را دوباره بسازیم.\n\n**context.read**\n\nاگرچه به نظر می‌رسد `context.read` با `context.bloc` یکسان است، اما تفاوت‌های\nظریف اما قابل توجهی وجود دارد. هر دو به شما اجازه می‌دهند تا با یک\n`BuildContext` به یک bloc دسترسی پیدا کنید و منجر به rebuild نشوند؛ با این حال،\n`context.read` نمی‌تواند مستقیماً درون یک متد `build` فراخوانی شود. دو دلیل اصلی\nبرای استفاده از `context.bloc` درون `build` وجود دارد:\n\n1. **برای دسترسی به حالت bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nاستفاده از کد بالا خطازا است زیرا ویجت `Text` دوباره ساخته نخواهد شد اگر حالت\nbloc تغییر کند. در این سناریو، باید از `BlocBuilder` یا `context.watch` استفاده\nکنید.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nیا\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nاستفاده از `context.watch` در ریشه متد `build` منجر به این می‌شود که کل ویجت\nهنگام تغییر حالت bloc دوباره ساخته شود. اگر کل ویجت نیاز به بازسازی ندارد، از\n`BlocBuilder` برای احاطه کردن بخش‌هایی که باید دوباره بسازند، استفاده کنید، از\n`Builder` با `context.watch` برای محدود کردن بازسازی‌ها استفاده کنید، یا ویجت را\nبه ویجت‌های کوچکتر تقسیم کنید.\n\n:::\n\n2. **برای دسترسی به bloc به طوری که یک رویداد بتواند اضافه شود**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nاستفاده از کد بالا ناکارآمد است زیرا منجر به جستجوی bloc در هر بار بازسازی\nمی‌شود در حالی که bloc فقط زمانی که کاربر دکمه `ElevatedButton` را فشار می‌دهد،\nمورد نیاز است. در این سناریو، بهتر است از `context.read` برای دسترسی به bloc به\nطور مستقیم در جایی که مورد نیاز است استفاده کنید (در این مورد، در callback\n`onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**خلاصه**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\nاگر به یک bloc برای اضافه کردن یک رویداد دسترسی پیدا می‌کنید، دسترسی به bloc را\nبا استفاده از `context.read` در callback جایی که مورد نیاز است، انجام دهید.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nهنگام دسترسی به حالت bloc از `context.watch` استفاده کنید تا اطمینان حاصل شود که\nویجت هنگام تغییر حالت دوباره ساخته می‌شود.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗متد BlocObserver.onError، Cubit را دریافت می‌کند\n\n##### دلیل\n\nبه دلیل ادغام `Cubit`، `onError` اکنون بین `Bloc` و `Cubit` مشترک است. از آنجایی\nکه `Cubit` پایه است، `BlocObserver` نوع `Cubit` را به جای نوع `Bloc` در override\n`onError` خواهد پذیرفت.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc هنگام اشتراک (subscription) آخرین حالت را منتشر نمی‌کند\n\n##### دلیل\n\nاین تغییر به منظور همسان‌سازی `Bloc` و `Cubit` با رفتار داخلی `Stream` در\n`زبان دارت` انجام شد. علاوه بر این، تطبیق این تغییر در زمینه `Cubit` منجر به\nبسیاری از عوارض جانبی ناخواسته و به طور کلی پیاده‌سازی داخلی سایر بسته‌ها مانند\n`flutter_bloc` و `bloc_test` را پیچیده‌تر کرد (نیاز به `skip(1)` و غیره...).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nقبلاً، قطعه کد بالا وضعیت اولیه bloc را به همراه تغییرات بعدی حالت‌ها چاپ\nمی‌کرد.\n\n**v6.x.x**\n\nدر v6.0.0، قطعه کد بالا وضعیت اولیه را چاپ نمی‌کند و فقط تغییرات بعدی حالت‌ها را\nچاپ می‌کند. رفتار قبلی را می‌توان با کد زیر به دست آورد:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n**توجه**: این تغییر فقط بر روی کدهایی که به اشتراک‌گذاری مستقیم bloc وابسته‌اند\nتأثیر خواهد گذاشت. هنگام استفاده از `BlocBuilder`، `BlocListener` یا\n`BlocConsumer` تغییر قابل توجهی در رفتار وجود نخواهد داشت.\n\n### `package:bloc_test`\n\n#### ❗MockBloc فقط نیاز به نوع State دارد\n\n##### دلیل\n\nاین غیرضروری است و کد اضافی را حذف می‌کند در حالی که همچنین `MockBloc` را با\n`Cubit` سازگار می‌کند.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen فقط نیاز به نوع State دارد\n\n##### دلیل\n\nاین غیرضروری است و کد اضافی را حذف می‌کند در حالی که همچنین `whenListen` را با\n`Cubit` سازگار می‌کند.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest به نوع Event نیاز ندارد\n\n##### دلیل\n\nاین غیرضروری است و کد اضافی را حذف می‌کند در حالی که همچنین `blocTest` را با\n`Cubit` سازگار می‌کند.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip به طور پیش‌فرض به 0 تنظیم شده است\n\n##### دلیل\n\nاز آنجایی که نمونه‌های `bloc` و `cubit` دیگر آخرین حالت را برای اشتراک‌گذاری‌های\nجدید منتشر نمی‌کنند، دیگر لازم نبود که `skip` به طور پیش‌فرض به `1` تنظیم شود.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nوضعیت اولیه یک bloc یا cubit را می‌توان با کد زیر تست کرد:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest ساخت را همزمان می‌کند\n\n##### دلیل\n\nقبلاً، `build` به صورت `async` تنظیم شده بود تا آماده‌سازی‌های مختلفی انجام شود\nتا bloc تحت تست در یک حالت خاص قرار گیرد. دیگر لازم نیست و همچنین چندین مشکل را\nبه دلیل تأخیر اضافی بین ساخت و اشتراک‌گذاری داخلی حل می‌کند. به جای انجام\nآماده‌سازی async برای قرار دادن یک bloc در حالت دلخواه، اکنون می‌توانیم وضعیت\nbloc را با زنجیره‌کردن `emit` با وضعیت دلخواه تنظیم کنیم.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` فقط برای تست قابل مشاهده است و هرگز نباید در خارج از تست‌ها استفاده شود.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder پارامتر bloc به cubit تغییر نام داد\n\n##### دلیل\n\nبه منظور هماهنگ‌سازی `BlocBuilder` با نمونه‌های `bloc` و `cubit`، پارامتر `bloc`\nبه `cubit` تغییر نام یافت (زیرا `Cubit` کلاس پایه است).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener پارامتر bloc به cubit تغییر نام داد\n\n##### دلیل\n\nبه منظور هماهنگ‌سازی `BlocListener` با نمونه‌های `bloc` و `cubit`، پارامتر\n`bloc` به `cubit` تغییر نام یافت (زیرا `Cubit` کلاس پایه است).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗BlocConsumer پارامتر bloc به cubit تغییر نام داد\n\n##### دلیل\n\nبه منظور هماهنگ‌سازی `BlocConsumer` با نمونه‌های `bloc` و `cubit`، پارامتر\n`bloc` به `cubit` تغییر نام یافت (زیرا `Cubit` کلاس پایه است).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState حذف شده است\n\n##### دلیل\n\nبه عنوان یک توسعه‌دهنده، مجبور بودن به override کردن `initialState` هنگام ایجاد\nیک bloc دو مشکل اصلی دارد:\n\n- `initialState` bloc می‌تواند پویا باشد و می‌تواند در زمان بعدی (حتی خارج از\n  خود bloc) به آن ارجاع داده شود. از برخی جهات، این می‌تواند به عنوان نشت\n  اطلاعات داخلی bloc به لایه UI مشاهده شود.\n- این بسیار verbose است.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\nبرای اطلاعات بیشتر به [#1304](https://github.com/felangel/bloc/issues/1304)\nمراجعه کنید.\n\n#### ❗BlocDelegate به BlocObserver تغییر نام یافت\n\n##### دلیل\n\nنام `BlocDelegate` توصیف دقیقی از نقشی که کلاس ایفا می‌کند، نبود. `BlocDelegate`\nنشان می‌دهد که کلاس نقش فعالی ایفا می‌کند در حالی که در واقع هدف از\n`BlocDelegate` این بود که یک مؤلفه غیرفعال باشد که به سادگی تمام blocs در یک\nبرنامه را مشاهده می‌کند.\n\n:::note\n\nبه طور ایده‌آل، باید هیچ عملکرد یا ویژگی کاربرپسند در `BlocObserver` مدیریت\nنشود.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor حذف شده است\n\n##### دلیل\n\n`BlocSupervisor` هنوز یک مؤلفه دیگر بود که توسعه‌دهندگان باید درباره آن\nمی‌دانستند و با آن تعامل می‌کردند فقط به منظور مشخص کردن یک `BlocDelegate`\nسفارشی. با تغییر به `BlocObserver`، احساس کردیم که تجربه توسعه‌دهنده را بهبود\nمی‌بخشد تا ناظر را مستقیماً بر روی bloc خود تنظیم کنید.\n\nاین تغییر همچنین به ما این امکان را داد که سایر افزودنی‌های bloc مانند\n`HydratedStorage` را از `BlocObserver` جدا کنیم.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder شرط به buildWhen تغییر نام یافت\n\n##### دلیل\n\nهنگام استفاده از `BlocBuilder`، قبلاً می‌توانستیم یک `شرط` مشخص کنیم تا تعیین\nکنیم آیا `سازنده` باید دوباره بسازد یا خیر.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nنام `شرط` توصیف چندانی ندارد و به طور خاص، زمانی که با `BlocConsumer` تعامل\nدارد، API را ناسازگار می‌کند زیرا توسعه‌دهندگان می‌توانند دو شرط (یکی برای\n`سازنده` و یکی برای `شنونده`) فراهم کنند. به عنوان نتیجه، API `BlocConsumer` یک\n`buildWhen` و `listenWhen` را در معرض دید قرار داد\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nبه منظور همسان‌سازی API و ارائه یک تجربه توسعه‌دهنده سازگارتر، `شرط` به\n`buildWhen` تغییر نام یافت.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener شرط به listenWhen تغییر نام یافت\n\n##### دلیل\n\nبه همان دلایلی که در بالا توضیح داده شد، شرط `BlocListener` نیز تغییر نام یافت.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage و HydratedBlocStorage تغییر نام یافتند\n\n##### دلیل\n\nبه منظور بهبود استفاده مجدد از کد بین\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) و\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit)، پیاده‌سازی پیش‌فرض\nذخیره‌سازی تغییر نام یافت از `HydratedBlocStorage` به `HydratedStorage`. علاوه\nبر این، رابط `HydratedStorage` از `HydratedStorage` به `Storage` تغییر نام یافت.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage از BlocDelegate جدا شد\n\n##### دلیل\n\nهمانطور که قبلاً ذکر شد، `BlocDelegate` به `BlocObserver` تغییر نام یافت و\nمستقیماً بر روی `bloc` تنظیم شد:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nتغییر زیر انجام شد تا:\n\n- با API ناظر جدید bloc سازگار باشد\n- ذخیره‌سازی را فقط به `HydratedBloc` محدود کند\n- `BlocObserver` را از `Storage` جدا کند\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗ساده‌سازی راه‌اندازی\n\n##### دلیل\n\nقبلاً، توسعه‌دهندگان مجبور بودند به صورت دستی فراخوانی کنند\n`super.initialState ?? DefaultInitialState()` به منظور راه‌اندازی نمونه‌های\n`HydratedBloc` خود. این کار دست و پاگیر و verbose بود و همچنین با تغییرات شکستن\nدر `initialState` در `bloc` ناسازگار بود. به عنوان نتیجه، در v5.0.0 راه‌اندازی\n`HydratedBloc` کاملاً مشابه با راه‌اندازی عادی `Bloc` است.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/fa/modeling-state.mdx",
    "content": "---\ntitle: مدل‌سازی حالت\ndescription:\n  نمای کلی از چندین روش برای مدل‌سازی حالت‌ها هنگام استفاده از package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nروش‌های مختلفی برای ساختاردهی به حالت برنامه وجود دارد. هر کدام مزایا و معایب\nخود را دارد. در این بخش، به چندین رویکرد نگاه خواهیم کرد، مزایا و معایب آنها، و\nزمان استفاده از هر کدام.\n\nرویکردهای زیر صرفاً توصیه هستند و کاملاً اختیاری. آزادانه از هر رویکردی که ترجیح\nمی‌دهید استفاده کنید. ممکن است برخی از مثال‌ها/مستندات از رویکردها پیروی نکنند،\nعمدتاً برای سادگی/کوتاهی.\n\n:::tip\n\nقطعه کدهای زیر بر ساختار حالت تمرکز دارند. در عمل، ممکن است بخواهید:\n\n- از `Equatable` ارث‌بری کنید از\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- کلاس را با `@Data()` از\n  [`package:data_class`](https://pub.dev/packages/data_class) حاشیه‌نویسی کنید\n- کلاس را با **@immutable** از [`package:meta`](https://pub.dev/packages/meta)\n  حاشیه‌نویسی کنید\n- یک متد `copyWith` پیاده‌سازی کنید\n- از کلمه کلیدی `const` برای سازنده‌ها استفاده کنید\n\n:::\n\n## کلاس پایه و Enum وضعیت\n\nاین رویکرد از یک **کلاس پایه واحد** برای همه حالت‌ها به همراه یک `enum` که\nوضعیت‌های مختلف را نشان می‌دهد تشکیل شده است. ویژگی‌ها nullable هستند و بر اساس\nوضعیت فعلی مدیریت می‌شوند. این رویکرد برای حالت‌هایی که کاملاً انحصاری نیستند\nو/یا دارای ویژگی‌های مشترک زیادی هستند بهترین کار را می‌کند.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### مزایا\n\n- **ساده**: مدیریت یک کلاس و یک enum وضعیت آسان است و همه ویژگی‌ها به راحتی قابل\n  دسترسی هستند.\n- **کوتاه**: به طور کلی نیاز به خطوط کد کمتری نسبت به رویکردهای دیگر دارد.\n\n#### معایب\n\n- **ایمن از نظر نوع نیست**: نیاز به بررسی `status` قبل از دسترسی به ویژگی‌ها\n  دارد. امکان `emit` کردن یک حالت نادرست وجود دارد که می‌تواند منجر به باگ شود.\n  ویژگی‌ها برای حالت‌های خاص nullable هستند، که مدیریت آنها می‌تواند دشوار باشد\n  و نیاز به باز کردن اجباری یا انجام بررسی‌های null دارد. برخی از این معایب\n  می‌توانند با نوشتن تست‌های واحد و نوشتن سازنده‌های تخصصی، نام‌گذاری شده کاهش\n  یابند.\n- **متورم**: منجر به یک حالت واحد می‌شود که می‌تواند با گذشت زمان با ویژگی‌های\n  زیادی متورم شود.\n\n#### حکم\n\nاین رویکرد برای حالت‌های ساده یا زمانی که نیازها حالت‌هایی را که انحصاری نیستند\n(مثلاً نمایش یک snackbar هنگام وقوع خطا در حالی که هنوز داده‌های قدیمی از آخرین\nحالت موفقیت را نشان می‌دهد) فراخوانی می‌کنند بهترین کار را می‌کند. این رویکرد\nانعطاف‌پذیری و کوتاهی را با هزینه ایمنی نوع فراهم می‌کند.\n\n## کلاس Sealed و زیرکلاس‌ها\n\nاین رویکرد از یک **کلاس sealed** که هر ویژگی مشترکی را نگه می‌دارد و چندین\nزیرکلاس برای حالت‌های جداگانه تشکیل شده است. این رویکرد عالی برای حالت‌های\nجداگانه، انحصاری است.\n\n<SealedClassAndSubclassesSnippet />\n\n#### مزایا\n\n- **ایمن از نظر نوع**: کد ایمن از کامپایل است و امکان دسترسی تصادفی به یک ویژگی\n  نامعتبر وجود ندارد. هر زیرکلاس ویژگی‌های خود را نگه می‌دارد، که مشخص می‌کند\n  کدام ویژگی‌ها به کدام حالت تعلق دارند.\n- **صریح**: ویژگی‌های مشترک را از ویژگی‌های خاص حالت جدا می‌کند.\n- **جامع**: استفاده از یک عبارت `switch` برای بررسی جامع بودن برای اطمینان از\n  اینکه هر حالت به طور صریح مدیریت می‌شود.\n  - اگر نمی‌خواهید\n    [switching جامع](https://dart.dev/language/branches#exhaustiveness-checking)\n    یا می‌خواهید بتوانید زیرنوع‌ها را بعداً بدون شکستن API اضافه کنید، از\n    اصلاح‌کننده [final](https://dart.dev/language/class-modifiers#final) استفاده\n    کنید.\n  - برای جزئیات بیشتر،\n    [مستندات کلاس sealed](https://dart.dev/language/class-modifiers#sealed) را\n    ببینید.\n\n#### معایب\n\n- **طولانی**: نیاز به کد بیشتری دارد (یک کلاس پایه و یک زیرکلاس برای هر حالت).\n  همچنین ممکن است نیاز به کد تکراری برای ویژگی‌های مشترک در زیرکلاس‌ها داشته\n  باشد.\n- **پیچیده**: افزودن ویژگی‌های جدید نیاز به به‌روزرسانی هر زیرکلاس و کلاس پایه\n  دارد، که می‌تواند دشوار باشد و منجر به افزایش پیچیدگی حالت شود. علاوه بر این،\n  ممکن است نیاز به بررسی نوع غیرضروری/زیادی برای دسترسی به ویژگی‌ها داشته باشد.\n\n#### حکم\n\nاین رویکرد برای حالت‌های انحصاری، خوب تعریف‌شده با ویژگی‌های منحصر به فرد بهترین\nکار را می‌کند. این رویکرد ایمنی نوع و بررسی‌های جامع بودن را فراهم می‌کند و بر\nایمنی نسبت به کوتاهی و سادگی تأکید دارد.\n"
  },
  {
    "path": "docs/src/content/docs/fa/naming-conventions.mdx",
    "content": "---\ntitle: قراردادهای نامگذاری\ndescription: مروری بر قوانین نامگذاری توصیه شده هنگام استفاده از بلوک.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nقراردادهای نامگذاری زیر صرفاً توصیه شده و کاملاً اختیاری هستند. با خیال راحت از\nهر گونه قرارداد نامگذاری که ترجیح می دهید استفاده کنید. ممکن است دریابید که برخی\nاز مثال‌ها/اسناد اصولاً به دلیل سادگی/مختصر بودن از قراردادهای نام‌گذاری پیروی\nنمی‌کنند. این قرارداد ها به شدت برای پروژه های بزرگ با توسعه دهندگان متعدد توصیه\nمی شود.\n\n## قراردادهای کلاس رویداد\n\nرویدادها باید با **گذشته ساده** نامگذاری شوند، زیرا رویدادها چیزهایی هستند که از\nدیدگاه Bloc قبلاً اتفاق افتاده اند.\n\n### ساختار\n\n`BlocSubject` + `Noun (optional)` + `Verb (event)`\n\nرویدادهای لود اولیه باید این قرارداد را دنبال کنند: `BlocSubject` + `Started`\n\n:::note\n\nکلاس های رویداد پایه باید این نامگذاری را داشته باشند: `BlocSubject` + `Event`.\n\n:::\n\n### مثال ها\n\n✅ **خوب**\n\n<EventExamplesGood1 />\n\n❌ **بد**\n\n<EventExamplesBad1 />\n\n## قراردادهای کلاس وضعیت\n\nوضعیت‌ها باید اسم باشند، چرا که یک وضعیت فقط یک لحظه‌ی خاص در زمان را نمایان\nمی‌کند. دو روش رایج برای نمایش وضعیت وجود دارد: استفاده از زیرکلاس‌ها\n(Subclasses) یا استفاده از یک کلاس تکی (Single class).\n\n### ساختار\n\n#### زیرکلاس‌ها\n\n`BlocSubject` + `Verb (action)` + `State`\n\nهنگام نمایش وضعیت به عنوان چندین زیرکلاس، `State` باید یکی از موارد زیر را دارا\nباشد:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nوضعیت‌های اولیه باید طبق این قرار داد عمل کنند: `BlocSubject` + `Initial`.\n\n:::\n\n#### کلاس تکی\n\n`BlocSubject` + `State`\n\nهنگام نمایش وضعیت به عنوان یک کلاس پایه تکی، باید از یک enum با نام\n`BlocSubject` + `Status` برای نمایش وضعیت‌های مختلف استفاده شود:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nکلاس وضعیت پایه همیشه باید به این روش نام گذاری شوند: `BlocSubject` + `State`.\n\n:::\n\n### مثال ها\n\n✅ **خوب**\n\n##### زیرکلاس‌ها\n\n<StateExamplesGood1Snippet />\n\n##### کلاس تکی\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **بد**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/fa/testing.mdx",
    "content": "---\ntitle: آزمایش کردن (Testing)\ndescription: اصول اولیه نحوه نوشتن تست برای Bloc های خود.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc به گونه ای طراحی شده است که آزمایش آن بسیار آسان باشد.در این بخش، نحوه تست\nواحد (Unit Test) یک Bloc را توضیح خواهیم داد.\n\nبه خاطر سادگی، بیایید تست ها را برای `CounterBloc` که در\n[مفاهیم اصلی](/fa/bloc-concepts) ایجاد کردیم بنویسیم.\n\nبرای خلاصه‌ی مطلب، پیاده‌سازی `CounterBloc` به شکل زیر است:\n\n<CounterBlocSnippet />\n\n## راه اندازی (Setup)\n\nقبل از شروع نوشتن تست های خود، باید یک چارچوب آزمایشی (Testing Framework) را به\nوابستگی های خود اضافه کنیم.\n\nما باید [test](https://pub.dev/packages/test) و\n[bloc_test](https://pub.dev/packages/bloc_test) را به پروژه خود اضافه کنیم.\n\n<AddDevDependenciesSnippet />\n\n## آزمایش کردن\n\nبیایید با ایجاد فایل تست برای `CounterBloc`، به نام `counter_bloc_test.dart`،\nشروع کنیم و بسته تست را وارد کنیم.\n\n<CounterBlocTestImportsSnippet />\n\nبعداز آن، باید `main` و گروه تست خود را ایجاد کنیم.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nگروه‌ها برای سازماندهی تست‌های فردی (Individual) و همچنین برای ایجاد یک محیط\n(Context) که در آن می‌توانید یک `setUp` و `tearDown` مشترک را در تمام تست‌های\nفردی به اشتراک بگذارید، استفاده می‌شوند.\n\n:::\n\nبیایید با ایجاد نمونه‌ای از `CounterBloc` خود که در تمامی تست‌هایمان استفاده\nخواهد شد، شروع کنیم.\n\n<CounterBlocTestSetupSnippet />\n\nحالا می‌توانیم شروع به نوشتن تست‌های فردی خود کنیم.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nمی‌توانیم تمامی تست‌های خود را با استفاده از دستور `pub run test` اجرا کنیم.\n\n:::\n\nدر این نقطه باید تست اولیه ما را پاس کرده باشیم! حالا بیایید یک تست پیچیده‌تر را\nبا استفاده از بسته [bloc_test](https://pub.dev/packages/bloc_test) بنویسیم.\n\n<CounterBlocTestBlocTestSnippet />\n\nباید بتوانیم تست‌ها را اجرا کنیم و ببینیم که همه آنها پاس می‌شوند.\n\nاین تمام چیزی است که در آن وجود دارد، آزمایش باید سریع باشد و ما باید هنگام\nایجاد تغییرات و بازسازی کد خود احساس اطمینان کنیم.\n\nشما می‌توانید به برنامه\n[Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nمراجعه کنید تا یک مثال از یک برنامه کاملاً تست شده را ببینید.\n"
  },
  {
    "path": "docs/src/content/docs/fa/why-bloc.mdx",
    "content": "---\ntitle: چرا Bloc؟\ndescription:\n  یک مروری بر ویژگی‌هایی که باعث می‌شود بلاک یک راه حل مدیریت وضعیت قوی باشد.\nsidebar:\n  order: 1\n---\n\nBloc به شما امکان می‌دهد به راحتی لایه نمایش (Presentation) را از منطق کسب و کار\n(Business logic) جدا کنید، که این امر باعث می‌شود, کد شما قابلیت هایی مانند\n_سرعت بالا_، _آزمون آسان_ و _قابل استفاده مجدد_ را داشته باشد.\n\nوقتی اپلیکیشن‌های با کیفیت تولید می‌شوند، مدیریت وضعیت (State) به مسئله‌ای حیاتی\nتبدیل می‌شود.\n\nما به عنوان توسعه دهندگان می خواهیم:\n\n- بدانیم درخواست ما در هر مقطع زمانی در چه وضعیتی است.\n- به راحتی هر مورد را آزمایش کنیم تا مطمئن شویم برنامه ما به درستی پاسخ می دهد.\n- هر تعامل کاربر را در برنامه خود ثبت کنیم تا بتوانیم تصمیمات مبتنی بر داده را\n  اتخاذ کنیم.\n- به صورت بهینه و کارآمد کار کنیم و اجزای مختلف را, هم در داخل برنامه‌ی خود و هم\n  در برنامه‌های دیگر استفاده مجدد کنیم.\n- امکان کار همزمان برای چندین توسعه‌دهنده و بدون هیچ مشکلی در یک کد پایه با\n  رعایت الگوها و قواعد مشترک، فراهم باشد.\n- برنامه های سریع و پاسخگو ایجاد کنیم.\n\nبلاک برای برآورده کردن همه این نیازها و بسیاری دیگر طراحی شده است.\n\nهمچنین، راه‌حل‌های مدیریت وضعیت (State Management) مختلفی وجود دارد و تصمیم گیری\nبرای استفاده از یکی از آن‌ها ممکن است یک وظیفه سخت باشد. هیچ راه‌حل مدیریت\nوضعیتی کامل و بی‌نقص وجود ندارد! مهم این است که شما یکی را انتخاب کنید که برای\nتیم و پروژه شما بهترین عمل کند.\n\nBloc با در نظر گرفتن سه ارزش اصلی طراحی شده است:\n\n- **ساده:** ساده درک شود و می‌تواند و توسط توسعه‌دهندگان با سطوح مهارتی متفاوت\n  استفاده شود.\n- **قدرتمند:** با ترکیب کردن اجزای کوچکتر، به شما کمک می‌کند برنامه‌های\n  شگفت‌انگیز و پیچیده‌ای را ایجاد کنید.\n- **قابل آزمایش:** با امکان تست آسان و سریع هر جنبه‌ای از برنامه، میتوان با\n  اطمینان بیشتری به بهبود و تغییرات نرم‌افزاری پرداخت.\n\nبطور کلی، Bloc سعی می‌کند با تنظیم زمانی که یک تغییر وضعیت می‌تواند رخ دهد و\nاجرای یک روش یکتا برای تغییر وضعیت در سراسر برنامه، تغییرات وضعیت را قابل\nپیش‌بینی کند.\n"
  },
  {
    "path": "docs/src/content/docs/faqs.mdx",
    "content": "---\ntitle: FAQs\ndescription: Answers to frequently asked questions regarding the bloc library.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## State Not Updating\n\n❔ **Question**: I'm emitting a state in my bloc but the UI is not updating.\nWhat am I doing wrong?\n\n💡 **Answer**: If you're using Equatable make sure to pass all properties to the\nprops getter.\n\n✅ **GOOD**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **BAD**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nIn addition, make sure you are emitting a new instance of the state in your\nbloc.\n\n✅ **GOOD**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **BAD**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\n`Equatable` properties should always be copied rather than modified. If an\n`Equatable` class contains a `List` or `Map` as properties, be sure to use\n`List.of` or `Map.of` respectively to ensure that equality is evaluated based on\nthe values of the properties rather than the reference.\n\n:::\n\n## When to use Equatable\n\n❔**Question**: When should I use Equatable?\n\n💡**Answer**:\n\n<EquatableEmitSnippet />\n\nIn the above scenario if `StateA` extends `Equatable` only one state change will\noccur (the second emit will be ignored). In general, you should use `Equatable`\nif you want to optimize your code to reduce the number of rebuilds. You should\nnot use `Equatable` if you want the same state back-to-back to trigger multiple\ntransitions.\n\nIn addition, using `Equatable` makes it much easier to test blocs since we can\nexpect specific instances of bloc states rather than using `Matchers` or\n`Predicates`.\n\n<EquatableBlocTestSnippet />\n\nWithout `Equatable` the above test would fail and would need to be rewritten\nlike:\n\n<NoEquatableBlocTestSnippet />\n\n## Handling Errors\n\n❔ **Question**: How can I handle an error while still showing previous data?\n\n💡 **Answer**:\n\nThis highly depends on how the state of the bloc has been modeled. In cases\nwhere data should still be retained even in the presence of an error, consider\nusing a single state class.\n\n<SingleStateSnippet />\n\nThis will allow widgets to have access to the `data` and `error` properties\nsimultaneously and the bloc can use `state.copyWith` to retain old data even\nwhen an error has occurred.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Question**: What's the difference between Bloc and Redux?\n\n💡 **Answer**:\n\nBLoC is a design pattern that is defined by the following rules:\n\n1. Input and Output of the BLoC are simple Streams and Sinks.\n2. Dependencies must be injectable and Platform agnostic.\n3. No platform branching is allowed.\n4. Implementation can be whatever you want as long as you follow the above\n   rules.\n\nThe UI guidelines are:\n\n1. Each \"complex enough\" component has a corresponding BLoC.\n2. Components should send inputs \"as is\".\n3. Components should show outputs as close as possible to \"as is\".\n4. All branching should be based on simple BLoC boolean outputs.\n\nThe Bloc Library implements the BLoC Design Pattern and aims to abstract RxDart\nin order to simplify the developer experience.\n\nThe three principles of Redux are:\n\n1. Single source of truth\n2. State is read-only\n3. Changes are made with pure functions\n\nThe bloc library violates the first principle; with bloc state is distributed\nacross multiple blocs. Furthermore, there is no concept of middleware in bloc\nand bloc is designed to make async state changes very easy, allowing you to emit\nmultiple states for a single event.\n\n## Bloc vs. Provider\n\n❔ **Question**: What's the difference between Bloc and Provider?\n\n💡 **Answer**: `provider` is designed for dependency injection (it wraps\n`InheritedWidget`). You still need to figure out how to manage your state (via\n`ChangeNotifier`, `Bloc`, `Mobx`, etc...). The Bloc Library uses `provider`\ninternally to make it easy to provide and access blocs throughout the widget\ntree.\n\n## BlocProvider.of() Fails to Find Bloc\n\n❔ **Question**: When using `BlocProvider.of(context)` it cannot find the bloc.\nHow can I fix this?\n\n💡 **Answer**: You cannot access a bloc from the same context in which it was\nprovided so you must ensure `BlocProvider.of()` is called within a child\n`BuildContext`.\n\n✅ **GOOD**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **BAD**\n\n<BlocProviderBad1Snippet />\n\n## Project Structure\n\n❔ **Question**: How should I structure my project?\n\n💡 **Answer**: While there is really no right/wrong answer to this question,\nsome recommended references are\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nThe most important thing is having a **consistent** and **intentional** project\nstructure.\n\n## Adding Events within a Bloc\n\n❔ **Question**: Is it okay to add events within a bloc?\n\n💡 **Answer**: In most cases, events should be added externally but in some\nselect cases it may make sense for events to be added internally.\n\nThe most common situation in which internal events are used is when state\nchanges must occur in response to real-time updates from a repository. In these\nsituations, the repository is the stimulus for the state change instead of an\nexternal event such as a button tap.\n\nIn the following example, the state of `MyBloc` is dependent on the current user\nwhich is exposed via the `Stream<User>` from the `UserRepository`. `MyBloc`\nlistens for changes in the current user and adds an internal `_UserChanged`\nevent whenever a user is emitted from the user stream.\n\n<BlocInternalAddEventSnippet />\n\nBy adding an internal event, we are also able to specify a custom `transformer`\nfor the event to determine how multiple `_UserChanged` events will be processed\n-- by default they will be processed concurrently.\n\nIt's highly recommended that internal events are private. This is an explicit\nway of signaling that a specific event is used only within the bloc itself and\nprevents external components from knowing about the event.\n\n<BlocInternalEventSnippet />\n\nWe can alternatively define an external `Started` event and use the\n`emit.forEach` API to handle reacting to real-time user updates:\n\n<BlocExternalForEachSnippet />\n\nThe benefits of the above approach are:\n\n- We do not need an internal `_UserChanged` event\n- We do not need to manage the `StreamSubscription` manually\n- We have full control over when the bloc subscribes to the stream of user\n  updates\n\nThe drawbacks of the above approach are:\n\n- We cannot easily `pause` or `resume` the subscription\n- We need to expose a public `Started` event which must be added externally\n- We cannot use a custom `transformer` to adjust how we react to user updates\n\n## Exposing Public Methods\n\n❔ **Question**: Is it okay to expose public methods on my bloc and cubit\ninstances?\n\n💡 **Answer**\n\nWhen creating a cubit, it's recommended to only expose public methods for the\npurposes of triggering state changes. As a result, generally all public methods\non a cubit instance should return `void` or `Future<void>`.\n\nWhen creating a bloc, it's recommended to avoid exposing any custom public\nmethods and instead notify the bloc of events by calling `add`.\n"
  },
  {
    "path": "docs/src/content/docs/fil/getting-started.mdx",
    "content": "---\ntitle: Panimulang Hakbang\ndescription: Lahat ng mga kailangan mo upang magamit ang Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Packages\n\nAng ecosystem ng bloc ay binubuo ng maraming mga package na nakalista sa ibaba:\n\n| Package                                                                                    | Description                 | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart Components      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Core Dart APIs              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers          | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | Testing APIs                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools          | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter Widgets             | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo Support           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Paggamit\n\n<InstallationTabs />\n\n:::note\n\nUpang magsimula sa paggamit ng bloc, kinakailangan mo ng naka-install na\n[Dart SDK](https://dart.dev/get-dart) sa iyong machine.\n\n:::\n\n## Imports\n\nNgayong na-install na natin ang bloc, maaari tayong gumawa ng `main.dart` at\ni-import ang gagamiting bloc na package.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/fil/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ Bisitahin ang\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Isang library para sa maaasahang pamamahala ng state sa Dart\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Magsimula\n      link: /fil/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Bisitahin sa GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"Inisponsor nang may 💖 ni\"\n\tbecomeASponsor=\"Maging isponsor\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Magsimula\" icon=\"rocket\">\n\t```sh\n\t# Idagdag ang bloc sa iyong proyekto.\n\tdart pub add bloc\n\t```\n\nAng aming [gabay sa pagsisimula](/fil/getting-started) ay naglalaman ng mga\nsunod-sunod na instruction kung paano magsimula sa paggamit ng Bloc sa loob\nlamang ng ilang minuto.\n\n</SplitCard>\n\n<Card title=\"Mag-tour ng may patnubay\" icon=\"star\">\n\tKumpletuhin [ang opisyal na pagtuturo](/fil/tutorials/flutter-counter) para\n\tmatutuhan ang tamang practices at gumawa ng iba't-ibang apps gamit si Bloc.\n</Card>\n\n<Card title=\"Gumawa gamit ang Bloc\" icon=\"laptop\">\n\tSiyasatin ang mga [halimbawang\n\tapps](https://github.com/felangel/bloc/tree/master/examples) na may mataas na\n\tkalidad at puno ng test tulad ng counter, timer, infinite list, weather, todo\n\tat iba pa!\n</Card>\n\n<ListCard title=\"Matuto\" icon=\"open-book\">\n\n    - [Bakit Bloc?](/fil/why-bloc)\n    - [Mahahalagang Konsepto](/fil/bloc-concepts)\n    - [Arkitektura](/fil/architecture)\n    - [Pag-test](/fil/testing)\n    - [Kumbensyon sa Pagpangalan](/fil/naming-conventions)\n    - [FAQs](/fil/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integrasyon\" icon=\"puzzle\">\n    - [Integrasyon sa VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Integrasyon sa IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Integrasyon sa Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Integrasyon sa Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Personal na Template](https://brickhub.dev/search?q=bloc)\n    - [Integrasyon sa Developer Tools](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Sumali sa aming Discord\" />\n"
  },
  {
    "path": "docs/src/content/docs/fil/why-bloc.mdx",
    "content": "---\ntitle: Bakit Bloc?\ndescription:\n  Kabuuang idea kung bakit ang Bloc ay isang solid na solusyon sa pamamahala ng\n  state.\nsidebar:\n  order: 1\n---\n\nPinadadali ng Bloc na ihiwalay ang presentation sa business logic, ginawawang\n_mabilis_, _madali i-test_, at _reusable_ ang iyong code.\n\nSa pagbuo ng mga application na may kalidad para sa produksiyon, naging kritikal\nang pamamahala ng state.\n\nBilang developers gusto nating:\n\n- malaman kung anong state ng ating application anumang oras.\n- madaling i-test ang bawat kalagayan sa application upang tiyakin na ito ay\n  tumutugon nang naaayon.\n- i-record ang bawat interaksyon ng user sa ating application upang magkaruon ng\n  mga desisyon na data-driven.\n- makapagtrabaho nang maayos at epektibo at gamitin muli ang ilang mga bahagi ng\n  application sa iba pang application.\n- magkaruon ng maraming developers na maayos na nagtatrabaho sa loob ng isang\n  code base na sinusunod ang parehong mga pattern at kumbensiyon.\n- gumawa ng mabilis at responsibong mga application.\n\nAng Bloc ay idinisenyo upang matugunan ang lahat ng pangangailangan na ito at\nmarami pang iba.\n\nMarami ang mga solusyon sa pamamahala ng state at ang pagpili kung alin ang\ngagamitin ay maaaring maging isang masalimuot na gawain. Wala namang perpektong\nsolusyon sa pamamahala ng state! Ang mahalaga ay pumili ka ng isang solusyon na\npinakamabuti para sa iyong mga kasama at proyekto.\n\nAng Bloc ay idinisenyo na may tatlong pangunahing halaga sa isipan:\n\n- **Simple:** Madaling maunawaan at maaaring gamitin ng mga developer na may\n  iba't ibang antas ng kasanayan.\n- **Mabisa:** Makatulong na gumawa ng kamangha-mangha at kumplikadong mga\n  application sa pamamagitan ng pag-compose ng mga ito mula sa mas maliit na mga\n  bahagi.\n- **Pwedeng I-test:** Madaling mai-test ang bawat bahagi ng application upang\n  magkaruon tayo ng kumpiyansa sa bawat pagbabago.\n\nSa kabuuan, ang Bloc ay sumusubok na gawing maaasahan ang mga pagbabago sa state\nsa pamamagitan ng pagre-regulate kung kailan maaaring mangyari ang pagbabago sa\nstate at ipinapatupad ang iisang paraan ng pagbabago ng state sa buong\napplication.\n"
  },
  {
    "path": "docs/src/content/docs/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Flutter Bloc Concepts\ndescription: An overview of the core concepts for package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nPlease make sure to carefully read the following sections before working with\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nAll widgets exported by the `flutter_bloc` package integrate with both `Cubit`\nand `Bloc` instances.\n\n:::\n\n## Bloc Widgets\n\n### BlocBuilder\n\n**BlocBuilder** is a Flutter widget which requires a `Bloc` and a `builder`\nfunction. `BlocBuilder` handles building the widget in response to new states.\n`BlocBuilder` is very similar to `StreamBuilder` but has a more simple API to\nreduce the amount of boilerplate code needed. The `builder` function will\npotentially be called many times and should be a\n[pure function](https://en.wikipedia.org/wiki/Pure_function) that returns a\nwidget in response to the state.\n\nSee `BlocListener` if you want to \"do\" anything in response to state changes\nsuch as navigation, showing a dialog, etc...\n\nIf the `bloc` parameter is omitted, `BlocBuilder` will automatically perform a\nlookup using `BlocProvider` and the current `BuildContext`.\n\n<BlocBuilderSnippet />\n\nOnly specify the bloc if you wish to provide a bloc that will be scoped to a\nsingle widget and isn't accessible via a parent `BlocProvider` and the current\n`BuildContext`.\n\n<BlocBuilderExplicitBlocSnippet />\n\nFor fine-grained control over when the `builder` function is called an optional\n`buildWhen` can be provided. `buildWhen` takes the previous bloc state and\ncurrent bloc state and returns a boolean. If `buildWhen` returns true, `builder`\nwill be called with `state` and the widget will rebuild. If `buildWhen` returns\nfalse, `builder` will not be called with `state` and no rebuild will occur.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** is a Flutter widget which is analogous to `BlocBuilder` but\nallows developers to filter updates by selecting a new value based on the\ncurrent bloc state. Unnecessary builds are prevented if the selected value does\nnot change. The selected value must be immutable in order for `BlocSelector` to\naccurately determine whether `builder` should be called again.\n\nIf the `bloc` parameter is omitted, `BlocSelector` will automatically perform a\nlookup using `BlocProvider` and the current `BuildContext`.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** is a Flutter widget which provides a bloc to its children via\n`BlocProvider.of<T>(context)`. It is used as a dependency injection (DI) widget\nso that a single instance of a bloc can be provided to multiple widgets within a\nsubtree.\n\nIn most cases, `BlocProvider` should be used to create new blocs which will be\nmade available to the rest of the subtree. In this case, since `BlocProvider` is\nresponsible for creating the bloc, it will automatically handle closing the\nbloc.\n\n<BlocProviderSnippet />\n\nBy default, `BlocProvider` will create the bloc lazily, meaning `create` will\nget executed when the bloc is looked up via `BlocProvider.of<BlocA>(context)`.\n\nTo override this behavior and force `create` to be run immediately, `lazy` can\nbe set to `false`.\n\n<BlocProviderEagerSnippet />\n\nIn some cases, `BlocProvider` can be used to provide an existing bloc to a new\nportion of the widget tree. This will be most commonly used when an existing\nbloc needs to be made available to a new route. In this case, `BlocProvider`\nwill not automatically close the bloc since it did not create it.\n\n<BlocProviderValueSnippet />\n\nthen from either `ChildA`, or `ScreenA` we can retrieve `BlocA` with:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** is a Flutter widget that merges multiple `BlocProvider`\nwidgets into one. `MultiBlocProvider` improves the readability and eliminates\nthe need to nest multiple `BlocProviders`. By using `MultiBlocProvider` we can\ngo from:\n\n<NestedBlocProviderSnippet />\n\nto:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nWhen a `BlocProvider` is defined within the context of a `MultiBlocProvider`,\nany `child` will be ignored.\n\n:::\n\n### BlocListener\n\n**BlocListener** is a Flutter widget which takes a `BlocWidgetListener` and an\noptional `Bloc` and invokes the `listener` in response to state changes in the\nbloc. It should be used for functionality that needs to occur once per state\nchange such as navigation, showing a `SnackBar`, showing a `Dialog`, etc...\n\n`listener` is only called once for each state change (**NOT** including the\ninitial state) unlike `builder` in `BlocBuilder` and is a `void` function.\n\nIf the `bloc` parameter is omitted, `BlocListener` will automatically perform a\nlookup using `BlocProvider` and the current `BuildContext`.\n\n<BlocListenerSnippet />\n\nOnly specify the bloc if you wish to provide a bloc that is otherwise not\naccessible via `BlocProvider` and the current `BuildContext`.\n\n<BlocListenerExplicitBlocSnippet />\n\nFor fine-grained control over when the `listener` function is called an optional\n`listenWhen` can be provided. `listenWhen` takes the previous bloc state and\ncurrent bloc state and returns a boolean. If `listenWhen` returns true,\n`listener` will be called with `state`. If `listenWhen` returns false,\n`listener` will not be called with `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** is a Flutter widget that merges multiple `BlocListener`\nwidgets into one. `MultiBlocListener` improves the readability and eliminates\nthe need to nest multiple `BlocListeners`. By using `MultiBlocListener` we can\ngo from:\n\n<NestedBlocListenerSnippet />\n\nto:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nWhen a `BlocListener` is defined within the context of a `MultiBlocListener`,\nany `child` will be ignored.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** exposes a `builder` and `listener` in order to react to new\nstates. `BlocConsumer` is analogous to a nested `BlocListener` and `BlocBuilder`\nbut reduces the amount of boilerplate needed. `BlocConsumer` should only be used\nwhen it is necessary to both rebuild UI and execute other reactions to state\nchanges in the `bloc`. `BlocConsumer` takes a required `BlocWidgetBuilder` and\n`BlocWidgetListener` and an optional `bloc`, `BlocBuilderCondition`, and\n`BlocListenerCondition`.\n\nIf the `bloc` parameter is omitted, `BlocConsumer` will automatically perform a\nlookup using `BlocProvider` and the current `BuildContext`.\n\n<BlocConsumerSnippet />\n\nAn optional `listenWhen` and `buildWhen` can be implemented for more granular\ncontrol over when `listener` and `builder` are called. The `listenWhen` and\n`buildWhen` will be invoked on each `bloc` `state` change. They each take the\nprevious `state` and current `state` and must return a `bool` which determines\nwhether or not the `builder` and/or `listener` function will be invoked. The\nprevious `state` will be initialized to the `state` of the `bloc` when the\n`BlocConsumer` is initialized. `listenWhen` and `buildWhen` are optional and if\nthey aren't implemented, they will default to `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** is a Flutter widget which provides a repository to its\nchildren via `RepositoryProvider.of<T>(context)`. It is used as a dependency\ninjection (DI) widget so that a single instance of a repository can be provided\nto multiple widgets within a subtree. `BlocProvider` should be used to provide\nblocs whereas `RepositoryProvider` should only be used for repositories.\n\n<RepositoryProviderSnippet />\n\nthen from `ChildA` we can retrieve the `Repository` instance with:\n\n<RepositoryProviderLookupSnippet />\n\nRepositories that manage resources which must be disposed can do so via the\n`dispose` callback:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** is a Flutter widget that merges multiple\n`RepositoryProvider` widgets into one. `MultiRepositoryProvider` improves the\nreadability and eliminates the need to nest multiple `RepositoryProvider`. By\nusing `MultiRepositoryProvider` we can go from:\n\n<NestedRepositoryProviderSnippet />\n\nto:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nWhen a `RepositoryProvider` is defined within the context of a\n`MultiRepositoryProvider`, any `child` will be ignored.\n\n:::\n\n## BlocProvider Usage\n\nLets take a look at how to use `BlocProvider` to provide a `CounterBloc` to a\n`CounterPage` and react to state changes with `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nAt this point we have successfully separated our presentational layer from our\nbusiness logic layer. Notice that the `CounterPage` widget knows nothing about\nwhat happens when a user taps the buttons. The widget simply tells the\n`CounterBloc` that the user has pressed either the increment or decrement\nbutton.\n\n## RepositoryProvider Usage\n\nWe are going to take a look at how to use `RepositoryProvider` within the\ncontext of the [`flutter_weather`][flutter_weather_link] example.\n\n<WeatherRepositorySnippet />\n\nIn our `main.dart`, we call `runApp` with our `WeatherApp` widget.\n\n<WeatherMainSnippet />\n\nWe will inject our `WeatherRepository` instance into our widget tree via\n`RepositoryProvider`.\n\nWhen instantiating a bloc, we can access the instance of a repository via\n`context.read` and inject the repository into the bloc via constructor.\n\n<WeatherAppSnippet />\n\n:::tip\n\nIf you have more than one repository, you can use `MultiRepositoryProvider` to\nprovide multiple repository instances to the subtree.\n\n:::\n\n:::note\n\nUse the `dispose` callback to handle freeing any resources when the\n`RepositoryProvider` is unmounted.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension Methods\n\n[Extension methods](https://dart.dev/guides/language/extension-methods),\nintroduced in Dart 2.7, are a way to add functionality to existing libraries. In\nthis section, we'll take a look at extension methods included in\n`package:flutter_bloc` and how they can be used.\n\n`flutter_bloc` has a dependency on\n[package:provider](https://pub.dev/packages/provider) which simplifies the use\nof\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nInternally, `package:flutter_bloc` uses `package:provider` to implement:\n`BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` and\n`MultiRepositoryProvider` widgets. `package:flutter_bloc` exports the\n`ReadContext`, `WatchContext` and `SelectContext`, extensions from\n`package:provider`.\n\n:::note\n\nLearn more about [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` looks up the closest ancestor instance of type `T` and is\nfunctionally equivalent to `BlocProvider.of<T>(context)`. `context.read` is most\ncommonly used for retrieving a bloc instance in order to add an event within\n`onPressed` callbacks.\n\n:::note\n\n`context.read<T>()` does not listen to `T` -- if the provided `Object` of type\n`T` changes, `context.read` will not trigger a widget rebuild.\n\n:::\n\n#### Usage\n\n✅ **DO** use `context.read` to add events in callbacks.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **AVOID** using `context.read` to retrieve state within a `build` method.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nThe above usage is error prone because the `Text` widget will not be rebuilt if\nthe state of the bloc changes.\n\n:::caution\n\nUse `BlocBuilder` or `context.watch` instead in order to rebuild in response to\nstate changes.\n\n:::\n\n### context.watch\n\nLike `context.read<T>()`, `context.watch<T>()` provides the closest ancestor\ninstance of type `T`, however it also listens to changes on the instance. It is\nfunctionally equivalent to `BlocProvider.of<T>(context, listen: true)`.\n\nIf the provided `Object` of type `T` changes, `context.watch` will trigger a\nrebuild.\n\n:::caution\n\n`context.watch` is only accessible within the `build` method of a\n`StatelessWidget` or `State` class.\n\n:::\n\n#### Usage\n\n✅ **DO** use `BlocBuilder` instead of `context.watch` to explicitly scope\nrebuilds.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Whenever the state changes, only the Text is rebuilt.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nAlternatively, use a `Builder` to scope rebuilds.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever the state changes, only the Text is rebuilt.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **DO** use `Builder` and `context.watch` as `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n❌ **AVOID** using `context.watch` when the parent widget in the `build` method\ndoesn't depend on the state.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsing `context.watch` at the root of the `build` method will result in the\nentire widget being rebuilt when the bloc state changes.\n\n:::\n\n### context.select\n\nJust like `context.watch<T>()`, `context.select<T, R>(R function(T value))`\nprovides the closest ancestor instance of type `T` and listens to changes on\n`T`. Unlike `context.watch`, `context.select` allows you listen for changes in a\nsmaller part of a state.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nThe above will only rebuild the widget when the property `name` of the\n`ProfileBloc`'s state changes.\n\n#### Usage\n\n✅ **DO** use `BlocSelector` instead of `context.select` to explicitly scope\nrebuilds.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Whenever the state.name changes, only the Text is rebuilt.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nAlternatively, use a `Builder` to scope rebuilds.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever state.name changes, only the Text is rebuilt.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **AVOID** using `context.select` when the parent widget in a build method\ndoesn't depend on the state.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state.value changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsing `context.select` at the root of the `build` method will result in the\nentire widget being rebuilt when the selection changes.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/fr/architecture.mdx",
    "content": "---\ntitle: Architecture\ndescription:\n  Vue d'ensemble des modèles d'architecture recommandés pour l'utilisation de\n  Bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nL'utilisation de la librairie bloc nous permet de séparer notre application en\ntrois couches :\n\n- Presentation\n- Logique Métier (Business Logic)\n- Data\n  - Repository\n  - Data Provider\n\nNous allons commencer avec la couche la plus basse (la plus éloignée de\nl'interfacer utilisateur) et remonter jusqu'à la couche de Présentation.\n\n## Couche Data\n\nLe rôle de la couche Data est de récupérer et de manipuler des données provenant\nd'une ou plusieurs sources.\n\nLa couche Data peut être divisée en deux parties :\n\n- Repository\n- Data Provider\n\nCette couche est le niveau le plus bas de l'application, elle interagit avec les\nbases de données, les requêtes réseau et d'autres sources de données\nasynchrones.\n\n### Data Provider\n\nLe rôle du Data Provider est de fournir des données brutes. Il doit être\ngénérique et polyvalent.\n\nLe Data Provider permet généralement d’interagir avec des API simple afin\nd'effectuer des opérations\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). Nous\npourrions avoir des méthodes `createData`, `readData`, `updateData`, et\n`deleteData` qui feront partie de notre couche Data.\n\n<DataProviderSnippet />\n\n### Repository\n\nLa couche Repository est un wrapper d'un ou plusieurs Data Provider avec\nlesquels la couche Bloc communique.\n\n<RepositorySnippet />\n\nComme vous pouvez le constater, notre couche Repository peut interagir avec\nplusieurs Data Provider et effectuer des transformations sur les données avant\nde transmettre le résultat à la couche Logic Métier.\n\n## Couche Logique Métier (Business Logic layer)\n\nLa fonction de la couche de Logique Métier est de répondre aux entrées provenant\nde la couche Présentation avec de nouveaux états. Cette couche peut dépendre\nd'un ou plusieurs Repository afin de récupérer les données nécessaires à la\nconstruction de l'état de l'application.\n\nConsidérez la couche Logique Métier comme le pont entre l'interface utilisateur\n(couche Présentation) et la couche Data. La couche Logique Métier est informée\ndes événements/actions de la couche Présentation et communique alors avec le\nRepository afin de construire un nouvel état que la couche Présentation pourra\nappliquer.\n\n<BusinessLogicComponentSnippet />\n\n### Communication de Bloc-à-Bloc\n\nBecause blocs expose streams, it may be tempting to make a bloc which listens to\nanother bloc. You should **not** do this. There are better alternatives than\nresorting to the code below: Comme les blocs mettent à disposition des flux, il\npeut être tentant de créer un bloc qui écoute un autre bloc. Vous ne devriez\n**pas** faire ça. Il existe de meilleures alternatives que le code ci-dessous :\n\n<BlocTightCouplingSnippet />\n\nBien que le code ci-dessus ne comporte pas d'erreurs, il présente un problème\nplus important : il crée une dépendance entre deux blocs.\n\nEn règle générale, il faut éviter à tout prix les dépendances entre deux entités\nde la même couche architecturale, car elles créent un couplage fort difficile à\nmaintenir. Étant donné que ces deux blocs résident tous les deux dans la couche\narchitecturale de la Logique Métier, aucun des deux blocs ne devraient connaître\nl'existence de l'un autre bloc.\n\n![Application Architecture Layers](~/assets/architecture/architecture.png)\n\nUn bloc ne doit recevoir des informations que par le biais d'événements et de\nRepository injectés (c'est-à-dire des Repository qui sont passés au bloc via son\nconstructeur).\n\nSi vous êtes dans une situation où un bloc doit répondre à un autre bloc, vous\navez deux autres options. Vous pouvez soit faire remonter le problème d'une\ncouche (couche de Présentation), soit le faire descendre d'une couche (couche\nDomain).\n\n#### Relier des Blocs à travers la couche Présentation\n\nVous pouvez utiliser un `BlocListener` pour écouter un bloc et ajouter un\névénement à un autre bloc lorsque le premier change.\n\n<BlocLooseCouplingPresentationSnippet />\n\nLe code ci-dessus empêche `SecondBloc` d'avoir besoin de connaître `FirstBloc`,\ncréant ainsi un couplage faible. L'application\n[flutter_weather](/fr/tutorials/flutter-weather)\n[utilise cette technique](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\npour changer le thème de l'application en fonction des informations\nmétéorologiques reçues.\n\nDans certains cas, il n'est pas préférable de coupler deux blocs dans la couche\nPrésentation. En revanche, il est souvent logique que deux blocs partagent la\nmême source de données et se mettent à jour lorsque ces données changent.\n\n#### Relier des Blocs à travers la couche Domain\n\nDeux blocs peuvent écouter un flux provenant d'un Repository et mettre à jour\nleur état indépendamment l'un de l'autre chaque fois que les données du\nRepository changent. L'utilisation de Repository réactifs pour maintenir la\nsynchronisation des états est courante dans les applications d'entreprise à\ngrande échelle.\n\nEn premier lieu, créez ou utilisez un Repository qui met à disposition un flux\nde données (`Stream`). Par exemple, le Repository suivant met à disposition un\n`Stream` infini comportant cinq idées d'applications.\n\n<AppIdeasRepositorySnippet />\n\nLe même Repository peut être injecté dans chaque bloc qui doit réagir aux\nnouvelles idées d'applications. Voici un `AppIdeaRankingBloc` qui crée un état\npour chaque idée d'application provenant du Repository ci-dessus :\n\n<AppIdeaRankingBlocSnippet />\n\nPour en savoir plus sur l'utilisation des flux avec Bloc, voir\n[How to use Bloc with streams and concurrency](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Couche Presentation\n\nLe rôle de la couche Presentation est de déterminer comment elle doit\ns'afficher, en fonction d'un ou plusieurs états de bloc. Elle doit également\ngérer les entrées de l'utilisateur et les événements du cycle de vie de\nl'application.\n\nLa plupart des flux d'applications commencent par un événement `AppStart` qui\ndéclenche la récupération des données qui vont être présentées à l'utilisateur.\n\nDans ce scénario, la couche Presentation provoquerait un événement `AppStart`.\n\nEn plus de cela, elle devra déterminer ce qu'il faut afficher à l'écran en\nfonction de l'état de la couche Bloc.\n\n<PresentationComponentSnippet />\n\nJusqu'à présent, bien que nous ayons vu quelques extraits de code, tout cela est\nresté assez théorique. Dans la section tutoriel, nous allons mettre en pratique\nces concepts en réalisant plusieurs exemples d'applications.\n"
  },
  {
    "path": "docs/src/content/docs/fr/getting-started.mdx",
    "content": "---\ntitle: Démarrer\ndescription: Tout le nécessaire pour débuter avec Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Packages\n\nL'écosystème bloc contient plusieurs packages listés ci-dessous :\n\n| Package                                                                                    | Description                 | Lien                                                                                                           |\n| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart Components      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Core Dart APIs              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers          | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | Testing APIs                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools          | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter Widgets             | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo Support           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Installation\n\n<InstallationTabs />\n\n:::note\n\nPour commencer à utiliser bloc, le [Dart SDK](https://dart.dev/get-dart) doit\npréalablement être installé sur votre machine.\n\n:::\n\n## Imports\n\nMaintenant que nous avons installé Bloc avec succès, nous pouvons créer notre\nfichier `main.dart` et importer le package `bloc`.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/fr/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Documentation officielle de la bibliothèque de gestion d'état Bloc. Supportée\n  pour Dart, Flutter et AngularDart. Inclut des exemples et des tutoriels.\nbanner:\n  content: |\n    ✨ Visite le\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Une bibliothèque de gestion d'états prévisibles pour Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Démarrer\n      link: /fr/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Voir sur GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Démarrer\" icon=\"rocket\">\n\t```sh\n\t# Ajoute un bloc ton projet\n\tdart pub add bloc\n\t```\n\nNotre [guide de démarrage](/fr/getting-started) possède des instructions étape\npar étape pour commencer à utiliser Bloc en quelques minutes seulement.\n\n</SplitCard>\n\n<Card title=\"Explorer avec des tutoriels\" icon=\"star\">\n\tComplète les [tutoriels officiels](/fr/tutorials/flutter-counter) pour\n\tapprendre les meilleures pratiques et créer une variété d'applications avec\n\tBloc.\n</Card>\n\n<Card title=\"Développer avec Bloc\" icon=\"laptop\">\n\tExplore des [applications\n\tdemo](https://github.com/felangel/bloc/tree/master/examples) de qualité et\n\tentièrement testées telles qu'un compteur, un minuteur, une liste infinie, une\n\tapplication de météo, une todo-list et plus encore!\n</Card>\n\n<ListCard title=\"Apprendre\" icon=\"open-book\">\n\n    - [Pourquoi utiliser Bloc?](/fr/why-bloc)\n    - [Concepts fondamentaux](/fr/bloc-concepts)\n    - [Architecture](/fr/architecture)\n    - [Tester](/fr/testing)\n    - [Conventions de nommage](/fr/naming-conventions)\n    - [FAQs](/fr/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Intégration\" icon=\"puzzle\">\n    - [Intégration pour VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Intégration pour IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Intégration pour Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Intégration de Mason CLI ](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Templates customisées](https://brickhub.dev/search?q=bloc)\n    - [Outils de développeur](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/fr/lint/index.mdx",
    "content": "---\ntitle: Vue d'ensemble du Linter\ndescription: Introduction à bloc_lint.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nLa vérification de code (linting) est un processus d'analyse statique du code\nqui permet d'identifier les bugs potentiels ainsi que les erreurs\nprogrammatiques et stylistiques.\n\nBloc dispose d'un linter intégré, qui peut être utilisé via votre IDE ou la\n[CLI `bloc`](https://pub.dev/packages/bloc_tools) avec la commande `bloc lint`.\n\nGrâce au linter bloc, vous pouvez améliorer la qualité de votre codebase et\nassurer la cohérence sans avoir à écrire une seule ligne de code.\n\nPar exemple, si vous importez accidentellement une dépendance Flutter dans votre\ncubit :\n\n<AvoidFlutterImportsWarningSnippet />\n\nS'il est correctement configuré, le linter bloc pointera du doigt l'`import` et\nproduira l'avertissement suivant.\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nDans les sections suivantes, nous verrons comment installer, configurer et\npersonnaliser le linter bloc pour que vous puissiez profiter des avantages de\nl'analyse statique.\n\n## Démarrage rapide\n\nCommencez à utiliser le linter bloc en quelques étapes simples et rapides.\n\n:::note\n\nPour commencer à utiliser bloc, vous devez avoir le\n[SDK Dart](https://dart.dev/get-dart) installé sur votre machine.\n\n:::\n\n1. Installez la [CLI bloc](https://pub.dev/packages/bloc_tools)\n\n   <InstallBlocToolsSnippet />\n\n1. Installez le [package bloc_lint](https://pub.dev/packages/bloc_lint)\n\n   <InstallBlocLintSnippet />\n\n1. Ajoutez un fichier `analysis_options.yaml` à la racine de votre projet avec\n   les règles recommandées\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. Exécutez le linter\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nFélicitations, c'est tout ce qu'il y avait à faire 🎉\n\nContinuez la lecture de cette documentation pour un aperçu plus approfondi de la\nconfiguration et de la personnalisation du linter bloc.\n"
  },
  {
    "path": "docs/src/content/docs/fr/lint/installation.mdx",
    "content": "---\ntitle: Installation du Linter\ndescription: Installation de bloc_lint.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## Outils en Ligne de Commande (CLI)\n\nPour utiliser le linter depuis la ligne de commande, installez\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) via la commande\nsuivante :\n\n<InstallBlocToolsSnippet />\n\nUne fois la CLI bloc installée, vous pouvez exécuter le linter bloc via la\ncommande `bloc lint` :\n\n<BlocToolsLintHelpOutputSnippet />\n\n## Set de Règles Recommandées\n\nPour installer le set de règles de lint recommandées, installez\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) comme dépendance de\ndéveloppement via la commande suivante : `package:bloc_lint`\n\n<InstallBlocLintSnippet />\n\nEnsuite, ajoutez un fichier à la racine de votre projet avec le set de règles\nrecommandées : `analysis_options.yaml`\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nSi nécessaire, vous pouvez inclure plusieurs sets de règles en les définissant\ncomme une liste :\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## Intégrations dans les IDE\n\nLes IDE suivants prennent officiellement en charge le linter bloc et le language\nserver (LSP) pour fournir des diagnostics instantanés directement dans votre\nIDE.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tLa prise en charge du [plugin Bloc pour\n\t\tVSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\test disponible à partir de la v6.8.0.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tLa prise en charge du [plugin Bloc pour\n\t\tIntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) est disponible à\n\t\tpartir de la v4.1.0.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/fr/testing.mdx",
    "content": "---\ntitle: Tester\ndescription: Les bases de l'écriture de tests pour vos blocs.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc a été conçu pour être extrêmement facile à tester. Dans cette section, nous\nallons voir comment tester un bloc de manière unitaire.\n\nPar souci de simplicité, nous allons écrire des tests pour le `CounterBloc` que\nnous avons créé dans [Core Concepts](/fr/bloc-concepts).\n\nPour rappel, l'implémentation du `CounterBloc` ressemble à ceci:\n\n<CounterBlocSnippet />\n\n## Configuration\n\nAvant de commencer à écrire nos tests, nous avons besoin d'ajouter des framework\nde test dans nos dépendances,\n\nNous allons ajouter les packages [test](https://pub.dev/packages/test) et\n[bloc_test](https://pub.dev/packages/bloc_test) à notre projet.\n\n<AddDevDependenciesSnippet />\n\n## Tester\n\nCommençons par créer le fichier `counter_bloc_test.dart` afin de tester notre\n`CounterBloc` et importons le package de test.\n\n<CounterBlocTestImportsSnippet />\n\nEnsuite, nous devons créer notre `main` ainsi que notre groupe de tests.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nLes groupes servent à organiser des tests unitaires et à créer un contexte dans\nlequel vous pouvez partager un `setUp` et un `tearDown` communs à tous les tests\ndu groupe.\n\n:::\n\nCommençons par créer une instance de notre `CounterBloc` qui sera utilisée dans\ntous nos tests.\n\n<CounterBlocTestSetupSnippet />\n\nMaintenant, nous pouvons commencer à écrire des tests unitaires.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nNous pouvons exécuter tous nos tests avec la commande `dart test`.\n\n:::\n\nÀ ce stade, nous devrions avoir notre premier test réussi! Écrivons maintenant\ndes tests plus complexes en utilisant le package\n[bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nNous devrions être en mesure d'exécuter les tests et de constater qu'ils passent\ntous.\n\nC'est aussi simple que ça, tester devrait être un jeu d'enfant et nous devrions\nnous sentir en confiance lorsque nous apportons des modifications et que nous\nrefactorisons notre code.\n\nVous pouvez vous référer à l'application\n[Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\npour un exemple d'application entièrement testée.\n"
  },
  {
    "path": "docs/src/content/docs/fr/why-bloc.mdx",
    "content": "---\ntitle: Pourquoi utiliser Bloc?\ndescription:\n  Un aperçu de ce qui fait de Bloc une solution solide pour la gestion d’états.\nsidebar:\n  order: 1\n---\n\nBloc permet de facilement séparer les composants visuels de la logique métier,\nrendant votre code `rapide`, `facile à tester` et `réutilisable`.\n\nLorsqu'on développe des application de qualité destinées à être utilisé en\nproduction, la gestion des états devient critique.\n\nEn tant que développeurs, nous souhaitons :\n\n- connaître l'état de notre application à tout moment.\n- tester facilement chaque cas pour nous assurer que notre application réagit\n  correctement.\n- enregistrer chaque interaction utilisateur dans notre application\n- travailler de manière aussi efficace que possible et réutiliser des composants\n  au sein de notre application ainsi que dans des applications différentes.\n- permettre à de nombreux développeurs de travailler en harmonie en suivant des\n  modèles et des conventions communes.\n- développer des applications performantes et réactives.\n\nBloc a été conçu pour répondre à tous ces besoins et bien plus encore.\n\nIl existe beaucoup de solutions pour gérer les états et décider laquelle\nutiliser peut être une tâche laborieuse. Il n'y a pas de solution parfaite! Ce\nqui est important est de choisir celle qui correspond le mieux à ton équipe et à\nton projet.\n\nBloc a été conçu avec trois principes fondamentaux en tête :\n\n- **Simple:** Facile à comprendre et utilisable par des développeurs de niveaux\n  variés.\n- **Puissant:** Aide à faire des applications complexes en les assemblant à\n  partir de composants plus simples.\n- **Testable:** Permet de tester facilement tous les aspects d'une application\n  afin de pouvoir itérer en toute confiance.\n\nGlobalement, Bloc tente de rendre les changements d'état prévisibles en\nréglementant le moment où un changement d'état peut se produire et en imposant\nune unique façon de changer d'état dans l'ensemble d'une application.\n"
  },
  {
    "path": "docs/src/content/docs/getting-started.mdx",
    "content": "---\ntitle: Getting Started\ndescription: Everything you need to start building with Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Packages\n\nThe bloc ecosystem consists of multiple packages listed below:\n\n| Package                                                                                    | Description                 | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart Components      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Core Dart APIs              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers          | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | Testing APIs                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools          | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter Widgets             | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo Support           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Installation\n\n<InstallationTabs />\n\n:::note\n\nIn order to start using bloc you must have the\n[Dart SDK](https://dart.dev/get-dart) installed on your machine.\n\n:::\n\n## Imports\n\nNow that we have successfully installed bloc, we can create our `main.dart` and\nimport the respective `bloc` package.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ Visit the\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: A predictable state management library for Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Get Started\n      link: /getting-started/\n      variant: primary\n      icon: rocket\n    - text: View on GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Get Started\" icon=\"rocket\">\n\t```sh\n\t# Add bloc to your project.\n\tdart pub add bloc\n\t```\n\nOur [getting started guide](/getting-started) has step-by-step instructions on\nhow to start using Bloc in just a few minutes.\n\n</SplitCard>\n\n<Card title=\"Take a guided tour\" icon=\"star\">\n\tComplete [the official tutorials](/tutorials/flutter-counter) to learn best\n\tpractices and build a variety of different apps powered by Bloc.\n</Card>\n\n<Card title=\"Build with Bloc\" icon=\"laptop\">\n\tExplore high quality, fully tested [sample\n\tapps](https://github.com/felangel/bloc/tree/master/examples) like the counter,\n\ttimer, infinite list, weather, todo and more!\n</Card>\n\n<ListCard title=\"Learn\" icon=\"open-book\">\n\n    - [Why Bloc?](/why-bloc)\n    - [Core Concepts](/bloc-concepts)\n    - [Architecture](/architecture)\n    - [Testing](/testing)\n    - [Naming Conventions](/naming-conventions)\n    - [FAQs](/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integrations\" icon=\"puzzle\">\n    - [VSCode Integration](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ Integration](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim Integration](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI Integration](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Custom Templates](https://brickhub.dev/search?q=bloc)\n    - [Developer Tools](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/it/architecture.mdx",
    "content": "---\ntitle: Architettura\ndescription:\n  Panoramica dei modelli architetturali consigliati quando si utilizza bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Architettura Bloc](~/assets/concepts/bloc_architecture_full.png)\n\nL'utilizzo della libreria bloc ci consente di separare la nostra applicazione in\ntre livelli:\n\n- Presentazione\n- Logica Applicativa\n- Dati\n  - \"Repository\"\n  - \"Data Provider\"\n\nInizieremo dal livello più basso (più lontano dall'interfaccia utente) e\nrisaliremo fino al livello di presentazione.\n\n## Livello Dati\n\nHa il compito di recuperare e manipolare i dati da uno o più sorgenti.\n\nPuò essere suddiviso in due parti:\n\n- \"Repository\"\n- \"Data Provider\"\n\nQuesto livello è il più basso dell'applicazione e interagisce con i database, le\nrichieste di rete e altre sorgenti di dati asincrone.\n\n### Data Provider\n\nIl compito del \"Data Provider\" è fornire dati grezzi. Dovrebbe essere generico e\nversatile.\n\nDi solito espone API semplici per eseguire operazioni\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). Potremmo\navere metodi come `createData`, `readData`, `updateData` e `deleteData` come\nparte del nostro livello dati.\n\n<DataProviderSnippet />\n\n### Repository\n\nIl livello \"Repository\" funge da \"wrapper\" attorno a uno o più \"Data Provider\" e\ncomunica con il livello di logica applicativa.\n\n<RepositorySnippet />\n\nCome puoi vedere, il nostro livello repository può interagire con più \"Data\nProvider\" ed eseguire trasformazioni sui dati prima di passare il risultato al\nlivello della logica applicativa.\n\n## Livello Logica Applicativa\n\nIl livello di logica applicativa risponde agli input provenienti dal livello di\npresentazione emettendo nuovi stati. Può dipendere da uno o più repository per\nrecuperare i dati necessari a costruire lo stato dell'applicazione.\n\nPensa a questo livello come al ponte tra l'interfaccia utente (livello di\npresentazione) e il livello dati. La logica applicativa riceve notifiche di\neventi e azioni dalla presentazione e poi comunica con il repository per\ncostruire un nuovo stato che il livello di presentazione potrà poi utilizzare.\n\n<BusinessLogicComponentSnippet />\n\n### Comunicazione tra Bloc\n\nPoiché i bloc espongono stream, potrebbe essere allettante creare un bloc che\nascolta un altro bloc. **Non dovresti** farlo. Esistono alternative migliori\nrispetto al codice riportato di seguito:\n\n<BlocTightCouplingSnippet />\n\nAnche se il codice sopra è privo di errori (e gestisce automaticamente la\ncancellazione delle dipendenze), presenta un problema più grave: crea una\ndipendenza tra due bloc.\n\nIn generale, le dipendenze tra entità dello stesso livello architetturale\ndovrebbero essere evitate a tutti i costi, poiché creano un accoppiamento\nstretto difficile da mantenere. Poiché i bloc risiedono nel livello di logica\napplicativa, nessun bloc dovrebbe conoscere altri bloc.\n\n![Layer applicativo](~/assets/architecture/architecture.png)\n\nUn bloc dovrebbe ricevere informazioni solo attraverso eventi e da repository\niniettati (ovvero, repository forniti al bloc tramite il suo costruttore).\n\nSe ti trovi in una situazione in cui un bloc deve rispondere a un altro bloc,\nhai due alternative. Puoi spostare il problema un livello più in alto (nel\nlivello di presentazione) oppure un livello più in basso (nel livello di\ndominio).\n\n#### Collegare i Bloc attraverso la Presentazione\n\nPuoi usare un `BlocListener` per ascoltare un bloc e aggiungere un evento a un\naltro bloc ogni volta che il primo bloc cambia.\n\n<BlocLooseCouplingPresentationSnippet />\n\nIl codice sopra impedisce a `SecondBloc` di dover conoscere `FirstBloc`,\nfavorendo un accoppiamento lasco. L'applicazione\n[flutter_weather](/it/tutorials/flutter-weather)\n[usa questa tecnica](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nper cambiare il tema dell'applicazione in base alle informazioni meteorologiche\nricevute.\n\nIn alcune situazioni, potresti non voler accoppiare due bloc nel livello di\npresentazione. In questi casi può avere senso che due bloc condividano la stessa\nsorgente di dati e si aggiornino ogni volta che i dati cambiano.\n\n#### Collegare i Bloc attraverso il Dominio\n\nDue bloc possono ascoltare uno stream da un repository e aggiornare i loro stati\nindipendentemente l'uno dall'altro ogni volta che i dati del repository\ncambiano. L'uso di repository reattivi per mantenere lo stato sincronizzato è\ncomune nelle applicazioni enterprise su larga scala.\n\nPrima, crea o usa un repository che fornisce uno `Stream` di dati. Ad esempio,\nil seguente repository espone uno stream infinito di \"idee\":\n\n<AppIdeasRepositorySnippet />\n\nLo stesso repository può essere iniettato in ogni bloc che deve reagire a nuove\n\"idee\". Di seguito è riportato un `AppIdeaRankingBloc` che emette uno stato per\nogni \"idea\" in arrivo dal repository sopra:\n\n<AppIdeaRankingBlocSnippet />\n\nPer maggiori informazioni sull'uso degli stream con Bloc, consulta\n[Come usare Bloc con stream e concorrenza](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Livello di Presentazione\n\nIl livello di presentazione ha il compito di capire come renderizzare se stesso\nin base a uno o più stati del bloc. Inoltre, dovrebbe gestire l'input\ndell'utente e gli eventi del ciclo di vita dell'applicazione.\n\nLa maggior parte dei flussi delle applicazioni inizierà con un evento `AppStart`\nche attiva l'applicazione per recuperare alcuni dati da presentare all'utente.\n\nIn questo scenario, il livello di presentazione aggiungerebbe un evento\n`AppStart`.\n\nInoltre, il livello di presentazione dovrà capire cosa renderizzare sullo\nschermo in base allo stato proveniente dal bloc.\n\n<PresentationComponentSnippet />\n\nFinora, anche se abbiamo visto alcuni frammenti di codice, siamo rimasti\nabbastanza ad alto livello. Nella sezione tutorial metteremo tutto insieme\nmentre costruiremo diversi esempi di app.\n"
  },
  {
    "path": "docs/src/content/docs/it/bloc-concepts.mdx",
    "content": "---\ntitle: Concetti Bloc\ndescription: Una panoramica dei concetti fondamentali per package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nAssicurati di leggere attentamente le seguenti sezioni prima di lavorare con\n[`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nCi sono diversi concetti fondamentali che sono essenziali per comprendere come\nusare la libreria bloc.\n\nNelle sezioni seguenti, discuteremo ciascuno di essi in dettaglio e lavoreremo\nsu come si applicherebbero ad un'app contatore.\n\n## Stream\n\n:::note\n\nConsulta la\n[Documentazione ufficiale di Dart](https://dart.dev/tutorials/language/streams)\nper maggiori informazioni riguardo gli `Stream`.\n\n:::\n\nUno stream è una sequenza di dati asincroni.\n\nPer usare la libreria bloc, è fondamentale avere una comprensione di base degli\n`Stream` e di come funzionano.\n\nSe non hai familiarità con `Stream`, pensa semplicemente ad un tubo con\ndell'acqua che scorre attraverso di esso. Il tubo è lo `Stream` e l'acqua sono i\ndati asincroni.\n\nPossiamo creare uno `Stream` in Dart scrivendo una funzione `async*` (generatore\nasincrono).\n\n<CountStreamSnippet />\n\nContrassegnando una funzione come `async*` siamo in grado di usare la parola\nchiave `yield` e restituire uno `Stream` di dati. Nell'esempio sopra, stiamo\nrestituendo uno `Stream` di interi che arriva fino al valore del parametro\n`max`.\n\nOgni volta che facciamo `yield` in una funzione `async*` stiamo spingendo quel\npezzo di dati attraverso lo `Stream`.\n\nPossiamo consumare lo `Stream` sopra in diversi modi. Se volessimo scrivere una\nfunzione per restituire la somma di uno `Stream` di interi potremmo scrivere\nqualcosa del tipo:\n\n<SumStreamSnippet />\n\nContrassegnando la funzione sopra come `async` siamo in grado di usare la parola\nchiave `await` e restituire un `Future` di interi. In questo esempio, stiamo\naspettando ogni valore dello stream per poi restituirne la somma.\n\nInfine possiamo mettere tutto insieme così:\n\n<StreamsMainSnippet />\n\nOra che abbiamo compreso le basi del funzionamento degli `Stream` in Dart, siamo\npronti a scoprire il componente core del pacchetto bloc: il `Cubit`.\n\n## Cubit\n\nUn `Cubit` è una classe che estende `BlocBase` e può essere estesa per gestire\nqualsiasi tipo di stato.\n\n![Architettura Cubit](~/assets/concepts/cubit_architecture_full.png)\n\nPuò esporre funzioni che possono essere invocate per attivare cambiamenti di\nstato.\n\nGli stati sono l'output di un `Cubit` e rappresentano una parte dello stato\ndella tua applicazione. I componenti UI possono essere notificati degli stati e\nridisegnare porzioni di se stessi in base allo stato corrente.\n\n:::note\n\nPer maggiori informazioni sulle origini di `Cubit` consulta\n[la seguente \"issue\"](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Creare un Cubit\n\nPossiamo creare un `CounterCubit` così:\n\n<CounterCubitSnippet />\n\nQuando creiamo un `Cubit`, dobbiamo definire il tipo di stato che il `Cubit`\ngestirà. Nel caso del `CounterCubit` sopra, lo stato può essere rappresentato\ntramite un `int` ma in casi più complessi potrebbe essere necessario definire\nuna classe invece di usare un tipo primitivo.\n\nLa seconda cosa che dobbiamo fare quando creiamo un `Cubit` è specificare lo\nstato iniziale. Possiamo farlo chiamando `super` con il valore dello stato\niniziale. Nel frammento sopra, viene inizializzato lo stato internamente a `0`\nma possiamo anche permettere al `Cubit` di essere più flessibile accettando un\nvalore esterno:\n\n<CounterCubitInitialStateSnippet />\n\nQuesto ci permetterebbe di istanziare istanze di `CounterCubit` con diversi\nstati iniziali come:\n\n<CounterCubitInstantiationSnippet />\n\n### Cambiamenti di Stato del Cubit\n\nOgni `Cubit` ha la capacità di emettere un nuovo stato tramite `emit`.\n\n<CounterCubitIncrementSnippet />\n\nNel frammento sopra, il `CounterCubit` espone un metodo pubblico chiamato\n`increment` che può essere chiamato esternamente per notificare il\n`CounterCubit` di incrementare il suo stato. Quando `increment` viene chiamato,\npossiamo accedere allo stato corrente del `Cubit` tramite il getter `state` ed\nemettere un nuovo stato aggiungendo 1 a quello corrente.\n\n:::caution\n\nIl metodo `emit` è protetto, il che significa che dovrebbe essere usato solo\nall'interno di un `Cubit`.\n\n:::\n\n### Usare un Cubit\n\nOra che abbiamo implementato `CounterCubit` lo possiamo mettere in uso!\n\n#### Uso semplice\n\n<CounterCubitBasicUsageSnippet />\n\nNel frammento sopra, iniziamo creando un'istanza del `CounterCubit`. Poi\nstampiamo lo stato corrente del cubit che è lo stato iniziale (dato che nessuno\nstato nuovo è stato ancora emesso). Successivamente, chiamiamo la funzione\n`increment` per attivare un cambiamento di stato. Infine, stampiamo di nuovo lo\nstato del `Cubit` che è passato da `0` a `1` e chiamiamo `close` sul `Cubit` per\nchiudere lo stream di stato interno.\n\n#### Uso con Stream\n\n`Cubit` espone uno `Stream` che ci permette di ricevere aggiornamenti di stato\nin tempo reale:\n\n<CounterCubitStreamUsageSnippet />\n\nNello snippet sopra, effettuiamo la sottoscrizione al `CounterCubit` stampando a\nvideo ogni variazione di stato. Invochiamo quindi la funzione `increment`, che\nemetterà un nuovo stato. Infine, chiamiamo `cancel` sulla `subscription` quando\nnon desideriamo più ricevere aggiornamenti e chiudiamo il `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` è aggiunto per questo esempio per evitare\ndi cancellare la sottoscrizione immediatamente.\n\n:::\n\n:::caution\n\nInvocando `listen` su un `Cubit`, si riceveranno esclusivamente i cambiamenti di\nstato che avvengono dopo il momento della sottoscrizione.\n\n:::\n\n### Osservare un Cubit\n\nQuando un `Cubit` emette un nuovo stato, si verifica un `Change`. Possiamo\nosservare tutti i cambiamenti per un dato `Cubit` sovrascrivendo `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nPossiamo poi interagire con il `Cubit` e osservare tutti i cambiamenti emessi\nnella console.\n\n<CounterCubitOnChangeUsageSnippet />\n\nL'esempio sopra produrrebbe:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nUn `Change` si verifica appena prima che lo stato del `Cubit` sia aggiornato. Un\n`Change` consiste di `currentState` e `nextState`.\n\n:::\n\n#### BlocObserver\n\nUn vantaggio aggiuntivo dell'usare la libreria bloc è che possiamo avere accesso\na tutti i `Changes` in un unico posto. Anche se in questa applicazione abbiamo\nsolo un `Cubit`, è abbastanza comune in applicazioni più grandi avere molti\n`Cubit` che gestiscono diverse parti dello stato dell'applicazione.\n\nSe vogliamo essere in grado di fare qualcosa in risposta a tutti i `Changes`\npossiamo semplicemente creare il nostro `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nTutto quello che dobbiamo fare è estendere `BlocObserver` e sovrascrivere il\nmetodo `onChange`.\n\n:::\n\nPer usare il `SimpleBlocObserver`, dobbiamo solo modificare la funzione `main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nIl frammento sopra produrrebbe quindi:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nL'override interno di `onChange` viene chiamato per primo, che chiama\n`super.onChange` notificando l'`onChange` nel `BlocObserver`.\n\n:::\n\n:::tip\n\nIn `BlocObserver` abbiamo accesso all'istanza del `Cubit` in aggiunta al\n`Change` stesso.\n\n:::\n\n### Gestione degli Errori del Cubit\n\nOgni `Cubit` ha un metodo `addError` che può essere usato per indicare che si è\nverificato un errore.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` può essere sovrascritto all'interno del `Cubit` per gestire tutti gli\nerrori per un `Cubit` specifico.\n\n:::\n\n`onError` può anche essere sovrascritto in `BlocObserver` per gestire tutti gli\nerrori segnalati globalmente.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nSe eseguiamo di nuovo lo stesso programma dovremmo vedere il seguente output:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nUn `Bloc` è una classe più avanzata che si basa su `eventi` per attivare\ncambiamenti di `stato` piuttosto che funzioni. `Bloc` estende anche `BlocBase`\nil che significa che ha un'API pubblica simile a `Cubit`. Tuttavia, piuttosto\nche chiamare una `funzione` su un `Bloc` ed emettere direttamente un nuovo\n`stato`, i `Bloc` ricevono `eventi` e convertono gli `eventi` in arrivo in\n`stati` in uscita.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Creare un Bloc\n\nCreare un `Bloc` è simile a creare un `Cubit` tranne che, oltre allo stato,\ndobbiamo anche definire l'evento che il `Bloc` sarà in grado di processare.\n\nGli eventi sono l'input di un Bloc. Sono comunemente aggiunti in risposta a\ninterazioni dell'utente come la pressione di pulsanti o eventi del ciclo di vita\ncome il caricamenti di una pagina.\n\n<CounterBlocSnippet />\n\nProprio come quando creiamo il `CounterCubit`, dobbiamo specificare uno stato\niniziale attraverso `super`.\n\n### Cambiamenti di Stato del Bloc\n\n`Bloc`, a differenza delle funzioni in `Cubit`, richiede la registrazione dei\ngestori di eventi tramite l'API `on<Event>`. Un gestore di eventi è responsabile\ndella conversione di qualsiasi evento in entrata in zero o più stati in uscita.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nUn `EventHandler` ha accesso all'evento aggiunto così come a un `Emitter` che\npuò essere usato per emettere zero o più stati in risposta all'evento in\nentrata.\n\n:::\n\nPossiamo poi aggiornare l'`EventHandler` per gestire l'evento\n`CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nNel frammento sopra, abbiamo registrato un `EventHandler` per gestire tutti gli\neventi `CounterIncrementPressed`. Per ogni evento `CounterIncrementPressed` in\narrivo possiamo accedere allo stato corrente del bloc tramite il getter `state`\ned emettere `emit(state + 1)`.\n\n:::note\n\nPoiché la classe `Bloc` estende `BlocBase`, abbiamo accesso allo stato corrente\ndel bloc in qualsiasi momento tramite il getter `state`, proprio come in\n`Cubit`.\n\n:::\n\n:::caution\n\nI Bloc non dovrebbero mai emettere direttamente nuovi stati (`emit`). Invece,\nogni cambiamento di stato deve essere emesso in risposta a un evento in arrivo\nall'interno di un `EventHandler`.\n\n:::\n\n:::caution\n\nSia i bloc che i cubit ignoreranno stati duplicati. Se emettiamo\n`State nextState` dove `state == nextState`, allora non si verificherà alcun\ncambiamento di stato.\n\n:::\n\n### Usare un Bloc\n\nA questo punto, possiamo creare un'istanza del nostro `CounterBloc` e metterlo\nin uso!\n\n#### Uso Base\n\n<CounterBlocUsageSnippet />\n\nNel frammento sopra, iniziamo creando un'istanza del `CounterBloc`. Poi\nstampiamo lo stato corrente del `Bloc` che è lo stato iniziale (dato che nessuno\nstato nuovo è stato ancora emesso). Successivamente, aggiungiamo l'evento\n`CounterIncrementPressed` per attivare un cambiamento di stato. Infine,\nstampiamo di nuovo lo stato del `Bloc` che è passato da `0` a `1` e chiamiamo\n`close` sul `Bloc` per chiudere lo stream di stato interno.\n\n:::note\n\n`await Future.delayed(Duration.zero)` è aggiunto per assicurarci di aspettare la\nprossima iterazione dell'event-loop (permettendo all'`EventHandler` di\nprocessare l'evento).\n\n:::\n\n#### Uso con Stream\n\nProprio come con `Cubit`, un `Bloc` è un tipo speciale di `Stream`, il che\nsignifica che possiamo anche sottoscriverci a un `Bloc` per aggiornamenti in\ntempo reale del suo stato:\n\n<CounterBlocStreamUsageSnippet />\n\nNel frammento sopra, ci stiamo sottoscrivendo al `CounterBloc` e chiamando\n`print` su ogni cambiamento di stato. Poi stiamo aggiungendo l'evento\n`CounterIncrementPressed` che attiva l'`EventHandler`\n`on<CounterIncrementPressed>` ed emette un nuovo stato. Infine, stiamo chiamando\n`cancel` sulla sottoscrizione quando non vogliamo più ricevere aggiornamenti e\nchiudendo il `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` è aggiunto per questo esempio per evitare\ndi cancellare la sottoscrizione immediatamente.\n\n:::\n\n### Osservare un Bloc\n\nPoiché `Bloc` estende `BlocBase`, possiamo osservare tutti i cambiamenti di\nstato per un `Bloc` usando `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nPossiamo poi aggiornare `main.dart` a:\n\n<CounterBlocOnChangeUsageSnippet />\n\nOra se eseguiamo il frammento sopra, l'output sarà:\n\n<CounterBlocOnChangeOutputSnippet />\n\nUn differenza chiave tra `Bloc` e `Cubit` è che poiché `Bloc` è orientato agli\neventi, siamo anche in grado di catturare informazioni su cosa ha attivato il\ncambiamento di stato.\n\nPossiamo farlo sovrascrivendo `onTransition`.\n\nIl cambiamento da uno stato a un altro è chiamato `Transition`. Una `Transition`\nconsiste dello stato corrente, dell'evento e dello stato successivo.\n\n<CounterBlocOnTransitionSnippet />\n\nSe poi rieseguiamo lo stesso `main.dart` di prima, dovremmo vedere il seguente\noutput:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` viene invocato prima di `onChange` e contiene l'evento che ha\nattivato il cambiamento da `currentState` a `nextState`.\n\n:::\n\n#### BlocObserver\n\nProprio come prima, possiamo sovrascrivere `onTransition` in un `BlocObserver`\npersonalizzato per osservare tutte le transizioni che si verificano da un unico\nposto.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nPossiamo inizializzare il `SimpleBlocObserver` proprio come prima:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nOra se eseguiamo il frammento sopra, l'output dovrebbe essere:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` viene invocato per primo (locale prima di globale) seguito da\n`onChange`.\n\n:::\n\nUn'altra caratteristica unica delle istanze di `Bloc` è che ci permettono di\nsovrascrivere `onEvent` che viene chiamato ogni volta che un nuovo evento viene\naggiunto al `Bloc`. Proprio come con `onChange` e `onTransition`, `onEvent` può\nessere sovrascritto localmente così come globalmente.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nPossiamo eseguire lo stesso `main.dart` di prima e dovremmo vedere il seguente\noutput:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` viene chiamato non appena l'evento viene aggiunto. L'`onEvent` locale\nviene invocato prima dell'`onEvent` globale in `BlocObserver`.\n\n:::\n\n### Gestione degli Errori del Bloc\n\nProprio come con `Cubit`, ogni `Bloc` ha un metodo `addError` e `onError`.\nPossiamo indicare che si è verificato un errore chiamando `addError` da\nqualsiasi punto all'interno del nostro `Bloc`. Possiamo poi reagire a tutti gli\nerrori sovrascrivendo `onError` proprio come con `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nSe rieseguiamo lo stesso `main.dart` di prima, possiamo vedere come appare\nquando viene segnalato un errore:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nL'`onError` locale viene invocato per primo seguito da quello globale presente\nin `BlocObserver`.\n\n:::\n\n:::note\n\n`onError` e `onChange` funzionano esattamente allo stesso modo per entrambe le\nistanze `Bloc` e `Cubit`.\n\n:::\n\n:::caution\n\nQualsiasi eccezione non gestita che si verifica all'interno di un `EventHandler`\nviene anche segnalata a `onError`.\n\n:::\n\n## Cubit vs. Bloc\n\nOra che abbiamo coperto le basi delle classi `Cubit` e `Bloc`, potresti\nchiederti quando dovresti usare `Cubit` e quando dovresti usare `Bloc`.\n\n### Vantaggi del Cubit\n\n#### Semplicità\n\nUno dei maggiori vantaggi dell'usare `Cubit` è la semplicità. Quando creiamo un\n`Cubit`, dobbiamo solo definire lo stato così come le funzioni che vogliamo\nesporre per cambiare lo stato. In confronto, quando creiamo un `Bloc`, dobbiamo\ndefinire gli stati, gli eventi e l'implementazione dell'`EventHandler`. Questo\nrende `Cubit` più facile da comprendere e richiede la scrittura di meno codice.\n\nOra diamo un'occhiata alle due implementazioni del `Counter`:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nL'implementazione del `Cubit` è più concisa e invece di definire eventi\nseparatamente, le funzioni agiscono come eventi. Inoltre, quando usiamo un\n`Cubit`, possiamo semplicemente chiamare `emit` da qualsiasi punto per attivare\nun cambiamento di stato.\n\n### Vantaggi del Bloc\n\n#### Tracciabilità\n\nUno dei maggiori vantaggi dell'usare `Bloc` è conoscere la sequenza dei\ncambiamenti di stato così come esattamente cosa ha attivato quei cambiamenti.\nPer gli stati cruciali per il funzionamento dell'applicazione, può essere molto\nvantaggioso adottare un approccio orientato agli eventi, così da poter tracciare\nogni evento oltre alle semplici variazioni di stato.\n\nUn caso d'uso comune potrebbe essere gestire `AuthenticationState`. Per\nsemplicità, diciamo che possiamo rappresentare `AuthenticationState` tramite un\n`enum`:\n\n<AuthenticationStateSnippet />\n\nPotrebbero esserci molte ragioni per cui lo stato dell'applicazione potrebbe\ncambiare da `authenticated` a `unauthenticated`. Ad esempio, l'utente potrebbe\naver toccato un pulsante di logout e richiesto di essere disconnesso\ndall'applicazione. D'altra parte, forse il token di accesso dell'utente è stato\nrevocato e sono stati forzatamente disconnessi. Quando usiamo `Bloc` possiamo\ntracciare chiaramente come lo stato dell'applicazione è arrivato a un certo\nstato.\n\n<AuthenticationTransitionSnippet />\n\nLa `Transition` sopra ci dà tutte le informazioni di cui abbiamo bisogno per\ncapire perché lo stato è cambiato. Se avessimo usato un `Cubit` per gestire\nl'`AuthenticationState`, i nostri log sarebbero:\n\n<AuthenticationChangeSnippet />\n\nQuesto ci dice che l'utente è stato disconnesso ma non spiega il perché, il che\npotrebbe essere critico per il debug e la comprensione di come lo stato dell'\napplicazione sta cambiando nel tempo.\n\n#### Trasformazioni Avanzate degli Eventi\n\nUn altro ambito in cui `Bloc` eccelle rispetto a `Cubit` è quando dobbiamo\nsfruttare operatori reattivi come `buffer`, `debounceTime`, `throttle`, ecc.\n\n:::tip\n\nVedi [`package:stream_transform`](https://pub.dev/packages/stream_transform) e\n[`package:rxdart`](https://pub.dev/packages/rxdart) per i trasformatori di\nstream.\n\n:::\n\n`Bloc` ha un `sink` di eventi che ci permette di controllare e trasformare il\nflusso in arrivo di eventi.\n\nPer esempio, nella creazione di una funzionalità di ricerca real-time, vorremmo\nprobabilmente limitare la frequenza delle chiamate al backend (tramite\n`debounce`). Questo serve a non superare le soglie di traffico consentite e a\nridurre il carico computazionale e i costi dell'infrastruttura.\n\nCon `Bloc` possiamo fornire un `EventTransformer` personalizzato per cambiare il\nmodo in cui gli eventi in arrivo sono processati dal `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nCon il codice sopra, possiamo facilmente applicare un `debounce` degli eventi in\narrivo con pochissimo codice aggiuntivo.\n\n:::tip\n\nDai un'occhiata a\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) per una\nraccolta di trasformatori di eventi che implementano strategie standard già\nottimizzate.\n\n:::\n\nNel dubbio, parti con un `Cubit`; potrai sempre evolverlo in un `Bloc` in\nfuturo, man mano che le esigenze del progetto aumentano.\n"
  },
  {
    "path": "docs/src/content/docs/it/faqs.mdx",
    "content": "---\ntitle: FAQ\ndescription: Risposte alle domande frequenti riguardo la libreria bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## Lo stato non si aggiorna\n\n❔ **Domanda**: Sto emettendo uno stato nel mio bloc ma l'interfaccia utente non\nsi aggiorna. Cosa sto sbagliando?\n\n💡 **Risposta**: Se stai usando Equatable assicurati di passare tutte le\nproprietà al getter `props`.\n\n✅ **Buono**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **Errato**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nInoltre, assicurati di emettere una nuova istanza dello stato nel tuo bloc.\n\n✅ **Buono**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **Errato**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nLe proprietà `Equatable` dovrebbero sempre essere copiate piuttosto che\nmodificate. Se una classe `Equatable` contiene una `List` o `Map` come\nproprietà, assicurati di usare `List.of` o `Map.of` rispettivamente per\nassicurarti che l'uguaglianza sia valutata in base ai valori delle proprietà\npiuttosto che al riferimento.\n\n:::\n\n## Quando Usare Equatable\n\n❔**Domanda**: Quando dovrei usare Equatable?\n\n💡**Risposta**:\n\n<EquatableEmitSnippet />\n\nNello scenario sopra, se `StateA` estende `Equatable` si verificherà solo un\ncambiamento di stato (la seconda emissione sarà ignorata). In generale, dovresti\nusare `Equatable` se vuoi ottimizzare il tuo codice per ridurre il numero di\naggiornamenti. Non dovresti usare `Equatable` se vuoi che lo stesso stato\nconsecutivo attivi più transizioni.\n\nInoltre, usare `Equatable` rende molto più facile testare i bloc poiché possiamo\naspettarci istanze specifiche degli stati del bloc piuttosto che usare\n`Matchers` o `Predicates`.\n\n<EquatableBlocTestSnippet />\n\nSenza `Equatable` il test sopra fallirebbe e dovrebbe essere riscritto così:\n\n<NoEquatableBlocTestSnippet />\n\n## Gestione degli Errori\n\n❔ **Domanda**: Come posso gestire un errore continuando a mostrare i dati\nprecedenti?\n\n💡 **Risposta**:\n\nQuesto dipende molto da come lo stato del bloc è stato modellato. Nei casi in\ncui i dati dovrebbero essere conservati anche in presenza di un errore,\nconsidera l'uso di una singola classe per lo stato.\n\n<SingleStateSnippet />\n\nQuesto permetterà ai widget di avere accesso alle proprietà `data` e `error`\ncontemporaneamente e il bloc può usare `state.copyWith` per conservare i vecchi\ndati anche quando si è verificato un errore.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Domanda**: Qual è la differenza tra Bloc e Redux?\n\n💡 **Risposta**:\n\nBLoC è un modello di progettazione basato sulle seguenti regole:\n\n1. Input e Output del BLoC sono semplici Stream e Sink;\n2. Le dipendenze devono essere iniettabili e indipendenti dalla piattaforma;\n3. Non è consentita alcuna ramificazione sulla base della piattaforma;\n4. L'implementazione può essere qualsiasi cosa tu voglia purché segua le regole\n   sopra indicate.\n\nLe linee guida UI sono:\n\n1. Ogni componente \"abbastanza complesso\" ha un BLoC corrispondente;\n2. I componenti dovrebbero inviare input \"così come sono\" (non deve elaborare,\n   filtrare o trasformare il dato);\n3. I componenti dovrebbero mostrare output il più vicino possibile a \"così come\n   sono\";\n4. Tutto il branching dovrebbe essere basato su semplici output booleani del\n   BLoC.\n\nLa libreria Bloc implementa il modello BLoC e mira ad astrarre RxDart per\nsemplificare l'esperienza dello sviluppatore.\n\nI tre principi di Redux sono:\n\n1. Singola fonte di verità;\n2. Lo stato è read-only;\n3. I cambiamenti sono fatti con funzioni pure.\n\nLa libreria bloc viola il primo principio; con bloc lo stato è distribuito\nattraverso più bloc. Inoltre, non c'è il concetto di middleware in bloc e bloc è\nprogettato per facilitare i cambiamenti di stato in modo asincrono,\npermettendoti di emettere più stati per un singolo evento.\n\n## Bloc vs. Provider\n\n❔ **Domanda**: Qual è la differenza tra Bloc e Provider?\n\n💡 **Risposta**: `provider` è progettato per la dependency injection (\"wrapper\"\nattorno a `InheritedWidget`). Devi ancora capire come gestire il tuo stato\n(tramite `ChangeNotifier`, `Bloc`, `Mobx`, ecc...). La libreria Bloc usa\n`provider` internamente per rendere facile fornire e accedere ai bloc in tutto\nl'albero dei widget.\n\n## BlocProvider.of() Non Trova il Bloc\n\n❔ **Domanda**: Quando uso `BlocProvider.of(context)` non riesce a trovare il\nbloc, come posso risolverlo?\n\n💡 **Risposta**: Non puoi accedere a un bloc dallo stesso contesto in cui è\nstato fornito (provide) quindi devi assicurarti che `BlocProvider.of()` sia\nchiamato all'interno di un `BuildContext` figlio.\n\n✅ **Buono**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **Errato**\n\n<BlocProviderBad1Snippet />\n\n## Struttura del Progetto\n\n❔ **Domanda**: Come dovrei strutturare il mio progetto?\n\n💡 **Risposta**: Anche se non c'è davvero una risposta giusta/sbagliata a questa\ndomanda, questi sono dei riferimenti che possono risponderti:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth);\n- [I/O Pinball](https://github.com/flutter/pinball);\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit).\n\nLa cosa più importante è avere una struttura del progetto **consistente** e\n**ben definita**.\n\n## Aggiungere Eventi all'Interno di un Bloc\n\n❔ **Domanda**: Va bene aggiungere eventi all'interno di un bloc?\n\n💡 **Risposta**: Nella maggior parte dei casi, gli eventi dovrebbero essere\naggiunti esternamente ma in alcuni casi particolari può avere senso.\n\nLa situazione più comune in cui vengono usati eventi interni è quando i\ncambiamenti di stato devono verificarsi in risposta ad aggiornamenti in tempo\nreale da un repository. In queste situazioni, il repository è lo stimolo per il\ncambiamento di stato invece di un evento esterno come il tocco di un pulsante.\n\nNell'esempio seguente, lo stato di `MyBloc` dipende dall'utente corrente che è\nesposto tramite lo `Stream<User>` dal `UserRepository`. `MyBloc` ascolta i\ncambiamenti nell'utente corrente e aggiunge un evento interno `_UserChanged`\nogni volta che un utente viene emesso dallo stream degli utenti.\n\n<BlocInternalAddEventSnippet />\n\nAggiungendo un evento interno, siamo anche in grado di specificare un\n`transformer` personalizzato per l'evento per determinare come più eventi\n`_UserChanged` saranno processati -- di default saranno processati\nconcorrentemente.\n\nÈ altamente raccomandato che gli eventi interni siano privati. Questo è un modo\nesplicito di segnalare che un evento specifico è usato solo all'interno del bloc\nstesso e previene che i componenti esterni conoscano l'evento.\n\n<BlocInternalEventSnippet />\n\nPossiamo alternativamente definire un evento esterno `Started` e usare l'API\n`emit.forEach` per gestire la reazione agli aggiornamenti degli utenti in tempo\nreale:\n\n<BlocExternalForEachSnippet />\n\nI vantaggi dell'approccio sopra sono:\n\n- Non abbiamo bisogno di un evento interno `_UserChanged`;\n- Non abbiamo bisogno di gestire manualmente la `StreamSubscription`;\n- Abbiamo pieno controllo su quando il bloc si sottoscrive allo stream degli\n  aggiornamenti degli utenti.\n\nGli svantaggi dell'approccio sopra sono:\n\n- Non possiamo facilmente mettere in pausa (`pause`) o riprendere (`resume`) la\n  sottoscrizione;\n- Dobbiamo esporre un evento pubblico `Started` che poi sarà da emittare\n  esternamente;\n- Non possiamo usare un `transformer` personalizzato per regolare come reagiamo\n  agli aggiornamenti degli utenti.\n\n## Esporre Metodi Pubblici\n\n❔ **Domanda**: Va bene esporre metodi pubblici sulle mie istanze di bloc e\ncubit?\n\n💡 **Risposta**\n\nQuando crei un cubit, è raccomandato esporre metodi pubblici solo per attivare\ndei cambiamenti di stato. Di conseguenza, generalmente tutti i metodi pubblici\nsu un'istanza di cubit dovrebbero restituire `void` o `Future<void>`.\n\nQuando crei un bloc, è raccomandato evitare di esporre qualsiasi metodo pubblico\npersonalizzato mentre è corretto notificare il bloc degli eventi chiamando\n`add`.\n"
  },
  {
    "path": "docs/src/content/docs/it/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Concetti Flutter Bloc\ndescription: Una panoramica dei concetti fondamentali per package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nAssicurati di leggere attentamente le seguenti sezioni prima di lavorare con\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nTutti i widget esportati dal pacchetto `flutter_bloc` si integrano con entrambe\nle istanze `Cubit` e `Bloc`.\n\n:::\n\n## Widget Bloc\n\n### BlocBuilder\n\n**BlocBuilder** è un widget Flutter che richiede un `Bloc` e una funzione\n`builder`. `BlocBuilder` gestisce la costruzione del widget in risposta a nuovi\nstati. `BlocBuilder` è molto simile a `StreamBuilder` ma ha un'API più semplice\nper ridurre la quantità di codice boilerplate necessario. La funzione `builder`\nsarà potenzialmente chiamata molte volte e dovrebbe essere una\n[funzione pura](https://en.wikipedia.org/wiki/Pure_function) che restituisce un\nwidget in risposta allo stato.\n\nVedi `BlocListener` se vuoi \"fare\" qualcosa in risposta ai cambiamenti di stato\ncome navigazione, mostrare un dialog, ecc...\n\nSe il parametro `bloc` è omesso, `BlocBuilder` eseguirà automaticamente una\nricerca usando `BlocProvider` e il `BuildContext` corrente.\n\n<BlocBuilderSnippet />\n\nSpecifica il bloc solo se desideri fornire un bloc che sarà limitato a un\nsingolo widget e non è accessibile tramite un `BlocProvider` padre e il\n`BuildContext` corrente.\n\n<BlocBuilderExplicitBlocSnippet />\n\nPer un controllo granulare su quando la funzione `builder` viene chiamata può\nessere fornit opzionalmente un `buildWhen`. `buildWhen` prende lo stato\nprecedente e quello corrente del bloc e restituisce un booleano. Se `buildWhen`\nrestituisce `true`, `builder` sarà chiamato con `state` e il widget si\nricostruirà. Se `buildWhen` restituisce `false`, `builder` non sarà chiamato con\n`state` e non si verificherà alcun aggiornamento.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** è un widget Flutter che è analogo a `BlocBuilder` ma permette\nagli sviluppatori di filtrare gli aggiornamenti selezionando un nuovo valore in\nbase allo stato corrente del bloc. Gli aggiornamenti non necessari sono\nprevenuti se il valore selezionato non cambia. Il valore selezionato deve essere\nimmutabile affinché `BlocSelector` possa determinare accuratamente se `builder`\ndovrebbe essere chiamato di nuovo.\n\nSe il parametro `bloc` è omesso, `BlocSelector` eseguirà automaticamente una\nricerca usando `BlocProvider` e il `BuildContext` corrente.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** è un widget Flutter che fornisce un bloc ai suoi figli tramite\n`BlocProvider.of<T>(context)`. È usato come widget di dependency injection (DI)\ncosì che una singola istanza di un bloc possa essere fornita a più widget\nall'interno di un sottoalbero.\n\nNella maggior parte dei casi, `BlocProvider` dovrebbe essere usato per creare\nnuovi bloc che saranno resi disponibili al resto del sottoalbero. In questo\ncaso, poiché `BlocProvider` è responsabile della creazione del bloc, gestirà\nautomaticamente la chiusura del bloc.\n\n<BlocProviderSnippet />\n\nDi default, `BlocProvider` creerà il bloc in modo pigro (`lazy`), il che\nsignifica che `create` sarà eseguito quando il bloc viene cercato tramite\n`BlocProvider.of<BlocA>(context)`.\n\nPer sovrascrivere questo comportamento e forzare `create` ad essere eseguito\nimmediatamente, `lazy` può essere impostato a `false`.\n\n<BlocProviderEagerSnippet />\n\nIn alcuni casi, `BlocProvider` può essere usato per fornire un bloc esistente a\nuna nuova porzione dell'albero dei widget. Questo sarà più comunemente usato\nquando un bloc esistente deve essere reso disponibile a una nuova route. In\nquesto caso, `BlocProvider` non chiuderà automaticamente il bloc poiché non l'ha\ncreato.\n\n<BlocProviderValueSnippet />\n\npoi da `ChildA`, o `ScreenA` possiamo recuperare `BlocA` con:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** è un widget Flutter che unisce più widget `BlocProvider`\nin uno unico. `MultiBlocProvider` migliora la leggibilità ed elimina la\nnecessità di annidare più `BlocProvider`. Usando `MultiBlocProvider` possiamo\npassare da:\n\n<NestedBlocProviderSnippet />\n\na:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nQuando un `BlocProvider` è definito nel contesto di un `MultiBlocProvider`,\nqualsiasi `child` sarà ignorato.\n\n:::\n\n### BlocListener\n\n**BlocListener** è un widget Flutter che prende un `BlocWidgetListener` e\nopzionalmente un `Bloc` e invoca il `listener` in risposta ai cambiamenti di\nstato nel bloc. Dovrebbe essere usato per funzionalità che devono verificarsi\nuna volta per cambiamento di stato come navigazione, mostrare uno `SnackBar`,\nmostrare un `Dialog`, ecc...\n\n`listener` è chiamato solo una volta per ogni cambiamento di stato (**NON** è\nchiamato con lo stato iniziale) ed è una funzione `void` a differenza di\n`builder` in `BlocBuilder`.\n\nSe il parametro `bloc` è omesso, `BlocListener` eseguirà automaticamente una\nricerca usando `BlocProvider` e il `BuildContext` corrente.\n\n<BlocListenerSnippet />\n\nSpecifica il bloc solo se desideri fornire un bloc che altrimenti non è\naccessibile tramite `BlocProvider` e il `BuildContext` corrente.\n\n<BlocListenerExplicitBlocSnippet />\n\nPer un controllo granulare su quando la funzione `listener` viene chiamata può\nessere fornito opzionalmente un `listenWhen`. `listenWhen` prende lo stato\nprecedente e quello corrente del bloc e restituisce un booleano. Se `listenWhen`\nrestituisce `true`, `listener` sarà chiamato con `state`. Se `listenWhen`\nrestituisce `false`, `listener` non sarà chiamato con `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** è un widget Flutter che unisce più widget `BlocListener`\nin uno. `MultiBlocListener` migliora la leggibilità ed elimina la necessità di\nannidare più `BlocListener`. Usando `MultiBlocListener` possiamo passare da:\n\n<NestedBlocListenerSnippet />\n\na:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nQuando un `BlocListener` è definito nel contesto di un `MultiBlocListener`,\nqualsiasi `child` sarà ignorato.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** espone un `builder` e un `listener` per reagire a nuovi stati.\n`BlocConsumer` è analogo a un `BlocListener` e `BlocBuilder` annidati ma riduce\nla quantità di boilerplate necessario. `BlocConsumer` dovrebbe essere usato solo\nquando è necessario sia ricostruire la UI che eseguire altre reazioni ai\ncambiamenti di stato nel `bloc`. `BlocConsumer` richiede un `BlocWidgetBuilder`\ne un `BlocWidgetListener`, opzionalmente prende un `bloc`, un\n`BlocBuilderCondition` e un `BlocListenerCondition`.\n\nSe il parametro `bloc` è omesso, `BlocConsumer` eseguirà automaticamente una\nricerca usando `BlocProvider` e il `BuildContext` corrente.\n\n<BlocConsumerSnippet />\n\nUn `listenWhen` e `buildWhen` opzionali possono essere implementati per un\ncontrollo più granulare su quando `listener` e `builder` sono chiamati.\n`listenWhen` e `buildWhen` saranno invocati su ogni cambiamento di `state` del\n`bloc`. Prendono ciascuno lo `state` precedente e lo `state` corrente e devono\nrestituire un `bool` che determina se la funzione `builder` e/o `listener` sarà\ninvocata. Lo `state` precedente sarà inizializzato allo `state` del `bloc`\nquando il `BlocConsumer` è inizializzato. `listenWhen` e `buildWhen` sono\nopzionali e se non sono implementati, avranno default a `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** è un widget Flutter che fornisce un repository ai suoi\nfigli tramite `RepositoryProvider.of<T>(context)`. È usato come widget di\ndependency injection (DI) così che una singola istanza di un repository possa\nessere fornita a più widget all'interno di un sottoalbero. `BlocProvider`\ndovrebbe essere usato per fornire bloc mentre `RepositoryProvider` dovrebbe\nessere usato solo per repository.\n\n<RepositoryProviderSnippet />\n\npoi da `ChildA` possiamo recuperare l'istanza del `Repository` con:\n\n<RepositoryProviderLookupSnippet />\n\nI repository che gestiscono risorse che devono essere eliminate possono farlo\ntramite la callback `dispose`:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** è un widget Flutter che unisce più widget\n`RepositoryProvider` in uno. `MultiRepositoryProvider` migliora la leggibilità\ned elimina la necessità di annidare più `RepositoryProvider`. Usando\n`MultiRepositoryProvider` possiamo passare da:\n\n<NestedRepositoryProviderSnippet />\n\na:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nQuando un `RepositoryProvider` è definito nel contesto di un\n`MultiRepositoryProvider`, qualsiasi `child` sarà ignorato.\n\n:::\n\n## Uso di BlocProvider\n\nDiamo un'occhiata a come usare `BlocProvider` per fornire un `CounterBloc` a una\n`CounterPage` e reagire ai cambiamenti di stato con `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nA questo punto abbiamo separato con successo il nostro livello di presentazione\ndal nostro livello di logica applicativa. Nota che il widget `CounterPage` non\nsa nulla di cosa succede quando un utente tocca i pulsanti. Il widget\nsemplicemente dice al `CounterBloc` che l'utente ha premuto il pulsante di\nincremento o decremento.\n\n## Uso di RepositoryProvider\n\nDaremo un'occhiata a come usare `RepositoryProvider` nel contesto dell'esempio\n[`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nNel nostro `main.dart`, chiamiamo `runApp` con il nostro widget `WeatherApp`.\n\n<WeatherMainSnippet />\n\nInietteremo la nostra istanza di `WeatherRepository` nel nostro albero dei\nwidget tramite `RepositoryProvider`.\n\nQuando istanziamo un bloc, possiamo accedere all'istanza di un repository\ntramite `context.read` e iniettare il repository nel bloc tramite costruttore.\n\n<WeatherAppSnippet />\n\n:::tip\n\nSe hai più di un repository, puoi usare `MultiRepositoryProvider` per fornire\npiù istanze di repository al sottoalbero.\n\n:::\n\n:::note\n\nUsa la callback `dispose` per gestire l'eliminazione di qualsiasi risorsa quando\nil `RepositoryProvider` viene smontato.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension methods\n\nGli [\"extension methods\"](https://dart.dev/guides/language/extension-methods),\nintrodotti in Dart 2.7, sono un modo per aggiungere funzionalità a librerie\nesistenti. In questa sezione, daremo un'occhiata alle estensione incluse in\n`package:flutter_bloc` e a come possono essere usate.\n\n`flutter_bloc` ha una dipendenza versp\n[package:provider](https://pub.dev/packages/provider) che semplifica l'uso di\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nInternamente, `package:flutter_bloc` usa `package:provider` per implementare:\n`BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` e widget\n`MultiRepositoryProvider`. `package:flutter_bloc` esporta le estensioni\n`ReadContext`, `WatchContext` e `SelectContext` da `package:provider`.\n\n:::note\n\nScopri di più su [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` cerca l'istanza dell'antenato più vicina di tipo `T` ed è\nfunzionalmente equivalente a `BlocProvider.of<T>(context)`. `context.read` è più\ncomunemente usato per recuperare un'istanza di bloc per aggiungere un evento\nall'interno di una callback `onPressed`.\n\n:::note\n\n`context.read<T>()` non ascolta `T` -- se l'oggetto fornito di tipo `T` cambia,\n`context.read` non attiverà un aggiornamento del widget.\n\n:::\n\n#### Uso\n\n✅ **FARE** usa `context.read` per aggiungere eventi nelle callback.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **EVITARE** di usare `context.read` per recuperare lo stato all'interno di un\nmetodo `build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nL'uso sopra è soggetto a errori perché il widget `Text` non sarà ricostruito se\nlo stato del bloc cambia.\n\n:::caution\n\nUsa invece `BlocBuilder` o `context.watch` per ricostruire la UI in risposta ai\ncambiamenti di stato.\n\n:::\n\n### context.watch\n\nCome `context.read<T>()`, `context.watch<T>()` fornisce l'istanza dell'antenato\npiù vicina di tipo `T`, tuttavia ascolta anche i cambiamenti sull'istanza. È\nfunzionalmente equivalente a `BlocProvider.of<T>(context, listen: true)`.\n\nSe l'oggetto fornito di tipo `T` cambia, `context.watch` attiverà un\naggiornamento.\n\n:::caution\n\n`context.watch` è accessibile solo all'interno del metodo `build` di un\n`StatelessWidget` o classe `State`.\n\n:::\n\n#### Uso\n\n✅ **FARE** usa `BlocBuilder` invece di `context.watch` per limitare\nesplicitamente gli aggiornamenti.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Ogni volta che lo stato cambia, solo il Text viene ricostruito.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nIn alternativa, usa un `Builder` per limitare gli aggiornamenti.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Ogni volta che lo stato cambia, solo il Text viene ricostruito.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **FARE** usa `Builder` e `context.watch` come `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // restituisci un Widget che dipende dallo stato di BlocA, BlocB e BlocC\n  }\n);\n```\n\n❌ **EVITARE** di usare `context.watch` quando il widget padre nel metodo\n`build` non dipende dallo stato.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Ogni volta che lo stato cambia, la MaterialApp viene ricostruit  a\n  // anche se è usato solo nel widget Text.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsare `context.watch` alla radice del metodo `build` risulterà nel aggiornamento\ndell'intero widget quando lo stato del bloc cambia.\n\n:::\n\n### context.select\n\nProprio come `context.watch<T>()`, `context.select<T, R>(R function(T value))`\nfornisce l'istanza dell'antenato più vicina di tipo `T` e ascolta i cambiamenti\nsu `T`. A differenza di `context.watch`, `context.select` ti permette di\nascoltare i cambiamenti in una parte più piccola di uno stato.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nIl codice sopra ricostruirà il widget solo quando la proprietà `name` dello\nstato del `ProfileBloc` cambia.\n\n#### Uso\n\n✅ **FARE** usa `BlocSelector` invece di `context.select` per limitare\nesplicitamente gli aggiornamenti.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Ogni volta che state.name cambia, solo il Text viene ricostruito.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nIn alternativa, usa un `Builder` per limitare gli aggiornamenti.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Ogni volta che state.name cambia, solo il Text viene ricostruito.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **EVITARE** di usare `context.select` quando il widget padre in un metodo\nbuild non dipende dallo stato.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Ogni volta che state.value cambia, il MaterialApp viene ricostruito\n  // anche se è usato solo nel widget Text.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsare `context.select` alla radice del metodo `build` risulterà nel\naggiornamento dell'intero widget quando la selezione cambia.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/it/getting-started.mdx",
    "content": "---\ntitle: Inizia\ndescription: Tutto ciò di cui hai bisogno per iniziare a costruire con Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Librerie\n\nL'ecosistema bloc si compone di molteplici librerie elencate qui di seguito:\n\n| Pacchetto                                                                                  | Descrizione                   | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | Componenti AngularDart        | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | API Core di Dart              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Trasformatori di Eventi       | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Linter Personalizzato         | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | API di Testing                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Strumenti da Linea di Comando | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Widget Flutter                | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Supporto Cache/Persistenza    | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Supporto Undo/Redo            | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Installazione\n\n<InstallationTabs />\n\n:::note\n\nPer iniziare a usare bloc devi avere [Dart SDK](https://dart.dev/get-dart)\ninstallato sulla tua macchina.\n\n:::\n\n## Import\n\nOra che abbiamo installato con successo bloc, possiamo creare il nostro\n`main.dart` e importare la corrispettiva libreria `bloc`.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/it/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Libreria di \"State Management\" Bloc\ndescription:\n  Documentazione ufficiale per la libreria di \"state management\" bloc. Supporto\n  per Dart, Flutter e AngularDart. Include esempi e tutorial.\nbanner:\n  content: |\n    ✨ Visita il\n    <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Una libreria di \"state management\" prevedibile per Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Inizia\n      link: /it/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Vedi su GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Inizia\" icon=\"rocket\">\n\t```sh\n\t# Aggiungi bloc al tuo progetto.\n\tdart pub add bloc\n\t```\n\nLa nostra [guida introduttiva](/it/getting-started) contiene istruzioni\npasso-passo su come iniziare a usare Bloc in pochi minuti.\n\n</SplitCard>\n\n<Card title=\"Fai un tour guidato\" icon=\"star\">\n\tCompleta [i tutorial ufficiali](/it/tutorials/flutter-counter) per imparare le\n\tbest practice e costruire una varietà di app diverse alimentate da Bloc.\n</Card>\n\n<Card title=\"Costruisci con Bloc\" icon=\"laptop\">\n\tEsplora app di esempio di alta qualità e completamente testate [come il\n\tcounter, timer, lista infinita, meteo, todo e altro\n\tancora!](https://github.com/felangel/bloc/tree/master/examples)\n</Card>\n\n<ListCard title=\"Impara\" icon=\"open-book\">\n\n    - [Perché Bloc?](/it/why-bloc)\n    - [Concetti Bloc](/it/bloc-concepts)\n    - [Architettura](/it/architecture)\n    - [Testing](/it/testing)\n    - [Convenzioni di Nomenclatura](/it/naming-conventions)\n    - [FAQ](/it/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integrazioni\" icon=\"puzzle\">\n    - [Integrazione VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Integrazione IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Integrazione Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Integrazione Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Template Personalizzati](https://brickhub.dev/search?q=bloc)\n    - [Strumenti per Sviluppatori](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint/configuration.mdx",
    "content": "---\ntitle: Configurazione del Linter\ndescription: Configurare il linter bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nDi default, il linter di bloc non segnalerà alcun avviso finché non avrai\nconfigurato esplicitamente le opzioni di analisi del progetto.\n\nPer iniziare, crea o modifica l'`analysis_options.yaml` esistente alla radice\ndel tuo progetto per includere una lista di regole sotto la chiave bloc di primo\nlivello:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nEsegui il linter usando il seguente comando nel tuo terminale:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nIl comando sopra analizzerà tutti i file nella directory corrente e nelle sue\nsottodirectory, ma puoi anche fare il \"lint\" di file e directory specifici\npassandoli come argomenti da linea di comando:\n\n<RunBlocLintInSrcTestSnippet />\n\nIl comando sopra analizzerà tutto il codice nelle directory `src` e `test`.\n\nSe la regola `avoid_flutter_imports` è abilitata, qualsiasi file bloc o cubit\nche contiene un import flutter sarà segnalato come avviso:\n\n<AvoidFlutterImportsWarningSnippet />\n\nPuoi vedere l'avviso eseguendo il comando `bloc lint`:\n\n<RunBlocLintCounterCubitSnippet />\n\nL'output dovrebbe essere:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nEcco tutte le regole di lint supportate:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/it/lint/customizing-rules.mdx",
    "content": "---\ntitle: Personalizzare le Regole di Lint\ndescription: Personalizzare le regole di lint bloc\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nPuoi personalizzare il comportamento del linter bloc cambiando la gravità di\nregole individualmente, abilitando o disabilitando regole individualmente, ed\nescludendo file dall'analisi statica.\n\n## Abilitare e Disabilitare Regole\n\nIl linter bloc supporta una lista crescente di regole di lint. Nota che le\nregole di lint non devono necessariamente essere compatibili tra loro. Ad\nesempio, alcuni sviluppatori potrebbero preferire usare bloc (`prefer_bloc`)\nmentre altri potrebbero preferire usare cubit (`prefer_cubit`).\n\n:::note\n\nA differenza dell'analisi statica, le regole di lint potrebbero contenere falsi\npositivi. Sentiti libero di segnalare eventuali falsi positivi o altri problemi\n[aprendo una \"issue\"](https://github.com/felangel/bloc/issues/new/choose).\n\n:::\n\n### Abilitare Regole Raccomandate\n\nLa libreria bloc fornisce un set di regole di lint raccomandate come parte del\npacchetto [`bloc_lint`](https://pub.dev/packages/bloc_lint).\n\nPer abilitare il set raccomandato di lint aggiungi il pacchetto `bloc_lint` come\ndipendenza di sviluppo:\n\n<InstallBlocLintSnippet />\n\nPoi modifica il tuo `analysis_options.yaml` per includere il set di regole:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nQuando viene pubblicata una nuova versione di `bloc_lint`, il codice che\nprecedentemente passava l'analisi statica potrebbe fallire. Raccomandiamo di\naggiornare il tuo codice per adeguarsi alle nuove regole, oppure puoi anche\nopzionalmente abilitare o disabilitare le regole individualmente.\n\n:::\n\n### Abilitare Regole Individualmente\n\nPer abilitare regole individualmente, aggiungi `bloc:` al file\n`analysis_options.yaml` come chiave di primo livello e `rules:` come chiave di\nsecondo livello. Nelle righe successive, specifica le regole che vuoi come una\nlista YAML (prefissate con trattini).\n\nAd esempio:\n\n<BlocLintEnablingRulesSnippet />\n\n### Disabilitare Regole Individualmente\n\nSe includi un set di regole esistente come il set `recommended`, potresti voler\ndisabilitarne una o più di queste individualmente. Disabilitare regole è simile\na abilitarle, ma richiede l'uso di una mappa YAML piuttosto che una lista.\n\nAd esempio, il seguente include il set raccomandato di regole di lint eccetto\n`avoid_public_bloc_methods` e inoltre abilita la regola `prefer_bloc`:\n\n<BlocLintDisablingRulesSnippet />\n\n## Personalizzare la Gravità delle Regole\n\nPuoi regolare la gravità di qualsiasi regola così:\n\n<BlocLintChangingSeveritySnippet />\n\nOra la stessa regola di lint sarà segnalata con una gravità di `info` invece che\ndi `warning`:\n\n<ImportFlutterInfoSnippet />\n\nL'output del comando `bloc lint` dovrebbe essere:\n\n<ImportFlutterInfoOutputSnippet />\n\nLe opzioni di gravità supportate sono:\n\n| Gravità   | Descrizione                                            |\n| --------- | ------------------------------------------------------ |\n| `error`   | Indica che il pattern non è permesso.                  |\n| `warning` | Indica che il pattern è sospetto ma permesso.          |\n| `info`    | Fornisce informazioni agli utenti ma non è un problema |\n| `hint`    | Propone un modo migliore di ottenere un risultato.     |\n\n## Escludere File\n\nA volte va bene che l'analisi statica fallisca. Ad esempio, potresti voler\nignorare avvisi o errori segnalati nel codice generato che non è stato scritto\nda te e dal tuo team. Proprio come con le regole di lint ufficiali di Dart, puoi\nusare l'opzione `exclude:` dell'analyzer per escludere file dall'analisi\nstatica.\n\nPuoi elencare file individuali o usare pattern\n[`glob`](https://pub.dev/packages/glob).\n\n:::note\n\nTutti gli usi di pattern glob dovrebbero essere relativi alla directory\ncontenente il file `analysis_options.yaml` corrispondente.\n\n:::\n\nAd esempio, possiamo escludere tutto il codice Dart generato tramite le seguenti\nopzioni di analisi:\n\n<BlocLintExcludingFilesSnippet />\n\n## Ignorare Regole\n\nProprio come con le regole di lint ufficiali di Dart, puoi ignorare le regole di\nlint bloc per un dato file o riga di codice usando `// ignore_for_file` e\n`// ignore` rispettivamente.\n\n:::note\n\nPer ignorare più regole per una data riga o file, fornisci una lista separata da\nvirgole.\n\n:::\n\n### Ignorare Righe\n\nPossiamo ignorare occorrenze specifiche di violazioni di regole aggiungendo un\ncommento `ignore` sia direttamente sopra la riga incriminata che aggiungendolo\nalla fine della riga.\n\nAd esempio, possiamo ignorare occorrenze specifiche di\n`prefer_file_naming_conventions` in un dato file:\n\n<BlocLintIgnoreForLineSnippet />\n\n### Ignorare File\n\nPossiamo ignorare tutte le occorrenze di violazioni di regole all'interno di un\nfile aggiungendo un commento `ignore_for_file` ovunque nel file.\n\nAd esempio, possiamo ignorare tutte le occorrenze di\n`prefer_file_naming_conventions` in un dato file:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint/index.mdx",
    "content": "---\ntitle: Panoramica del Linter\ndescription: Un'introduzione al linter bloc.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nIl \"linting\" è il processo di analisi statica del codice per individuare\npotenziali bug in aggiunta a errori programmatici e stilistici.\n\nBloc ha un linter integrato, che può essere usato tramite il tuo IDE o gli\n[strumenti da linea di comando bloc](https://pub.dev/packages/bloc_tools) con il\ncomando `bloc lint`.\n\nCon l'aiuto del linter bloc, puoi migliorare la qualità dell'intero progetto e\nimporre coerenza senza eseguire una singola riga di codice.\n\nAd esempio, forse hai accidentalmente importato una dipendenza Flutter nel tuo\ncubit:\n\n<AvoidFlutterImportsWarningSnippet />\n\nSe configurato correttamente, il linter bloc indicherà l'import e produrrà il\nseguente avviso:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nNelle sezioni seguenti, copriremo come installare, configurare e personalizzare\nil linter bloc in modo da poter sfruttare l'analisi statica nel tuo progetto.\n\n## Primi passi\n\nInizia a usare il linter bloc in pochi passaggi rapidi e facili.\n\n:::note\n\nPer iniziare a usare bloc devi avere [Dart SDK](https://dart.dev/get-dart)\ninstallato sulla tua macchina.\n\n:::\n\n1. Installa gli\n   [strumenti da linea di comando bloc](https://pub.dev/packages/bloc_tools);\n\n   <InstallBlocToolsSnippet />\n\n1. Installa il pacchetto [bloc_lint](https://pub.dev/packages/bloc_lint);\n\n   <InstallBlocLintSnippet />\n\n1. Aggiungi un `analysis_options.yaml` alla radice del tuo progetto con le\n   regole raccomandate;\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. Esegui il linter.\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nQuesto è tutto 🎉\n\nContinua a leggere le altre sezioni per approfondire l'argomento su come\nconfigurare e personalizzare il linter bloc.\n"
  },
  {
    "path": "docs/src/content/docs/it/lint/installation.mdx",
    "content": "---\ntitle: Installazione del Linter\ndescription: Installare il linter bloc.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## Strumenti da Linea di Comando\n\nPer usare il linter dalla linea di comando, installa\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) tramite il seguente\ncomando:\n\n<InstallBlocToolsSnippet />\n\nUna volta che gli strumenti da linea di comando bloc sono stati installati, puoi\neseguire il linter bloc tramite il comando `bloc lint`:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## Set di Regole Raccomandato\n\nPer installare il set di regole di lint raccomandato, installa\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) come dipendenza di\nsviluppo tramite il seguente comando:\n\n<InstallBlocLintSnippet />\n\nPoi, aggiungi un `analysis_options.yaml` alla radice del tuo progetto con il set\ndi regole raccomandato:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nSe necessario, puoi includere più set di regole definendoli come una lista:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## Integrazioni IDE\n\nI seguenti IDE supportano ufficialmente il linter e il language server di bloc,\nfornendo diagnostica in tempo reale direttamente all'interno dell'ambiente di\nsviluppo.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tIl supporto per l'[Estensione Bloc\n\t\tVSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tè disponibile nella v6.8.0.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tIl supporto per il [Plugin Bloc\n\t\tIntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) è disponibile\n\t\tnella v4.1.0.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: Evitare Estensioni BuildContext\ndescription: La regola avoid_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nEvita di usare estensioni di `BuildContext` per accedere a istanze di `Bloc` o\n`Cubit`.\n\n:::note\n\nQuesta regola di lint è stata introdotta nella versione `0.3.0` di\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Motivazione\n\nPer coerenza e per il bene di essere espliciti, preferisci usare direttamente i\nmetodi di bloc rispetto le estensioni di `BuildContext`. Questo è anche\nvantaggioso per il testing dato che non è possibile mockare un metodo di una\nestensione.\n\n| estensione       | metodo esplicito                                                   |\n| ---------------- | ------------------------------------------------------------------ |\n| `context.read`   | `BlocProvider.of<Bloc>(context, listen: false)`                    |\n| `context.watch`  | `BlocBuilder<Bloc, State>(...)` o `BlocProvider.of<Bloc>(context)` |\n| `context.select` | `BlocSelector<Bloc, State>(...)`                                   |\n\n## Esempi\n\n**Evita** di usare estensioni `BuildContext` per interagire con istanze di\n`Bloc` o `Cubit`.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `avoid_build_context_extensions`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: Evitare Import Flutter\ndescription: La regola di lint bloc avoid_flutter_imports.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nEvita di introdurre dipendenze su Flutter all'interno di componenti di logica\napplicativa (istanze `Bloc` o `Cubit`).\n\n## Motivazione\n\nStratificare un'applicazione è una parte chiave della costruzione di un progetto\nmantenibile e aiuta gli sviluppatori a iterare rapidamente e con fiducia. Ogni\nlivello dovrebbe avere una singola responsabilità ed essere in grado di\nfunzionare e di essere testato in modo isolato. Questo ti permette di contenere\ni cambiamenti a livelli specifici, minimizzando l'impatto su tutta\nl'applicazione.\n\nDi conseguenza, i componenti di logica applicativa dovrebbero generalmente\ngestire lo stato delle funzionalità ed essere disaccoppiati dal livello UI. Gli\neventi dovrebbero fluire nei componenti di logica applicativa dal livello UI e\nlo stato dovrebbe fluire dal livello di logica applicativa nel livello UI.\n\nMantenere i componenti di logica applicativa disaccoppiati da Flutter fornisce\nla capacità di riutilizzare la logica applicativa su più piattaforme/framework\n(ad es. Flutter, AngularDart, Jaspr, ecc.).\n\n## Esempi\n\n**NON** importare Flutter all'interno dei tuoi componenti di logica applicativa.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `avoid_flutter_imports`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: Evitare Metodi Pubblici Bloc\ndescription: La regola avoid_public_bloc_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nEvita di esporre metodi pubblici su istanze `Bloc`.\n\n## Motivazione\n\nI bloc reagiscono a eventi in arrivo ed emettono stati in uscita. Di\nconseguenza, il modo raccomandato di comunicare con un'istanza di bloc è tramite\nil metodo `add`. Nella maggior parte dei casi, non c'è bisogno di creare\nastrazioni aggiuntive sopra l'API `add`.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n## Esempi\n\n**NON** esporre metodi pubblici su istanze bloc.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `avoid_public_bloc_methods`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: Evitare Campi Pubblici\ndescription: La regola avoid_public_fields.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nEvita di esporre campi pubblici su istanze `Bloc` e `Cubit`.\n\n## Motivazione\n\nI componenti di logica applicativa mantengono il proprio `state` ed emettono\ncambiamenti di stato tramite l'API `emit`. Di conseguenza, tutto lo stato\npubblico dovrebbe essere esposto tramite l'oggetto `state`.\n\n## Esempi\n\n**NON** esporre campi pubblici su istanze bloc e cubit.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `avoid_public_fields`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: Preferire Bloc\ndescription: La regola prefer_bloc.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPreferisci usare istanze `Bloc` invece di istanze `Cubit`.\n\n## Motivazione\n\nQuesta regola è puramente una regola stilistica. In alcuni casi, i team\npotrebbero preferire standardizzare solo sull'uso di istanze `Bloc` in tutta la\nloro applicazione per coerenza.\n\n:::tip\n\nScopri di più sui vantaggi di `Bloc` in\n[Concetti Bloc](/it/bloc-concepts/#vantaggi-del-bloc).\n\n:::\n\n## Esempi\n\n**Evita** di usare istanze `Cubit`.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `prefer_bloc`, aggiungila al tuo `analysis_options.yaml`\nsotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: Preferire Estensioni BuildContext\ndescription: La regola prefer_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPreferisci usare estensioni `BuildContext` per accedere a un'istanza di `Bloc` o\n`Repository`.\n\n:::note\n\nQuesta regola di lint è stata introdotta nella versione `0.3.2` di\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Motivazione\n\nPer coerenza, preferisci usare estensioni `BuildContext` come `context.read`,\n`context.watch`, e `context.select` invece di `BlocProvider.of`,\n`RepositoryProvider.of`, `BlocBuilder` o `BlocSelector`.\n\n| metodo esplicito                                                   | estensione            |\n| ------------------------------------------------------------------ | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                    | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` o `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                   | `context.select`      |\n\n## Esempi\n\n**Evita** di usare `BlocProvider.of<T>(context)` per accedere a un'istanza di\n`Bloc`.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `prefer_build_context_extensions`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: Preferire Cubit\ndescription: La regola prefer_cubit.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPreferisci usare istanze `Cubit` invece di istanze `Bloc`.\n\n## Motivazione\n\nQuesta regola è puramente una regola stilistica. In alcuni casi, i team\npotrebbero preferire standardizzare solo sull'uso di istanze `Cubit` in tutta la\nloro applicazione per coerenza.\n\n:::tip\n\nScopri di più sui vantaggi di `Cubit` in\n[Concetti Bloc](/it/bloc-concepts/#vantaggi-del-cubit).\n\n:::\n\n## Esempi\n\n**Evita** di usare istanze `Bloc`.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `prefer_cubit`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: Preferire Convenzioni di Nomenclatura dei File\ndescription: La regola prefer_file_naming_conventions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nPreferisci seguire le convenzioni di nomenclatura dei file.\n\n:::note\n\nQuesta regola di lint è stata introdotta nella versione `0.3.0` di\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Motivazione\n\nPer coerenza, facilità di manutenzione e separazione delle responsabilità\npreferisci definire istanze bloc e cubit nei rispettivi file Dart invece di\ninserirle inline.\n\n:::tip\n\nConsidera di usare il comando `bloc new <component>` da\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) per generare\nrapidamente e coerentemente nuove istanze bloc/cubit.\n\n:::\n\n## Esempi\n\n**Preferisci** dichiarare istanze bloc/cubit nei loro rispettivi file.\n\n**Buono**:\n\n<GoodSnippet />\n\n**Errato**:\n\n<BadSnippet />\n\n## Abilita\n\nPer abilitare la regola `prefer_file_naming_conventions`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: Preferire Metodi Pubblici Void Cubit\ndescription: La regola prefer_void_public_cubit_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nPreferisci metodi pubblici void su istanze `Cubit`.\n\n:::note\n\nQuesta regola di lint è stata introdotta nella versione `0.2.0-dev.2` di\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Motivazione\n\nI metodi pubblici su istanze `Cubit` dovrebbero essere usati per notificare il\n`Cubit` e iniziare cambiamenti di stato tramite il metodo `emit`. Se il\nchiamante ha bisogno di accedere a qualsiasi informazione di stato, dovrebbero\naccedervi trammite `state`.\n\n:::note\n\nLe seguenti regole sono correlate e sono solitamente abilitate in combinazione\ncon `prefer_void_public_cubit_methods`.\n\n- [`avoid_public_bloc_methods`](/it/lint-rules/avoid_public_bloc_methods);\n- [`avoid_public_fields`](/it/lint-rules/avoid_public_fields).\n\n:::\n\n## Esempi\n\n**Evita** metodi pubblici non-void su istanze `Cubit`.\n\n**Errato**:\n\n<BadSnippet />\n\n**Buono**:\n\n<GoodSnippet />\n\n## Abilita\n\nPer abilitare la regola `prefer_void_public_cubit_methods`, aggiungila al tuo\n`analysis_options.yaml` sotto `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/it/migration.mdx",
    "content": "---\ntitle: Guida alla Migrazione\ndescription: Migrare all'ultima versione stabile di Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nFai riferimento al\n[log delle release](https://github.com/felangel/bloc/releases) per maggiori\ninformazioni riguardo a cosa è cambiato in ogni release.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ Disaccoppiare `blocTest` da `BlocBase`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc_test v10.0.0, l'API `blocTest` non è più strettamente accoppiata a\n`BlocBase`.\n\n:::\n\n##### Motivazione\n\n`blocTest` dovrebbe usare le interfacce core di bloc quando possibile per\naumentare flessibilità e riutilizzabilità. Precedentemente questo non era\npossibile perché `BlocBase` implementava `StateStreamableSource` che non era\nsufficiente per `blocTest` a causa della dipendenza interna sull'API `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Supporto WebAssembly\n\n:::note[Cosa è Cambiato?]\n\nIn hydrated_bloc v10.0.0, è stato aggiunto il supporto per compilare a\nWebAssembly (wasm).\n\n:::\n\n##### Motivazione\n\nPrecedentemente non era possibile compilare app a wasm quando si usava\n`hydrated_bloc`. In v10.0.0, il pacchetto è stato modificato per permettere la\ncompilazione a wasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Rimuovere API Deprecate\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v9.0.0, tutte le API precedentemente deprecate sono state rimosse.\n\n:::\n\n##### Riepilogo\n\n- `BlocOverrides` rimosso a favore di `Bloc.observer` e `Bloc.transformer`.\n\n#### ❗✨ Introdurre nuova Interfaccia `EmittableStateStreamableSource`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v9.0.0, è stata introdotta una nuova interfaccia core\n`EmittableStateStreamableSource`.\n\n:::\n\n##### Motivazione\n\n`package:bloc_test` era precedentemente strettamente accoppiato a `BlocBase`.\nL'interfaccia `EmittableStateStreamableSource` è stata introdotta per permettere\na `blocTest` di essere disaccoppiato dall'implementazione concreta di\n`BlocBase`.\n\n### `package:hydrated_bloc`\n\n#### ✨ Reintrodurre API `HydratedBloc.storage`\n\n:::note[Cosa è Cambiato?]\n\nIn hydrated_bloc v9.0.0, `HydratedBlocOverrides` è stato rimosso a favore\ndell'API `HydratedBloc.storage`.\n\n:::\n\n##### Motivazione\n\nFai riferimento a\n[Motivazione per reintrodurre gli override di Bloc.observer e Bloc.transformer](/it/migration#-reintrodurre-api-blocobserver-e-bloctransformer).\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ Reintrodurre API `Bloc.observer` e `Bloc.transformer`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.1.0, `BlocOverrides` è stato deprecato a favore delle API\n`Bloc.observer` e `Bloc.transformer`.\n\n:::\n\n##### Motivazione\n\nL'API `BlocOverrides` è stata introdotta in v8.0.0 nel tentativo di supportare\nlo scoping di configurazioni specifiche del bloc come `BlocObserver`,\n`EventTransformer`, e `HydratedStorage`. Nelle applicazioni Dart pure, i\ncambiamenti hanno funzionato bene; tuttavia, nelle applicazioni Flutter la nuova\nAPI ha causato più problemi di quanti ne risolvesse.\n\nL'API `BlocOverrides` è stata ispirata da API simili in Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html);\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html).\n\n**Problemi**\n\nAnche se non era il motivo principale per questi cambiamenti, l'API\n`BlocOverrides` ha introdotto complessità aggiuntiva per gli sviluppatori. Oltre\nad aumentare la quantità di annidamento e righe di codice necessarie per\nottenere lo stesso effetto, l'API `BlocOverrides` richiedeva agli sviluppatori\ndi avere una solida comprensione delle\n[Zone](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) in Dart.\nLe `Zone` non sono un concetto adatto ai principianti e la non comprensione di\ncome funzionano le Zone potrebbe portare all'introduzione di bug (come observer,\ntrasformatori e istanze di storage non inizializzati).\n\nAd esempio, molti sviluppatori avrebbero qualcosa del tipo:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nIl codice sopra, anche se sembra innocuo, può effettivamente portare a molti bug\ndifficili da tracciare. Qualunque sia la zona da cui viene inizialmente chiamato\n`WidgetsFlutterBinding.ensureInitialized`, sarà quella in cui verranno gestiti\ngli eventi gestuali (ad esempio callback `onTap`, `onPressed`) grazie a\n`GestureBinding.initInstances`. Questo è solo uno dei molti problemi causati\ndall'uso di `zoneValues`.\n\nInoltre, Flutter fa molte cose dietro le quinte che coinvolgono\nfork/manipolazione di Zone (specialmente quando si eseguono test) che possono\nportare a comportamenti inaspettati (e in molti casi comportamenti che sono\nfuori dal controllo dello sviluppatore -- vedi \"issue\" sotto).\n\nA causa dell'uso di\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), la\ntransizione all'API `BlocOverrides` ha portato alla scoperta di diversi\nbug/limitazioni in Flutter (specificamente intorno ai Test Widget e di\nIntegrazione):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nche hanno colpito molti sviluppatori che usavano la libreria bloc:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ Introdurre nuova API `BlocOverrides`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, `Bloc.observer` e `Bloc.transformer` sono stati rimossi a favore\ndell'API `BlocOverrides`.\n\n:::\n\n##### Motivazione\n\nL'API precedente usata per sovrascrivere il `BlocObserver` di default e\n`EventTransformer` si basava su un singleton globale sia per `BlocObserver` che\nper `EventTransformer`.\n\nDi conseguenza, non era possibile:\n\n- Avere più implementazioni di `BlocObserver` o `EventTransformer` con ambito su\n  parti diverse dell'applicazione;\n- Limitare l'ambito delle sostituzioni di `BlocObserver` o `EventTransformer` a\n  un pacchetto\n  - Se un pacchetto dipendesse da `package:bloc` e registrasse il proprio\n    `BlocObserver`, qualsiasi consumatore del pacchetto dovrebbe sovrascrivere\n    il `BlocObserver` del pacchetto o segnalarlo al `BlocObserver` del\n    pacchetto.\n\nEra anche più difficile testare a causa dello stato globale condiviso tra test.\n\nBloc v8.0.0 introduce una classe `BlocOverrides` che permette agli sviluppatori\ndi sovrascrivere `BlocObserver` e/o `EventTransformer` per una specifica `Zone`\npiuttosto che affidarsi a un singleton globale mutabile.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nLe istanze `Bloc` useranno il `BlocObserver` e/o `EventTransformer` per la\n`Zone` corrente tramite `BlocOverrides.current`. Se non ci sono `BlocOverrides`\nper la zona, useranno i default interni esistenti (nessun cambiamento nel\ncomportamento/funzionalità).\n\nQuesto permette a ogni `Zone` di funzionare indipendentemente con i propri\n`BlocOverrides`.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA e eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // I Bloc in questa zona segnalano a BlocObserverA\n    // e usano eventTransformerA come trasformatore di default.\n    // ...\n\n    // Più tardi...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB e eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // I Bloc in questa zona segnalano a BlocObserverB\n        // e usano eventTransformerB come trasformatore di default.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Migliorare Gestione e Segnalazione degli Errori\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, `BlocUnhandledErrorException` è rimosso. Inoltre, qualsiasi\neccezione non gestita è sempre segnalata a `onError` e rilanciata\n(indipendentemente dalla modalità debug o release). L'API `addError` segnala\nerrori a `onError`, ma non tratta gli errori segnalati come eccezioni non\ngestite.\n\n:::\n\n##### Motivazione\n\nL'obiettivo di questi cambiamenti è:\n\n- rendere le eccezioni interne non gestite estremamente ovvie preservando ancora\n  la funzionalità bloc;\n- supportare `addError` senza interrompere il flusso di controllo.\n\nPrecedentemente, la gestione e segnalazione degli errori variava a seconda che\nl'applicazione fosse in esecuzione in modalità debug o release. Inoltre, gli\nerrori segnalati tramite `addError` erano trattati come eccezioni non gestite in\nmodalità debug il che portava a una cattiva esperienza per lo sviluppatore\nquando si usava l'API `addError` (specialmente quando si scrivevano test\nunitari).\n\nIn v8.0.0, `addError` può essere usato in sicurezza per segnalare errori e\n`blocTest` può essere usato per verificare che gli errori siano segnalati. Tutti\ngli errori sono ancora segnalati a `onError`, tuttavia, solo le eccezioni non\ngestite sono rilanciate (indipendentemente dalla modalità debug o release).\n\n#### ❗🧹 Rendere `BlocObserver` astratto\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, `BlocObserver` è stato convertito in una classe `abstract` il\nche significa che un'istanza di `BlocObserver` non può essere istanziata.\n\n:::\n\n##### Motivazione\n\n`BlocObserver` era inteso per essere un'interfaccia. Poiché l'implementazione\nAPI di default sono \"no-op\", `BlocObserver` è ora una classe `abstract` per\ncomunicare chiaramente che la classe è pensata per essere estesa e non\nistanziata direttamente.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // Era possibile creare un'istanza della classe base.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // Non è possibile istanziare la classe base.\n  final observer = BlocObserver(); // ERRORE\n\n  // Estendi `BlocObserver` invece.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ `add` lancia `StateError` se Bloc è chiuso\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, chiamare `add` su un bloc chiuso risulterà in un `StateError`.\n\n:::\n\n##### Motivazione\n\nPrecedentemente, era possibile chiamare `add` su un bloc chiuso e l'errore\ninterno veniva ingoiato, rendendo difficile il debug del perché l'evento\naggiunto non era processato. Per rendere questo scenario più visibile, in\nv8.0.0, chiamare `add` su un bloc chiuso lancerà un `StateError` che sarà\nsegnalato come eccezione non gestita e propagato a `onError`.\n\n#### ❗✨ `emit` lancia `StateError` se Bloc è chiuso\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, chiamare `emit` all'interno di un bloc chiuso risulterà in un\n`StateError`.\n\n:::\n\n##### Motivazione\n\nPrecedentemente, era possibile chiamare `emit` all'interno di un bloc chiuso e\nnessun cambiamento di stato si verificava ma non c'era anche alcuna indicazione\ndi cosa fosse andato storto, rendendo difficile il debug. Per rendere questo\nscenario più visibile, in v8.0.0, chiamare `emit` all'interno di un bloc chiuso\nlancerà un `StateError` che sarà segnalato come eccezione non gestita e\npropagato a `onError`.\n\n#### ❗🧹 Rimuovere API Deprecate\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v8.0.0, tutte le API precedentemente deprecate sono state rimosse.\n\n:::\n\n##### Riepilogo\n\n- `mapEventToState` rimosso a favore di `on<Event>`;\n- `transformEvents` rimosso a favore dell'API `EventTransformer`;\n- `TransitionFunction` typedef rimosso a favore dell'API `EventTransformer`;\n- `listen` rimosso a favore di `stream.listen`.\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` e `MockCubit` non richiedono più `registerFallbackValue`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc_test v9.0.0, gli sviluppatori non devono più chiamare esplicitamente\n`registerFallbackValue` quando usano `MockBloc` o `MockCubit`.\n\n:::\n\n##### Riepilogo\n\n`registerFallbackValue` è necessario solo quando si usa il matcher `any()` da\n`package:mocktail` per un tipo personalizzato. Precedentemente,\n`registerFallbackValue` era necessario per ogni `Event` e `State` quando si\nusava `MockBloc` o `MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Test...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Test...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Introdurre nuova API `HydratedBlocOverrides`\n\n:::note[Cosa è Cambiato?]\n\nIn hydrated_bloc v8.0.0, `HydratedBloc.storage` è stato rimosso a favore\ndell'API `HydratedBlocOverrides`.\n\n:::\n\n##### Motivazione\n\nPrecedentemente, veniva usato un singleton globale per sovrascrivere\nl'implementazione `Storage`.\n\nDi conseguenza, non era possibile avere più implementazioni `Storage` con ambito\nin diverse parti dell'applicazione. Era anche più difficile testare a causa\ndello stato globale condiviso tra test.\n\n`HydratedBloc` v8.0.0 introduce una classe `HydratedBlocOverrides` che permette\nagli sviluppatori di sovrascrivere `Storage` per una specifica `Zone` piuttosto\nche affidarsi a un singleton globale mutabile.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\nLe istanze `HydratedBloc` useranno il `Storage` per la `Zone` corrente tramite\n`HydratedBlocOverrides.current`.\n\nQuesto permette a ogni `Zone` di funzionare indipendentemente con i propri\n`BlocOverrides`.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ Introdurre nuova API `on<Event>`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v7.2.0, `mapEventToState` è stato deprecato a favore di `on<Event>`.\n`mapEventToState` sarà rimosso in bloc v8.0.0.\n\n:::\n\n##### Motivazione\n\nL'API `on<Event>` è stata introdotta come parte di\n[[Proposta] Sostituire mapEventToState con on\\<Event\\> in Bloc](https://github.com/felangel/bloc/issues/2526).\nA causa di [un problema in Dart](https://github.com/dart-lang/sdk/issues/44616)\nnon è sempre ovvio quale sarà il valore di `state` quando si ha a che fare con\ngeneratori asincroni annidati (`async*`). Anche se ci sono modi per aggirare il\nproblema, uno dei principi fondamentali della libreria bloc è essere\nprevedibile. L'API `on<Event>` è stata creata per rendere la libreria il più\nsicura possibile da usare e per eliminare qualsiasi incertezza quando si tratta\ndi cambiamenti di stato.\n\n:::tip\n\nPer maggiori informazioni,\n[leggi la proposta completa](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**Riepilogo**\n\n`on<E>` ti permette di registrare un gestore di eventi per tutti gli eventi di\ntipo `E`. Di default, gli eventi saranno processati concorrentemente quando si\nusa `on<E>` al contrario di `mapEventToState` che processa eventi\n`sequenzialmente`.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nOgni `EventHandler` registrato funziona indipendentemente quindi è importante\nregistrare gestori di eventi in base al tipo di trasformatore che vuoi\napplicato.\n\n:::\n\nSe vuoi mantenere esattamente lo stesso comportamento di v7.1.0 puoi registrare\nun singolo gestore di eventi per tutti gli eventi e applicare un trasformatore\n`sequential`:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: la logica va qui...\n  }\n}\n```\n\nPuoi anche sovrascrivere il `EventTransformer` di default per tutti i bloc nella\ntua applicazione:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ Introdurre nuova API `EventTransformer`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v7.2.0, `transformEvents` è stato deprecato a favore dell'API\n`EventTransformer`. `transformEvents` sarà rimosso in bloc v8.0.0.\n\n:::\n\n##### Motivazione\n\nL'API `on<Event>` ha aperto la porta alla possibilità di fornire un\ntrasformatore di eventi personalizzato per gestore di eventi. È stato introdotto\nun nuovo typedef `EventTransformer` che permette agli sviluppatori di\ntrasformare lo stream di eventi in arrivo per ogni gestore di eventi piuttosto\nche dover specificare un singolo trasformatore di eventi per tutti gli eventi.\n\n**Riepilogo**\n\nUn `EventTransformer` è responsabile di prendere lo stream di eventi in arrivo\ninsieme a un `EventMapper` (il tuo gestore di eventi) e restituire un nuovo\nstream di eventi.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nIl `EventTransformer` di default processa tutti gli eventi concorrentemente e\nassomiglia a qualcosa del tipo:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nDai un'occhiata a\n[package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) per un set\nopinionato di trasformatori di eventi personalizzati\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Definisci un `EventTransformer` personalizzato\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Applica il `EventTransformer` personalizzato all'`EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ Deprecare API `transformTransitions`\n\n:::note[Cosa è Cambiato?]\n\nIn bloc v7.2.0, `transformTransitions` è stato deprecato a favore di\nsovrascrivere l'API `stream`. `transformTransitions` sarà rimosso in bloc\nv8.0.0.\n\n:::\n\n##### Motivazione\n\nIl getter `stream` su `Bloc` rende facile sovrascrivere lo stream in uscita di\nstati quindi non è più prezioso mantenere un'API separata\n`transformTransitions`.\n\n**Riepilogo**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc e Cubit estendono BlocBase\n\n##### Motivazione\n\nCome sviluppatore, la relazione tra bloc e cubit era un po' scomoda. Quando\ncubit è stato introdotto per la prima volta è iniziato come classe base per bloc\nil che aveva senso perché aveva un sottoinsieme della funzionalità e i bloc\navrebbero semplicemente esteso Cubit e definito API aggiuntive. Questo ha\nportato ad alcuni svantaggi:\n\n- Tutte le API dovrebbero essere o rinominate per accettare un cubit per\n  accuratezza o dovrebbero essere mantenute come bloc per coerenza anche se\n  gerarchicamente è inaccurato\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560));\n\n- Cubit avrebbe bisogno di estendere Stream e implementare EventSink per avere\n  una base comune su cui widget come BlocBuilder, BlocListener, ecc. possono\n  essere implementati ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nPiù tardi, abbiamo sperimentato invertendo la relazione e rendendo bloc la\nclasse base che ha parzialmente risolto il primo punto sopra ma ha introdotto\naltri problemi:\n\n- L'API cubit è gonfia a causa delle API bloc sottostanti come mapEventToState,\n  add, ecc. ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - Gli sviluppatori possono tecnicamente invocare queste API e rompere le cose;\n- Abbiamo ancora lo stesso problema di cubit che espone l'intera API stream come\n  prima. ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nPer affrontare questi problemi abbiamo introdotto una classe base per entrambi\n`Bloc` e `Cubit` chiamata `BlocBase` così che i componenti upstream possano\nancora interoperare con entrambe le istanze bloc e cubit ma senza esporre\nl'intera API `Stream` e `EventSink` direttamente.\n\n**Riepilogo**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed restituisce una funzione per supportare valori dinamici\n\n##### Motivazione\n\nPer supportare avere un valore seed mutabile che può essere aggiornato\ndinamicamente in `setUp`, `seed` restituisce una funzione.\n\n**Riepilogo**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect restituisce una funzione per supportare valori dinamici e include supporto matcher\n\n##### Motivazione\n\nPer supportare avere un'aspettativa mutabile che può essere aggiornata\ndinamicamente in `setUp`, `expect` restituisce una funzione. `expect` supporta\nanche `Matchers`.\n\n**Riepilogo**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// Può anche essere un `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors restituisce una funzione per supportare valori dinamici e include supporto matcher\n\n##### Motivazione\n\nPer supportare avere errori mutabili che possono essere aggiornati dinamicamente\nin `setUp`, `errors` restituisce una funzione. `errors` supporta anche\n`Matchers`.\n\n**Riepilogo**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// Può anche essere un `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc e MockCubit\n\n##### Motivazione\n\nPer supportare lo stubbing di varie API core, `MockBloc` e `MockCubit` sono\nesportati come parte del pacchetto `bloc_test`. Precedentemente, `MockBloc`\ndoveva essere usato per entrambe le istanze `Bloc` e `Cubit` il che non era\nintuitivo.\n\n**Riepilogo**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Integrazione Mocktail\n\n##### Motivazione\n\nA causa di varie limitazioni del null-safe\n[package:mockito](https://pub.dev/packages/mockito) descritte\n[qui](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) è usato da `MockBloc` e\n`MockCubit`. Questo permette agli sviluppatori di continuare a usare un'API di\nmocking familiare senza la necessità di scrivere implementazioni simulate (stub)\nmanualmente o affidarsi alla generazione di codice.\n\n**Riepilogo**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Fai riferimento a [#347](https://github.com/dart-lang/mockito/issues/347) così\n> come alla\n> [documentazione mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> per maggiori informazioni.\n\n### `package:flutter_bloc`\n\n#### ❗ rinominare parametro `cubit` a `bloc`\n\n##### Motivazione\n\nCome risultato del refactoring in `package:bloc` per introdurre `BlocBase` che\n`Bloc` e `Cubit` estendono, i parametri di `BlocBuilder`, `BlocConsumer`, e\n`BlocListener` sono stati rinominati da `cubit` a `bloc` perché i widget operano\nsul tipo `BlocBase`. Questo si allinea anche ulteriormente con il nome della\nlibreria e speriamo migliori la leggibilità.\n\n**Riepilogo**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory è richiesto quando si chiama HydratedStorage.build\n\n##### Motivazione\n\nPer rendere `package:hydrated_bloc` un pacchetto Dart puro, la dipendenza su\n[package:path_provider](https://pub.dev/packages/path_provider) è stata rimossa\ne il parametro `storageDirectory` quando si chiama `HydratedStorage.build` è\nrichiesto e non ha più default a `getTemporaryDirectory`.\n\n**Riepilogo**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc e context.repository sono deprecati a favore di context.read e context.watch\n\n##### Motivazione\n\n`context.read`, `context.watch`, e `context.select` sono stati aggiunti per\nallinearsi con l'API esistente di [provider](https://pub.dev/packages/provider)\ncon cui molti sviluppatori hanno familiarità e per affrontare problemi che sono\nstati sollevati dalla comunità. Per migliorare la sicurezza del codice e\nmantenere la coerenza, `context.bloc` è stato deprecato perché può essere\nsostituito con `context.read` o `context.watch` a seconda che sia usato\ndirettamente all'interno di `build`.\n\n**context.watch**\n\n`context.watch` affronta la richiesta di avere un\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) perché possiamo\nguardare diversi bloc all'interno di un singolo `Builder` per renderizzare UI\nbasata su stati multipli:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // restituisci un Widget che dipende dallo stato di BlocA, BlocB e BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` permette agli sviluppatori di renderizzare/aggiornare UI basata\nsu una parte di uno stato bloc e affronta la richiesta di avere un\n[buildWhen più semplice](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nIl frammento sopra ci permette di accedere e ricostruire il widget solo quando\nil nome dell'utente corrente cambia.\n\n**context.read**\n\nAnche se sembra che `context.read` sia identico a `context.bloc` ci sono alcune\ndifferenze sottili ma significative. Entrambi ti permettono di accedere a un\nbloc con un `BuildContext` e non risultano in aggiornamenti; tuttavia,\n`context.read` non può essere chiamato direttamente all'interno di un metodo\n`build`. Ci sono due ragioni principali per usare `context.bloc` all'interno di\n`build`:\n\n1. **Per accedere allo stato del bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nL'uso sopra è soggetto a errori perché il widget `Text` non sarà ricostruito se\nlo stato del bloc cambia. In questo scenario, dovrebbe essere usato un\n`BlocBuilder` o `context.watch`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\no\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nUsare `context.watch` alla radice del metodo `build` risulterà nel aggiornamento\ndell'intero widget quando lo stato del bloc cambia. Se l'intero widget non ha\nbisogno di essere ricostruito, usa `BlocBuilder` per avvolgere le parti che\ndovrebbero essere ricostruite, usa un `Builder` con `context.watch` per limitare\ngli aggiornamenti, o scomponi il widget in widget più piccoli.\n\n:::\n\n2. **Per accedere al bloc così che un evento possa essere aggiunto**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nL'uso sopra è inefficiente perché risulta in una ricerca del bloc su ogni\naggiornamento quando il bloc è necessario solo quando l'utente tocca\nl'`ElevatedButton`. In questo scenario, preferisci usare `context.read` per\naccedere al bloc direttamente dove è necessario (in questo caso, nel callback\n`onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Riepilogo**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> Se accedi a un bloc per aggiungere un evento, esegui l'accesso al bloc usando\n`context.read` nel callback dove è necessario.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Usa `context.watch` quando accedi allo stato del bloc per assicurarti che il\nwidget sia ricostruito quando lo stato cambia.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError prende Cubit\n\n##### Motivazione\n\nA causa dell'integrazione di `Cubit`, `onError` è ora condiviso tra entrambe le\nistanze `Bloc` e `Cubit`. Poiché `Cubit` è la base, `BlocObserver` accetterà un\ntipo `Cubit` piuttosto che un tipo `Bloc` nell'override `onError`.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc non emette ultimo stato su sottoscrizione\n\n##### Motivazione\n\nQuesto cambiamento è stato fatto per allineare `Bloc` e `Cubit` con il\ncomportamento `Stream` integrato in `Dart`. Inoltre, conformarsi al vecchio\ncomportamento nel contesto di `Cubit` ha portato a molti effetti collaterali non\nintenzionali e in generale ha complicato le implementazioni interne di altri\npacchetti come `flutter_bloc` e `bloc_test` inutilmente (richiedendo `skip(1)`,\necc...).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nPrecedentemente, il frammento sopra avrebbe emesso lo stato iniziale del bloc\nseguito da cambiamenti di stato successivi.\n\n**v6.x.x**\n\nIn v6.0.0, il frammento sopra non emette lo stato iniziale e emette solo\ncambiamenti di stato successivi. Il comportamento precedente può essere ottenuto\ncon il seguente:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Nota**: Questo cambiamento influenzerà solo il codice che si affida a\nsottoscrizioni bloc diretti. Quando si usa `BlocBuilder`, `BlocListener`, o\n`BlocConsumer` non ci sarà alcun cambiamento evidente nel comportamento.\n\n### `package:bloc_test`\n\n#### ❗MockBloc richiede solo tipo State\n\n##### Motivazione\n\nNon è necessario ed elimina codice extra rendendo anche `MockBloc` compatibile\ncon `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen richiede solo tipo State\n\n##### Motivazione\n\nNon è necessario ed elimina codice extra rendendo anche `whenListen` compatibile\ncon `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest non richiede tipo Event\n\n##### Motivazione\n\nNon è necessario ed elimina codice extra rendendo anche `blocTest` compatibile\ncon `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip default a 0\n\n##### Motivazione\n\nPoiché le istanze `bloc` e `cubit` non emetteranno più lo stato più recente per\nnuove sottoscrizioni, non era più necessario impostare `skip` di default a `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nLo stato iniziale di un bloc o cubit può essere testato con il seguente:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest rendere build sincrono\n\n##### Motivazione\n\nPrecedentemente, `build` era reso `async` così che varie preparazioni potessero\nessere fatte per mettere il bloc sotto test in uno stato specifico. Non è più\nnecessario e risolve anche diversi problemi a causa della latenza aggiunta tra\nla build e l' sottoscrizione internamente. Invece di fare preparazione async per\nottenere un bloc in uno stato desiderato possiamo ora impostare lo stato del\nbloc concatenando `emit` con lo stato desiderato.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` è visibile solo per il testing e non dovrebbe mai essere usato al di\nfuori dei test.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder parametro bloc rinominato a cubit\n\n##### Motivazione\n\nPer rendere `BlocBuilder` interoperabile con istanze `bloc` e `cubit` il\nparametro `bloc` è stato rinominato a `cubit` (poiché `Cubit` è la classe base).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener parametro bloc rinominato a cubit\n\n##### Motivazione\n\nPer rendere `BlocListener` interoperabile con istanze `bloc` e `cubit` il\nparametro `bloc` è stato rinominato a `cubit` (poiché `Cubit` è la classe base).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗BlocConsumer parametro bloc rinominato a cubit\n\n##### Motivazione\n\nPer rendere `BlocConsumer` interoperabile con istanze `bloc` e `cubit` il\nparametro `bloc` è stato rinominato a `cubit` (poiché `Cubit` è la classe base).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState è stato rimosso\n\n##### Motivazione\n\nCome sviluppatore, dover sovrascrivere `initialState` quando si crea un bloc\npresenta due problemi principali:\n\n- Lo `initialState` del bloc può essere dinamico e può anche essere referenziato\n  in un momento successivo (anche al di fuori del bloc stesso). In alcuni modi,\n  questo può essere visto come perdita di informazioni interne del bloc al\n  livello UI;\n- È verboso.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> Per maggiori informazioni controlla\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate rinominato a BlocObserver\n\n##### Motivazione\n\nIl nome `BlocDelegate` non era una descrizione accurata del ruolo che la classe\ngiocava. `BlocDelegate` suggerisce che la classe gioca un ruolo attivo mentre in\nrealtà il ruolo inteso del `BlocDelegate` era che fosse un componente passivo\nche semplicemente osserva tutti i bloc in un'applicazione.\n\n:::note\n\nNon dovrebbe idealmente esserci funzionalità o caratteristiche user-facing\ngestite all'interno di `BlocObserver`.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor è stato rimosso\n\n##### Motivazione\n\n`BlocSupervisor` era un altro componente che gli sviluppatori dovevano conoscere\ne interagire per il solo scopo di specificare un `BlocDelegate` personalizzato.\nCon il cambiamento a `BlocObserver` abbiamo sentito che migliorava l'esperienza\ndello sviluppatore impostare l'observer direttamente sul bloc stesso.\n\n?> Questo cambiamento ci ha anche permesso di disaccoppiare altri add-on bloc\ncome `HydratedStorage` dal `BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder condition rinominato a buildWhen\n\n##### Motivazione\n\nQuando si usa `BlocBuilder`, potevamo precedentemente specificare una\n`condition` per determinare se il `builder` dovrebbe essere ricostruito.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // restituisci true/false per determinare se chiamare builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nIl nome `condition` non è molto auto-esplicativo o ovvio e più importante,\nquando si interagisce con un `BlocConsumer` l'API è diventata inconsistente\nperché gli sviluppatori possono fornire due condizioni (una per `builder` e una\nper `listener`). Di conseguenza, l'API `BlocConsumer` espone un `buildWhen` e\n`listenWhen`\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // restituisci true/false per determinare se chiamare listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // restituisci true/false per determinare se chiamare builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nPer allineare l'API e fornire un'esperienza più coerente per lo sviluppatore,\n`condition` è stato rinominato a `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // restituisci true/false per determinare se chiamare builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // restituisci true/false per determinare se chiamare builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener condition rinominato a listenWhen\n\n##### Motivazione\n\nPer le stesse ragioni descritte sopra, anche la `condition` di `BlocListener` è\nstata rinominata.\n\n**v4.x.x**\n\n```dart\nBlocListener(\n  condition: (previous, current) {\n    // restituisci true/false per determinare se chiamare listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener(\n  listenWhen: (previous, current) {\n    // restituisci true/false per determinare se chiamare listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage e HydratedBlocStorage rinominati\n\n##### Motivazione\n\nPer migliorare il riutilizzo del codice tra\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) e\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), l'implementazione\nstorage concreta di default è stata rinominata da `HydratedBlocStorage` a\n`HydratedStorage`. Inoltre, l'interfaccia `HydratedStorage` è stata rinominata\nda `HydratedStorage` a `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage disaccoppiato da BlocDelegate\n\n##### Motivazione\n\nCome menzionato prima, `BlocDelegate` è stato rinominato a `BlocObserver` ed è\nstato impostato direttamente come parte del `bloc` tramite:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nIl seguente cambiamento è stato fatto per:\n\n- Rimanere coerenti con la nuova API bloc observer;\n- Mantenere lo storage con ambito solo a `HydratedBloc`;\n- Disaccoppiare il `BlocObserver` da `Storage`.\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Inizializzazione Semplificata\n\n##### Motivazione\n\nPrecedentemente, gli sviluppatori dovevano chiamare manualmente\n`super.initialState ?? DefaultInitialState()` per impostare le loro istanze\n`HydratedBloc`. Questo è goffo e verboso e anche incompatibile con i cambiamenti\nbreaking a `initialState` in `bloc`. Di conseguenza, in v5.0.0\nl'inizializzazione di `HydratedBloc` è identica all'inizializzazione normale di\n`Bloc`.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/it/modeling-state.mdx",
    "content": "---\ntitle: Modellare lo Stato\ndescription:\n  Una panoramica di diversi modi per modellare gli stati quando si usa\n  package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nCi sono molti approcci diversi quando si tratta di strutturare lo stato\ndell'applicazione. Ognuno ha i suoi vantaggi e svantaggi. In questa sezione,\ndaremo un'occhiata a diversi approcci, i loro pro e contro, e quando usare\nciascuno.\n\nI seguenti approcci sono semplicemente raccomandazioni e sono completamente\nopzionali. Sentiti libero di usare qualsiasi approccio preferisci. Potresti\nnotare che alcuni esempi o parti della documentazione non seguono rigorosamente\nquesti approcci, principalmente per ragioni di semplicità e brevità.\n\n:::tip\n\nI seguenti frammenti di codice si concentrano sulla struttura dello stato.\nAll'atto pratico, potresti anche voler:\n\n- Estendere `Equatable` da\n  [`package:equatable`](https://pub.dev/packages/equatable);\n- Annotare la classe con `@Data()` da\n  [`package:data_class`](https://pub.dev/packages/data_class);\n- Annotare la classe con **@immutable** da\n  [`package:meta`](https://pub.dev/packages/meta);\n- Implementare un metodo `copyWith`;\n- Usare la parola chiave `const` per i costruttori.\n\n:::\n\n## Classe Concreta e Enum di Stato\n\nQuesto approccio consiste in una **singola classe concreta** per tutti gli stati\ncon un parametro `enum` che rappresenta diversi stati. Le proprietà sono rese\nnullable e sono gestite in base allo stato corrente. Questo approccio funziona\nmeglio per stati che non sono strettamente esclusivi e/o contengono molte\nproprietà condivise.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Pro\n\n- **Semplice**: Facile gestire una singola classe e un enum di stato e tutte le\n  proprietà sono facilmente accessibili;\n- **Conciso**: Generalmente richiede meno righe di codice rispetto ad altri\n  approcci.\n\n#### Contro\n\n- **Non \"Type Safe\"**: Richiede controllare lo `status` prima di accedere alle\n  proprietà. È possibile emittare (`emit`) uno stato malformato che può portare\n  a bug. Le proprietà per stati specifici sono nullable; questo può rendere la\n  gestione macchinosa, poiché richiede continui controlli sui null o l'uso del\n  \"force unwrap\". Alcuni di questi svantaggi possono essere mitigati scrivendo\n  unit test e definendo dei costruttori nominativi specializzati;\n- **Sovraccarico**: Il rischio è di avere un unico stato che, con il passare del\n  tempo, può sovraccaricarsi di proprietà.\n\n#### Verdetto\n\nQuesto approccio funziona meglio per stati semplici o quando i requisiti\nrichiedono stati che non sono esclusivi (ad es. mostrare uno snackbar quando si\nverifica un errore mentre si mostrano ancora i vecchi dati dall'ultimo stato di\nsuccesso). Questo approccio fornisce flessibilità e concisione al costo della\n\"type safety\".\n\n## Classe Sealed e Sottoclassi\n\nQuesto approccio consiste in una **classe sealed** che contiene qualsiasi\nproprietà condivisa e più sottoclassi per gli stati separati. Questo approccio è\nottimo per creare stati separati con proprietà esclusive.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Pro\n\n- **\"Type Safe\"**: Il codice è sicuro in fase di compilazione e non è possibile\n  accedere accidentalmente a una proprietà non valida. Ogni sottoclasse contiene\n  le sue proprietà, rendendo chiaro quali proprietà appartengono a quale stato;\n- **Esplicito:** Separa le proprietà condivise dalle proprietà specifiche dello\n  stato;\n- **Esaustivo**: Usare un'istruzione `switch` ci assicura che ogni stato sia\n  gestito esplicitamente.\n  - Se non vuoi\n    [switch esaustivi](https://dart.dev/language/branches#exhaustiveness-checking)\n    o vuoi essere in grado di aggiungere sottotipi in seguito senza rompere\n    l'API, usa il modificatore\n    [final](https://dart.dev/language/class-modifiers#final).\n  - Vedi la\n    [documentazione delle classi sealed](https://dart.dev/language/class-modifiers#sealed)\n    per maggiori dettagli.\n\n#### Contro\n\n- **Verboso**: Richiede più codice (una classe base e una sottoclasse per\n  stato). Inoltre può richiedere codice duplicato per proprietà condivise tra\n  alcune sottoclassi;\n- **Complesso**: Aggiungere nuove proprietà richiede aggiornare ogni sottoclasse\n  e la classe base, il che può essere scomodo e portare ad aumenti nella\n  complessità dello stato. Inoltre, può richiedere controlli di tipo non\n  necessari/eccessivi per accedere alle proprietà.\n\n#### Verdetto\n\nQuesto approccio funziona meglio per stati ben definiti ed esclusivi con\nproprietà uniche. Questo approccio fornisce sicurezza sui tipi, controlli di\nesaustività e enfatizza la sicurezza rispetto a concisione e semplicità.\n"
  },
  {
    "path": "docs/src/content/docs/it/naming-conventions.mdx",
    "content": "---\ntitle: Convenzioni di Nomenclatura\ndescription:\n  Panoramica delle convenzioni di nomenclatura raccomandate quando si usa bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nLe seguenti convenzioni di nomenclatura sono semplicemente raccomandazioni e\nsono completamente opzionali. Sentiti libero di usare qualsiasi convenzione di\nnomenclatura preferisci. Potresti notare che alcuni esempi o parti della\ndocumentazione non seguono le convenzioni di nomenclatura, principalmente per\nragioni di semplicità e brevità. Tuttavia, queste convenzioni sono caldamente\nraccomandate per progetti di grandi dimensioni che coinvolgono più sviluppatori.\n\n## Convenzioni per gli Eventi\n\nGli eventi dovrebbero essere nominati al **tempo passato** perché gli eventi\nsono cose che sono già accadute dalla prospettiva del bloc.\n\n### Anatomia\n\n`BlocSubject` + `Sostantivo (opzionale)` + `Verbo (evento)`\n\nGli eventi di caricamento iniziale dovrebbero seguire la convenzione:\n`BlocSubject` + `Started`\n\n:::note\n\nLa classe evento base dovrebbe essere nominata: `BlocSubject` + `Event`.\n\n:::\n\n### Esempi\n\n✅ **Buono**\n\n<EventExamplesGood1 />\n\n❌ **Errato**\n\n<EventExamplesBad1 />\n\n## Convenzioni per gli Stati\n\nGli stati dovrebbero essere sostantivi perché uno stato è solo un'istantanea in\nun punto particolare nel tempo. Ci sono due modi comuni per rappresentare lo\nstato: usando sottoclassi o usando una singola classe.\n\n### Anatomia\n\n#### Sottoclassi\n\n`BlocSubject` + `Verbo (azione)` + `State`\n\nQuando si rappresenta lo stato come più sottoclassi `State` dovrebbe essere uno\ndei seguenti:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nGli stati iniziali dovrebbero seguire la convenzione: `BlocSubject` + `Initial`.\n\n:::\n\n#### Classe Singola\n\n`BlocSubject` + `State`\n\nQuando si rappresenta lo stato come una singola classe base un enum chiamato\n`BlocSubject` + `Status` dovrebbe essere usato per rappresentare la condizione\ndello stato:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nLa classe stato base dovrebbe sempre essere nominata: `BlocSubject` + `State`.\n\n:::\n\n### Esempi\n\n✅ **Buono**\n\n##### Sottoclassi\n\n<StateExamplesGood1Snippet />\n\n##### Classe Singola\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Errato**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/it/testing.mdx",
    "content": "---\ntitle: Testing\ndescription: Le basi su come scrivere test per i tuoi bloc.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc è stato progettato per essere estremamente facile da testare. In questa\nsezione, vedremo come testare un bloc con i test unitari.\n\nPer semplicità, scriviamo i test per il `CounterBloc` che abbiamo creato in\n[Concetti Bloc](/it/bloc-concepts).\n\nRicapitolando, l'implementazione del `CounterBloc` è la seguente:\n\n<CounterBlocSnippet />\n\n## Configurazione\n\nPrima di iniziare a scrivere i nostri test avremo bisogno di aggiungere un\nframework di test alle nostre dipendenze.\n\nDobbiamo aggiungere [test](https://pub.dev/packages/test) e\n[bloc_test](https://pub.dev/packages/bloc_test) al nostro progetto.\n\n<AddDevDependenciesSnippet />\n\n## Testing\n\nIniziamo creando il file `counter_bloc_test.dart` per i nostri test del\n`CounterBloc` e importando il pacchetto test.\n\n<CounterBlocTestImportsSnippet />\n\nSuccessivamente, dobbiamo creare il nostro `main` così come il nostro gruppo di\ntest.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nI gruppi servono sia per organizzare i singoli test, sia per creare un contesto\nin cui è possibile condividere le operazioni di `setUp` e `tearDown` tra tutti i\ntest contenuti. :::\n\nIniziamo creando un'istanza del nostro `CounterBloc` che sarà usata in tutti i\nnostri test.\n\n<CounterBlocTestSetupSnippet />\n\nOra possiamo iniziare a scrivere i nostri test individuali.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nPossiamo eseguire tutti i nostri test con il comando `dart test`.\n\n:::\n\nA questo punto dovremmo avere il nostro primo test che passa! Ora scriviamo un\ntest più complesso usando il pacchetto\n[bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nA questo punto, possiamo eseguire i test e verificare che siano tutti superati.\n\nQuesto è tutto, il testing dovrebbe essere semplice e dovremmo sentirci\nfiduciosi quando facciamo cambiamenti e \"refactoring\" del nostro codice.\n\nPuoi fare riferimento all'\n[App Meteo](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nper un esempio di un'applicazione completamente testata.\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Flutter Counter\ndescription:\n  Una guida approfondita su come costruire un'app Flutter counter con bloc.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn questo tutorial, costruiremo un contatore in Flutter usando la libreria bloc.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## Argomenti Chiave\n\n- Osservare i cambiamenti di stato con\n  [BlocObserver](/it/bloc-concepts#blocobserver);\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- Usare Cubit invece di Bloc.\n  [Qual è la differenza?](/it/bloc-concepts#cubit-vs-bloc);\n- Aggiungere eventi con [context.read](/it/flutter-bloc-concepts#contextread).\n\n## Configurazione\n\nInizieremo creando un nuovo progetto Flutter\n\n<FlutterCreateSnippet />\n\nPossiamo poi sostituire il contenuto di `pubspec.yaml` con\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\ne poi installare tutte le nostre dipendenze\n\n<FlutterPubGetSnippet />\n\n## Struttura del Progetto\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nL'applicazione usa una struttura di directory guidata dalle funzionalità. Questa\nstruttura ci permette di scalare il progetto avendo funzionalità autonome. In\nquesto esempio avremo solo una singola funzionalità (il counter stesso) ma in\napplicazioni più complesse possiamo avere centinaia di funzionalità diverse.\n\n## BlocObserver\n\nLa prima cosa che vedremo è come creare un `BlocObserver` che ci aiuterà a\nosservare tutti i cambiamenti di stato nell'applicazione.\n\nCreiamo `lib/counter_observer.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\nIn questo caso, stiamo solo sovrascrivendo `onChange` per vedere tutti i\ncambiamenti di stato che si verificano.\n\n:::note\n\n`onChange` funziona allo stesso modo per entrambe le istanze `Bloc` e `Cubit`.\n\n:::\n\n## main.dart\n\nSuccessivamente, sostituiamo il contenuto di `lib/main.dart` con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nStiamo inizializzando il `CounterObserver` che abbiamo appena creato e chiamando\n`runApp` con il widget `CounterApp` che vedremo dopo.\n\n## Counter App\n\nCreiamo `lib/app.dart`:\n\n`CounterApp` sarà un `MaterialApp` che specifica `CounterPage` come `home`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nStiamo estendendo `MaterialApp` perché `CounterApp` _è_ un `MaterialApp`. Nella\nmaggior parte dei casi, creeremo istanze di `StatelessWidget` o `StatefulWidget`\ne comporremo widget in `build`, ma in questo caso non ci sono widget da comporre\nquindi è più semplice estendere `MaterialApp`.\n\n:::\n\nDiamo un'occhiata a `CounterPage`!\n\n## Counter Page\n\nCreiamo `lib/counter/view/counter_page.dart`:\n\nIl widget `CounterPage` è responsabile di creare un `CounterCubit` (che vedremo\ndopo) e fornirlo a `CounterView`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\nÈ importante separare o disaccoppiare la creazione di un `Cubit` dal consumo di\nun `Cubit` per avere codice molto più testabile e riutilizzabile.\n\n:::\n\n## Counter Cubit\n\nCreiamo `lib/counter/cubit/counter_cubit.dart`:\n\nLa classe `CounterCubit` esporrà due metodi:\n\n- `increment`: aggiunge 1 allo stato corrente;\n- `decrement`: sottrae 1 dallo stato corrente.\n\nIl tipo di stato che il `CounterCubit` sta gestendo è solo un `int` e lo stato\niniziale è `0`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\nUsa l'\n[Estensione VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\no [Plugin IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) per creare\nnuovi cubit automaticamente.\n\n:::\n\nSuccessivamente, diamo un'occhiata a `CounterView` che sarà responsabile di\nconsumare lo stato e interagire con il `CounterCubit`.\n\n## Counter View\n\nCreiamo `lib/counter/view/counter_view.dart`:\n\n`CounterView` è responsabile di renderizzare il conteggio corrente e\nrenderizzare due `FloatingActionButton` per incrementare e decrementare il\ncounter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\nUn `BlocBuilder` è usato per avvolgere il widget `Text` per aggiornare il testo\nogni volta che lo stato del `CounterCubit` cambia. Inoltre,\n`context.read<CounterCubit>()` è usato per recuperare l'istanza `CounterCubit`\npiù vicina.\n\n:::note\n\nSolo il widget `Text` è avvolto in un `BlocBuilder` perché è l'unico widget che\ndeve essere ricostruito in risposta ai cambiamenti di stato del `CounterCubit`.\nEvita di avvolgere inutilmente widget che non hanno bisogno di essere\nricostruiti quando lo stato cambia.\n\n:::\n\n## Barrel\n\nCrea `lib/counter/view/view.dart`:\n\nAggiungi `view.dart` per esportare tutte le parti pubbliche della vista del\ncounter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\nCreiamo `lib/counter/counter.dart`:\n\nAggiungi `counter.dart` per esportare tutte le parti pubbliche della\nfunzionalità counter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\nQuesto è tutto! Abbiamo separato il livello di presentazione dal livello di\nlogica applicativa. `CounterView` non ha idea di cosa succede quando un utente\npreme un pulsante; semplicemente notifica il `CounterCubit`. Inoltre, il\n`CounterCubit` non ha idea di cosa sta succedendo con lo stato (valore del\ncounter); sta semplicemente emettendo nuovi stati in risposta ai metodi\nchiamati.\n\nPossiamo eseguire l'app con `flutter run` e visualizzarla sul nostro dispositivo\no simulatore/emulatore.\n\nIl codice sorgente completo (inclusi test unitari e widget) per questo esempio\npuò essere trovato\n[qui](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: Flutter Firebase Login\ndescription:\n  Guida completa alla creazione di un flusso di login Flutter con bloc e\n  Firebase.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn questo tutorial costruiremo in Flutter un flusso di accesso al sistema\ntramite Firebase usando la libreria bloc.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## Argomenti Chiave\n\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un'istanza di bloc ai suoi figli;\n- Usare Cubit invece di Bloc.\n  [Qual è la differenza?](/it/bloc-concepts#cubit-vs-bloc);\n- Aggiungere eventi con [context.read](/it/flutter-bloc-concepts#contextread);\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- [RepositoryProvider](/it/flutter-bloc-concepts#repositoryprovider), widget\n  Flutter che fornisce un repository ai suoi figli;\n- [BlocListener](/it/flutter-bloc-concepts#bloclistener), widget Flutter che\n  invoca codice in risposta ai cambiamenti di stato nel bloc;\n- Aggiungere eventi con [context.read](/it/flutter-bloc-concepts#contextselect).\n\n## Configurazione\n\nInizieremo creando un nuovo progetto Flutter.\n\n<FlutterCreateSnippet />\n\nProprio come nel [tutorial login](/it/tutorials/flutter-login), creeremo\npacchetti interni per strutturare meglio l'architettura dell'applicazione e\nmantenere confini chiari, massimizzando la riutilizzabilità e migliorando la\ntestabilità.\n\nIn questo caso, i pacchetti\n[firebase_auth](https://pub.dev/packages/firebase_auth) e\n[google_sign_in](https://pub.dev/packages/google_sign_in) costituiranno il\nnostro livello dati; creeremo quindi un `AuthenticationRepository` per comporre\ni dati provenienti dai due client API.\n\n## Authentication Repository\n\nL'`AuthenticationRepository` ha la responsabilità di astrarre i dettagli\nimplementativi dell'autenticazione e del recupero delle informazioni utente.\n\nIn questo caso si integrerà con Firebase, ma potremo cambiare l'implementazione\ninterna in futuro senza influenzare il resto dell'applicazione.\n\n### Configurazione\n\nInizieremo creando la cartella `packages/authentication_repository` e un\n`pubspec.yaml` alla radice del progetto.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\nSuccessivamente, possiamo installare le dipendenze eseguendo:\n\n<FlutterPubGetSnippet />\n\nnella directory `authentication_repository`.\n\nCome la maggior parte dei pacchetti, l'`authentication_repository` definirà la\nsua superficie API tramite\n`packages/authentication_repository/lib/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\nIl pacchetto `authentication_repository` esporrà un `AuthenticationRepository` e\ni relativi modelli.\n\n:::\n\nDiamo ora un'occhiata ai modelli.\n\n### User\n\nIl modello `User` descriverà un utente nel contesto del dominio di\nautenticazione.\n\nPer gli scopi di questo esempio, un utente sarà composto da `email`, `id`,\n`name` e `photo`.\n\n:::note\n\nSta a te definire come deve essere un utente nel contesto del tuo dominio.\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\nLa classe `User` estende [equatable](https://pub.dev/packages/equatable) per\nsovrascrivere i confronti di uguaglianza, permettendoci di confrontare diverse\nistanze di `User` per valore.\n\n:::\n\n:::tip\n\nÈ utile definire un `User` vuoto `static` per evitare di gestire `User` null e\nlavorare sempre con un oggetto `User` concreto.\n\n:::\n\n### Repository\n\nL'`AuthenticationRepository` ha il compito di astrarre l'implementazione\nsottostante dell'autenticazione e di recuperare l'utente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nL'`AuthenticationRepository` espone uno `Stream<User>` a cui possiamo\nsottoscriverci per essere notificati quando un `User` cambia.\n\nInoltre, espone metodi per `signUp`, `logInWithGoogle`,\n`logInWithEmailAndPassword` e `logOut`.\n\n:::note\n\nL'`AuthenticationRepository` è anche responsabile della gestione degli errori di\nbasso livello che possono verificarsi nel livello dati, esponendo un set pulito\ne semplice di errori allineati con il dominio.\n\n:::\n\nQuesto è tutto per l'`AuthenticationRepository`. Vediamo ora come integrarlo nel\nprogetto Flutter che abbiamo creato.\n\n## Configurazione Firebase\n\nDobbiamo seguire le\n[istruzioni di utilizzo firebase_auth](https://pub.dev/packages/firebase_auth#usage)\nper collegare la nostra applicazione a Firebase e abilitare\n[google_sign_in](https://pub.dev/packages/google_sign_in).\n\n:::caution\n\nRicorda di aggiornare il `google-services.json` su Android e i file\n`GoogleService-Info.plist` e `Info.plist` su iOS, altrimenti l'applicazione\nandrà in crash.\n\n:::\n\n## Dipendenze del Progetto\n\nPossiamo sostituire il `pubspec.yaml` generato alla radice del progetto con il\nseguente:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nNota che stiamo specificando una directory assets per tutti gli asset locali\ndelle nostre applicazioni.\n\nCrea una directory `assets` alla radice del tuo progetto e aggiungi l'asset\n[bloc logo](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\n(che useremo più tardi).\n\nPoi installa tutte le dipendenze:\n\n<FlutterPubGetSnippet />\n\n:::note\n\nDipendiamo dal pacchetto `authentication_repository` tramite path; questo ci\npermetterà di iterare rapidamente mantenendo una separazione chiara.\n\n:::\n\n## main.dart\n\nIl file `main.dart` può essere sostituito con il seguente:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nQui impostiamo alcune configurazioni globali per l'applicazione e chiamiamo\n`runApp` con un'istanza di `App`.\n\n:::note\n\nStiamo iniettando una singola istanza di `AuthenticationRepository` nell'`App`\ncome dipendenza esplicita del costruttore.\n\n:::\n\n## App\n\nProprio come nel [tutorial login](/it/tutorials/flutter-login), il nostro file\n`app.dart` fornisce all'applicazione un'istanza di `AuthenticationRepository`\ntramite `RepositoryProvider` e crea un'istanza di `AuthenticationBloc`\nrendendola disponibile.\n\nSuccessivamente, `AppView` utilizza l'`AuthenticationBloc` per gestire\nl'aggiornamento della route corrente in base all'`AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\nL'`AppBloc` è responsabile della gestione dello stato globale dell'applicazione.\n\nHa una dipendenza dall'`AuthenticationRepository` e si sottoscrive allo stream\n`user` per emettere nuovi stati in risposta ai cambiamenti dell'utente corrente.\n\n### State\n\nL'`AppState` consiste in un `AppStatus` e un `User`. Il costruttore di default\naccetta un `User` opzionale e reindirizza al costruttore privato con lo stato di\nautenticazione appropriato.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### Event\n\nL'`AppEvent` ha due sottoclassi:\n\n- `AppUserSubscriptionRequested`: notifica il bloc di sottoscriversi allo stream\n  utente;\n- `AppLogoutPressed`: notifica il bloc di un'azione di logout dell'utente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\nNel corpo del costruttore, le sottoclassi di `AppEvent` sono mappate ai loro\ngestori di eventi corrispondenti.\n\nNel gestore `_onUserSubscriptionRequested`, l'`AppBloc` usa `emit.onEach` per\nsottoscriversi allo stream utente dell'`AuthenticationRepository` ed emettere\nuno stato in risposta a ogni `User`.\n\n`emit.onEach` crea internamente una sottoscrizione allo stream e si occupa di\ncancellarla quando l'`AppBloc` o lo stream utente vengono chiusi.\n\nSe lo stream utente emette un errore, `addError` inoltra l'errore e lo stack\ntrace a qualsiasi `BlocObserver` in ascolto.\n\n:::caution\n\nSe `onError` viene omesso, eventuali errori sullo stream utente sono considerati\nnon gestiti e saranno lanciati da `onEach`. Di conseguenza, la sottoscrizione\nallo stream utente verrà cancellata.\n\n:::\n\n:::tip\n\nUn [`BlocObserver`](/it/bloc-concepts/#blocobserver-1) è ottimo per registrare\neventi Bloc, errori e cambiamenti di stato, specialmente nel contesto di\nanalytics e crash reporting.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## Modelli\n\nModelli di input per `Email` e `Password` sono utili per incapsulare la logica\ndi validazione e saranno usati sia nel `LoginForm` che nel `SignUpForm` (più\navanti nel tutorial).\n\nEntrambi i modelli di input sono realizzati usando il pacchetto\n[formz](https://pub.dev/packages/formz) e ci permettono di lavorare con un\noggetto validato piuttosto che con un tipo primitivo come `String`.\n\n### Email\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## Login Page\n\nLa `LoginPage` è responsabile della creazione e fornitura di un'istanza di\n`LoginCubit` al `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nÈ molto importante mantenere la creazione di bloc/cubit separata da dove vengono\nconsumati. Questo ti permetterà di iniettare facilmente istanze fittizie (mock)\ne testare la tua vista in isolamento.\n\n:::\n\n## Login Cubit\n\nIl `LoginCubit` gestisce il `LoginState` del form.\n\nEspone API per `logInWithCredentials`, `logInWithGoogle`, e viene notificato\nquando email o password vengono aggiornate.\n\n### State\n\nIl `LoginState` consiste in `Email`, `Password` e `FormzStatus`. I modelli\n`Email` e `Password` estendono `FormzInput` dal pacchetto\n[formz](https://pub.dev/packages/formz).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\nIl `LoginCubit` ha una dipendenza dall'`AuthenticationRepository` per accedere\nall'utente tramite credenziali o tramite Google Sign In.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\nAbbiamo usato un `Cubit` invece di un `Bloc` perché il `LoginState` è piuttosto\nsemplice e localizzato. Anche senza eventi, possiamo intuire cosa è successo\nosservando i cambiamenti di stato; inoltre, il nostro codice risulta più\nsemplice e conciso.\n\n:::\n\n## Login Form\n\nIl `LoginForm` renderizza il form in base al `LoginState` e invoca metodi sul\n`LoginCubit` in risposta alle interazioni dell'utente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\nIl `LoginForm` renderizza anche un pulsante \"Create Account\" che naviga alla\n`SignUpPage`, dove un utente può creare un nuovo account.\n\n## Sign Up Page\n\nLa struttura `SignUp` rispecchia quella di `Login` e consiste in `SignUpPage`,\n`SignUpView` e `SignUpCubit`.\n\nLa `SignUpPage` è responsabile solo di creare e fornire un'istanza del\n`SignUpCubit` al `SignUpForm` (esattamente come in `LoginPage`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\nCosì come il `LoginCubit`, anche il `SignUpCubit` dipende\ndall'`AuthenticationRepository` per la creazione di nuovi utenti.\n\n:::\n\n## Sign Up Cubit\n\nIl `SignUpCubit` gestisce lo stato del `SignUpForm` e comunica con\nl'`AuthenticationRepository` per creare nuovi utenti.\n\n### State\n\nIl `SignUpState` riutilizza gli stessi modelli di input form `Email` e\n`Password` perché la logica di validazione è identica.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\nIl `SignUpCubit` è estremamente simile al `LoginCubit`, con la principale\neccezione che espone un'API per inviare il form di registrazione anziché di\nlogin.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## Sign Up Form\n\nIl `SignUpForm` è responsabile del rendering del form in risposta al\n`SignUpState` e invoca metodi sul `SignUpCubit` in risposta alle interazioni\ndell'utente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## Home Page\n\nDopo che un utente accede o si registra con successo, lo stream `user` sarà\naggiornato. Questo porterà a un cambiamento di stato nell'`AuthenticationBloc` e\nfarà sì che l'`AppView` mostri la `HomePage`.\n\nDalla `HomePage`, l'utente può visualizzare le informazioni del proprio profilo\ne disconnettersi toccando l'icona di uscita nella `AppBar`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\nUna directory `widgets` è stata creata insieme alla directory `view` all'interno\ndella funzionalità `home` per componenti riutilizzabili specifici di quella\nfunzionalità. In questo caso un semplice widget `Avatar` è esportato e usato\nall'interno della `HomePage`.\n\n:::\n\n:::note\n\nQuando viene toccato l'`IconButton` di logout, un evento\n`AuthenticationLogoutRequested` viene aggiunto all'`AuthenticationBloc`, che\ndisconnette l'utente e lo riporta alla `LoginPage`.\n\n:::\n\nA questo punto abbiamo un'implementazione di login solida usando Firebase e\nabbiamo disaccoppiato il livello di presentazione dalla logica applicativa\nusando la Libreria Bloc.\n\nIl codice sorgente completo per questo esempio può essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: Flutter Infinite List\ndescription:\n  Una guida approfondita su come costruire una lista infinita Flutter con bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nIn questo tutorial, usando Flutter e bloc, implementeremo un'app che recupera\ndati tramite la rete e li carica mentre l'utente scorre una lista.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## Argomenti Chiave\n\n- Osservare i cambiamenti di stato con\n  [BlocObserver](/it/bloc-concepts#blocobserver);\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- Aggiungere eventi con [context.read](/it/flutter-bloc-concepts#contextread);\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- Usare il metodo `transformEvents` con Rx.\n\n## Configurazione\n\nInizieremo creando un nuovo progetto Flutter\n\n<FlutterCreateSnippet />\n\nPossiamo poi sostituire il contenuto di pubspec.yaml con\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\ne poi installare tutte le nostre dipendenze\n\n<FlutterPubGetSnippet />\n\n## Struttura del Progetto\n\n```\n├── lib\n│   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n│   |   |   └── post_event.dart\n│   |   |   └── post_state.dart\n│   |   └── models\n│   |   |   └── models.dart*\n│   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n│   |   |   └── view.dart*\n│   |   └── widgets\n│   |   |   └── bottom_loader.dart\n│   |   |   └── post_list_item.dart\n│   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nL'applicazione usa una struttura di directory guidata dalle funzionalità. Questa\nstruttura del progetto ci permette di scalare il progetto avendo funzionalità\nautonome. In questo esempio avremo solo una singola funzionalità (la\nfunzionalità post) ed è divisa in cartelle ognuna con rispettivamente un\n\"barrel\" file, indicati dall'asterisco (\\*).\n\n## REST API\n\nPer questa app demo, useremo\n[jsonplaceholder](http://jsonplaceholder.typicode.com) come nostra sorgente di\ndati.\n\n:::note\n\njsonplaceholder è un'API REST online che serve dati falsi; è molto utile per\ncostruire prototipi.\n\n:::\n\nApri una nuova scheda nel tuo browser e visita\nhttps://jsonplaceholder.typicode.com/posts?_start=0&_limit=2 per vedere cosa\nl'API restituisce.\n\n<PostsJsonSnippet />\n\n:::note\n\nNel nostro URL abbiamo specificato `start` e `limit` come parametri di query\nnella richiesta GET.\n\n:::\n\nOttimo! Ora che sappiamo come saranno strutturati i nostri dati, creiamo il\nmodello.\n\n## Modello Dati\n\nCreiamo `post.dart` e iniziamo a definire il modello del nostro oggetto Post.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post` è solo una classe con un `id`, `title`, e `body`.\n\n:::note\n\nEstendiamo [`Equatable`](https://pub.dev/packages/equatable) per poter\nconfrontare gli oggetti `Post`. Senza questo, dovremmo modificare manualmente la\nclasse per sovrascrivere `==` e `hashCode` per distinguere due oggetti `Post`.\nVedi [il pacchetto](https://pub.dev/packages/equatable) per maggiori dettagli.\n\n:::\n\nOra che abbiamo definito il modello `Post`, iniziamo a lavorare sul componente\ndi logica applicativa (bloc).\n\n## Post Events\n\nPrima di immergerci nell'implementazione, dobbiamo definire cosa farà il nostro\n`PostBloc`.\n\nAd alto livello, risponderà all'input dell'utente (scorrimento) e recupererà più\npost in modo che il livello di presentazione possa visualizzarli. Iniziamo\ncreando il nostro `Event`.\n\nIl nostro `PostBloc` risponderà solo a un singolo evento: `PostFetched`, che\nverrà aggiunto dal livello di presentazione ogni volta che ha bisogno di più\npost da visualizzare. Poiché `PostFetched` è un tipo di `PostEvent`, possiamo\ncreare `bloc/post_event.dart` e implementare l'evento in questo modo.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\nPer ricapitolare, il nostro `PostBloc` riceverà `PostEvents` e li convertirà in\n`PostStates`. Abbiamo definito tutti i nostri `PostEvents` (`PostFetched`),\nquindi ora definiamo il nostro `PostState`.\n\n## Post States\n\nIl nostro livello di presentazione avrà bisogno di diverse informazioni per\ndisporre correttamente l'interfaccia:\n\n- `PostInitial` - indica al livello di presentazione che deve renderizzare un\n  indicatore di caricamento mentre il batch iniziale di post viene caricato;\n- `PostSuccess` - indica al livello di presentazione che ha contenuto da\n  renderizzare:\n  - `posts` - sarà la `List<Post>` che verrà visualizzata;\n  - `hasReachedMax` - indica al livello di presentazione se ha o meno raggiunto\n    il numero massimo di post;\n- `PostFailure` - indica al livello di presentazione che si è verificato un\n  errore durante il recupero dei post.\n\nPossiamo ora creare `bloc/post_state.dart` e implementarlo così.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\nAbbiamo implementato `copyWith` così possiamo copiare un'istanza di\n`PostSuccess` e aggiornare zero o più proprietà convenientemente (questo tornerà\nutile più tardi).\n\n:::\n\nOra che abbiamo implementato i nostri `Events` e `States`, possiamo creare il\nnostro `PostBloc`.\n\n## Post Bloc\n\nPer semplicità, il nostro `PostBloc` avrà una dipendenza diretta su un client\nHTTP; tuttavia, in un'applicazione di produzione suggeriamo invece di iniettare\nun client API e usare il pattern repository [docs](/it/architecture).\n\nCreiamo `post_bloc.dart` e creiamo il nostro `PostBloc` vuoto.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\nDalla dichiarazione della classe possiamo vedere che il nostro `PostBloc`\nriceverà `PostEvents` come input e produrrà `PostStates`.\n\n:::\n\nSuccessivamente, dobbiamo registrare un gestore di eventi per gestire gli eventi\n`PostFetched` in arrivo. In risposta a un evento `PostFetched`, chiameremo\n`_fetchPosts` per recuperare i post dall'API.\n\n<PostBlocOnPostFetchedSnippet />\n\nIl nostro `PostBloc` emetterà nuovi stati tramite l'`Emitter<PostState>` fornito\nnel gestore di eventi. Consulta [concetti base](/it/bloc-concepts/#stream) per\nmaggiori informazioni.\n\nOgni volta che viene aggiunto un `PostEvent`, se è un evento `PostFetched` e ci\nsono più post da recuperare, il nostro `PostBloc` recupererà i prossimi 20 post.\n\nL'API restituirà un array vuoto se proviamo a recuperare oltre il numero massimo\ndi post (100), quindi se otteniamo un array vuoto, il nostro bloc emetterà lo\nstato corrente impostando `hasReachedMax` a `true`.\n\nSe non possiamo recuperare i post, emettiamo `PostFailure`.\n\nSe riusciamo a recuperare i post, emettiamo `PostSuccess` con l'intera lista di\npost.\n\nUn'ottimizzazione che possiamo fare è applicare il `throttle` all'evento\n`PostFetched` per evitare di sovraccaricare inutilmente la nostra API. Possiamo\nfarlo usando il parametro `transform` quando registriamo il gestore di eventi\n`_onFetched`.\n\n:::note\n\nPassare un `transformer` a `on<PostFetched>` ci permette di personalizzare come\ngli eventi sono processati.\n\n:::\n\n:::note\n\nAssicurati di importare\n[`package:stream_transform`](https://pub.dev/packages/stream_transform) per\nusare l'API `throttle`.\n\n:::\n\n<PostBlocTransformerSnippet />\n\nIl nostro `PostBloc` finito dovrebbe ora essere così:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\nOttimo! Ora che abbiamo finito di implementare la logica applicativa tutto ciò\nche resta da fare è implementare il livello di presentazione.\n\n## Livello di Presentazione\n\nNel nostro `main.dart` possiamo iniziare implementando la nostra funzione main e\nchiamando `runApp` per renderizzare il nostro widget root. Qui, possiamo anche\nincludere il nostro bloc observer per registrare transizioni e eventuali errori.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nNel nostro widget `App`, la radice del nostro progetto, possiamo poi impostare\nhome a `PostsPage`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nNel nostro widget `PostsPage`, usiamo `BlocProvider` per creare e fornire\nun'istanza di `PostBloc` al sottoalbero. Inoltre, aggiungiamo un evento\n`PostFetched` così che quando l'app si carica, richiede il batch iniziale di\nPosts.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\nSuccessivamente, dobbiamo implementare la vista `PostsList` che presenterà i\nnostri post e si collegherà al `PostBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList` è un `StatefulWidget` perché dovrà mantenere un `ScrollController`.\nIn `initState`, aggiungiamo un listener al `ScrollController` per poter\nrispondere agli eventi di scroll. Accediamo anche all'istanza `PostBloc` tramite\n`context.read<PostBloc>()`.\n\n:::\n\nProseguendo, il nostro metodo build restituisce un `BlocBuilder`. `BlocBuilder`\nè un widget Flutter dal\n[pacchetto flutter_bloc](https://pub.dev/packages/flutter_bloc) che gestisce la\ncostruzione di un widget in risposta a nuovi stati bloc. Ogni volta che lo stato\ndel nostro `PostBloc` cambia, la nostra funzione builder sarà chiamata con il\nnuovo `PostState`.\n\n:::caution\n\nDobbiamo ricordarci di pulire dopo di noi e eliminare il `ScrollController`\nquando lo `StatefulWidget` viene eliminato.\n\n:::\n\nOgni volta che l'utente scorre, calcoliamo quanto è stato scrollato nella pagina\ne se la distanza è ≥ 90% del `maxScrollExtent`, aggiungiamo un evento\n`PostFetched` per caricare più post.\n\nSuccessivamente, dobbiamo implementare il nostro widget `BottomLoader` che\nindicherà all'utente che stiamo caricando più post.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\nInfine, dobbiamo implementare `PostListItem` che renderizzerà un singolo `Post`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\nA questo punto, dovremmo essere in grado di eseguire l'app e tutto dovrebbe\nfunzionare; tuttavia, c'è un'altra cosa che possiamo fare.\n\nUn vantaggio aggiuntivo dell'usare la libreria bloc è che possiamo avere accesso\na tutte le transizioni in un unico posto.\n\nIl cambiamento da uno stato a un altro è chiamato transizione.\n\n:::note\n\nUna `Transition` consiste dello stato corrente, dell'evento e dello stato\nsuccessivo.\n\n:::\n\nAnche se in questa applicazione abbiamo solo un bloc, è abbastanza comune in\napplicazioni più grandi avere molti bloc che gestiscono diverse parti dello\nstato dell'applicazione.\n\nSe vogliamo essere in grado di fare qualcosa in risposta a tutte le\n`Transitions` possiamo semplicemente creare il nostro `BlocObserver`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\nTutto ciò che dobbiamo fare è estendere `BlocObserver` e sovrascrivere il metodo\n`onTransition`.\n\n:::\n\nOra ogni volta che si verifica una transizione del bloc, possiamo vedere la\ntransizione stampata nella console.\n\n:::note\n\nIn pratica, puoi creare diversi `BlocObservers` e poiché ogni cambiamento di\nstato è registrato, siamo in grado di strumentare molto facilmente le nostre\napplicazioni e tracciare tutte le interazioni dell'utente e i cambiamenti di\nstato in un unico posto!\n\n:::\n\nQuesto è tutto! Abbiamo ora implementato con successo una lista infinita in\nFlutter usando i pacchetti [bloc](https://pub.dev/packages/bloc) e\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) e abbiamo separato con\nsuccesso il livello di presentazione dalla logica applicativa.\n\nLa `PostsPage` non ha idea da dove vengono i `Post` o come sono recuperati. Al\ncontrario, il `PostBloc` non ha idea di come lo stato viene renderizzato,\nconverte semplicemente eventi in stati.\n\nIl codice sorgente completo per questo esempio può essere trovato\n[qui](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-login.mdx",
    "content": "---\ntitle: Flutter Login\ndescription:\n  Una guida approfondita su come costruire un flusso di login Flutter con bloc.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nIn questo tutorial, costruiremo un flusso per accedere al sistema, in Flutter,\nusando la libreria bloc.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## Argomenti Chiave\n\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- Aggiungere eventi con [context.read](/it/flutter-bloc-concepts#contextread);\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- [RepositoryProvider](/it/flutter-bloc-concepts#repositoryprovider), widget\n  Flutter che fornisce un repository ai suoi figli;\n- [BlocListener](/it/flutter-bloc-concepts#bloclistener), widget Flutter che\n  invoca il codice listener in risposta ai cambiamenti di stato nel bloc;\n- Aggiornare l'UI basata su una parte di uno stato bloc con\n  [context.select](/it/flutter-bloc-concepts#contextselect).\n\n## Configurazione del Progetto\n\nInizieremo creando un nuovo progetto Flutter\n\n<FlutterCreateSnippet />\n\nSuccessivamente, possiamo installare tutte le nostre dipendenze\n\n<FlutterPubGetSnippet />\n\n## Authentication Repository\n\nLa prima cosa che faremo è creare un pacchetto `authentication_repository` che\nsarà responsabile di gestire il dominio della autenticazione.\n\nInizieremo creando una directory `packages` alla radice del progetto che\nconterrà tutti i pacchetti interni. Dopodiché aggiungiamo la directory\n`packages/authentication_repository`.\n\nAd alto livello, la struttura della directory dovrebbe essere così:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\nSuccessivamente, possiamo creare un `pubspec.yaml` per il pacchetto\n`authentication_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\n`package:authentication_repository` sarà un pacchetto Dart puro senza alcuna\ndipendenza esterna.\n\n:::\n\nSuccessivamente, dobbiamo implementare la classe `AuthenticationRepository`\nstessa che sarà in\n`packages/authentication_repository/lib/src/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nL'`AuthenticationRepository` espone uno `Stream` di aggiornamenti\n`AuthenticationStatus` che sarà usato per notificare l'applicazione quando un\nutente accede o esce.\n\nInoltre, ci sono metodi `logIn` e `logOut` che sono simulati (stub) per\nsemplicità ma possono essere facilmente estesi per autenticare con\n`FirebaseAuth` per esempio o qualche altro provider di autenticazione.\n\n:::note\n\nPoiché stiamo mantenendo un `StreamController` internamente, un metodo `dispose`\nè esposto così che il controller possa essere chiuso quando non è più\nnecessario.\n\n:::\n\nInfine, dobbiamo creare\n`packages/authentication_repository/lib/authentication_repository.dart` che\nconterrà le esportazioni pubbliche:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\nQuesto è tutto per l'`AuthenticationRepository`, successivamente lavoreremo sul\n`UserRepository`.\n\n## User Repository\n\nProprio come con l'`AuthenticationRepository`, creeremo un pacchetto\n`user_repository` all'interno della directory `packages`.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\nSuccessivamente, creeremo il `pubspec.yaml` per il `user_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\nIl `user_repository` sarà responsabile del dominio utente ed esporrà API per\ninteragire con l'utente corrente.\n\nLa prima cosa che definiremo è il modello utente in\n`packages/user_repository/lib/src/models/user.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\nPer semplicità, un utente ha solo una proprietà `id` ma potremmo aggiungere\nproprietà quali `firstName`, `lastName`, `avatarUrl`, ecc...\n\n:::note\n\n[`package:equatable`](https://pub.dev/packages/equatable) è usato per\nconfrontare le istanze `User` per valore e non per riferimento.\n\n:::\n\nSuccessivamente, possiamo creare un `models.dart` in\n`packages/user_repository/lib/src/models` che esporterà tutti i modelli. In\nquesto modo possiamo usare un singolo import per importare più modelli.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\nOra che i modelli sono stati definiti, possiamo implementare la classe\n`UserRepository` in `packages/user_repository/lib/src/user_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\nPer questo semplice esempio, il `UserRepository` espone un singolo metodo\n`getUser` che recupererà l'utente corrente. Stiamo simulando una risposta (stub)\nma nella pratica sarebbe il posto dove interrogheremmo l'utente corrente dal\nbackend.\n\nAbbiamo quasi finito con il pacchetto `user_repository` -- l'unica cosa che\nresta da fare è creare il file `user_repository.dart` in\n`packages/user_repository/lib` che definisce cosa esportare pubblicamente:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\nOra che abbiamo completato i pacchetti `authentication_repository` e\n`user_repository`, possiamo concentrarci sull'applicazione Flutter.\n\n## Installare Dipendenze\n\nIniziamo aggiornando il `pubspec.yaml` generato alla radice del nostro progetto:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nPossiamo installare le dipendenze eseguendo:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\nL'`AuthenticationBloc` sarà responsabile di reagire ai cambiamenti nello stato\ndi autenticazione (esposto dall'`AuthenticationRepository`) ed emetterà stati a\ncui possiamo reagire nel livello di presentazione.\n\nL'implementazione per l'`AuthenticationBloc` è all'interno di\n`lib/authentication` perché trattiamo l'autenticazione come una funzionalità nel\nnostro livello applicazione.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\nUsa l'\n[Estensione VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\no [Plugin IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) per creare\nbloc automaticamente.\n\n:::\n\n### authentication_event.dart\n\nLe istanze `AuthenticationEvent` saranno l'input dell'`AuthenticationBloc` e\nsaranno processate e usate per emettere nuove istanze `AuthenticationState`.\n\nIn questa applicazione, l'`AuthenticationBloc` reagirà a due diversi eventi:\n\n- `AuthenticationSubscriptionRequested`: evento iniziale che notifica il bloc di\n  sottoscriversi allo stream `AuthenticationStatus`;\n- `AuthenticationLogoutPressed`: notifica il bloc di un'azione di logout\n  dell'utente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\nSuccessivamente, diamo un'occhiata all'`AuthenticationState`.\n\n### authentication_state.dart\n\nLe istanze `AuthenticationState` saranno l'output dell'`AuthenticationBloc` e\nsaranno consumate dal livello di presentazione.\n\nLa classe `AuthenticationState` ha tre costruttori nominati:\n\n- `AuthenticationState.unknown()`: lo stato di default che indica che il bloc\n  non sa ancora se l'utente corrente è autenticato o meno;\n\n- `AuthenticationState.authenticated()`: lo stato che indica che l'utente è\n  attualmente autenticato;\n\n- `AuthenticationState.unauthenticated()`: lo stato che indica che l'utente non\n  è attualmente autenticato.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\nOra che abbiamo visto le implementazioni di `AuthenticationEvent` e\n`AuthenticationState` diamo un'occhiata all'`AuthenticationBloc`.\n\n### authentication_bloc.dart\n\nL'`AuthenticationBloc` gestisce lo stato di autenticazione dell'applicazione.\nTra i molteplici usi è adoperato per determinare la rotta, login o home page,\ndove l'utente farà l'accesso.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\nL'`AuthenticationBloc` ha una dipendenza sia sull'`AuthenticationRepository` che\nsul `UserRepository` e definisce lo stato iniziale come\n`AuthenticationState.unknown()`.\n\nNel corpo del costruttore, le sottoclassi di `AuthenticationEvent` sono mappate\nai loro corrispondenti gestori di eventi.\n\nNel gestore di eventi `_onSubscriptionRequested`, l'`AuthenticationBloc` usa\n`emit.onEach` per sottoscriversi allo stream `status` dell'\n`AuthenticationRepository` ed emettere uno stato in risposta a ogni\n`AuthenticationStatus`.\n\n`emit.onEach` crea una sottoscrizione internamente e si occupa di cancellarla\nquando l'`AuthenticationBloc` o lo stream `status` è chiuso.\n\nSe lo stream `status` emette un errore, `addError` inoltra l'errore e lo\nstackTrace a qualsiasi `BlocObserver` in ascolto.\n\n:::caution\n\nSe `onError` è omesso, eventuali errori sullo stream `status` sono considerati\nnon gestiti, e saranno lanciati da `onEach`. Di conseguenza, la sottoscrizione\nallo stream `status` sarà cancellato.\n\n:::\n\n:::tip\n\nUn [`BlocObserver`](/it/bloc-concepts/#blocobserver-1) è ottimo per registrare\nad eventi Bloc, errori e cambiamenti di stato specialmente nel contesto di\nanalytics e crash reporting.;\n\n:::\n\nQuando lo stream `status` emette `AuthenticationStatus.unknown` o\n`unauthenticated`, viene emesso lo `AuthenticationState` corrispondente.\n\nQuando viene emesso `AuthenticationStatus.authenticated`, l'`AuthentictionBloc`\ninterroga l'utente tramite il `UserRepository`.\n\n## main.dart\n\nSuccessivamente, possiamo sostituire il `main.dart` di default con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## App\n\n`app.dart` conterrà il widget `App` root per l'intera applicazione.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`app.dart` è diviso in due parti `App` e `AppView`. `App` è responsabile di\ncreare/fornire l'`AuthenticationBloc` che sarà consumato dal `AppView`. Questo\ndisaccoppiamento ci permetterà in seguito di testare facilmente sia il widget\n`App` che `AppView`.\n\n:::\n\n:::note\n\n`RepositoryProvider` è usato per fornire la singola istanza di\n`AuthenticationRepository` all'intera applicazione. Più tardi vedremo come\ntornerà utile.\n\n:::\n\nDi default, `BlocProvider` è lazy e non chiama `create` fino alla prima volta in\ncui viene richiesto l'accesso al Bloc. Poiché `AuthenticationBloc` dovrebbe\nsempre sottoscriversi immediatamente allo stream `AuthenticationStatus` (tramite\nl'evento `AuthenticationSubscriptionRequested`), possiamo esplicitamente\nrifiutare questo comportamento impostando `lazy: false`.\n\n`AppView` è un `StatefulWidget` perché mantiene una `GlobalKey` che è usata per\naccedere al `NavigatorState`. Di default, `AppView` renderizzerà la `SplashPage`\n(che vedremo dopo) e usa `BlocListener` per navigare a pagine diverse in base ai\ncambiamenti nell'`AuthenticationState`.\n\n## Splash\n\nLa funzionalità splash conterrà solo una vista semplice che sarà renderizzata\nquando l'app viene avviata mentre l'app determina se l'utente è autenticato.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage` espone una `Route` statica che rende molto facile navigare tramite\n`Navigator.of(context).push(SplashPage.route())`;\n\n:::\n\n## Login\n\nLa funzionalità login contiene una `LoginPage`, `LoginForm` e `LoginBloc` e\npermette agli utenti di inserire un username e password per accedere\nall'applicazione.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### Modelli Login\n\nStiamo usando [`package:formz`](https://pub.dev/packages/formz) per creare\nmodelli riutilizzabili e standard per `username` e `password`.\n\n#### Username\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\nPer semplicità, stiamo solo validando l'username per assicurarci che non sia\nvuoto ma nella pratica puoi imporre l'uso di caratteri speciali, lunghezza,\necc...\n\n#### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\nAnche per la password stiamo semplicemente eseguendo un controllo per\nassicurarci non sia vuota.\n\n#### Barrel Modelli\n\nProprio come prima, c'è un \"barrel\" `models.dart` per rendere facile importare i\nmodelli `Username` e `Password` con un singolo import.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\nIl `LoginBloc` gestisce lo stato del `LoginForm` e si occupa di validare l'input\nusername e password così come lo stato del form.\n\n#### login_event.dart\n\nIn questa applicazione ci sono tre diversi tipi di `LoginEvent`:\n\n- `LoginUsernameChanged`: notifica il bloc che l'username è stato modificato;\n- `LoginPasswordChanged`: notifica il bloc che la password è stata modificata;\n- `LoginSubmitted`: notifica il bloc che il form è stato inviato.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\nIl `LoginState` conterrà lo stato del form così come gli stati di input username\ne password.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\nI modelli `Username` e `Password` sono usati come parte del `LoginState` e lo\nstatus fa anche parte di [package:formz](https://pub.dev/packages/formz).\n\n:::\n\n#### login_bloc.dart\n\nIl `LoginBloc` è responsabile di reagire alle interazioni dell'utente nel\n`LoginForm` e gestire la validazione e l'invio del form.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\nIl `LoginBloc` ha una dipendenza sull'`AuthenticationRepository` perché quando\nil form viene inviato, invoca `logIn`. Lo stato iniziale del bloc è `pure` il\nche significa che non c'è stata alcuna interazione né con gli input né con il\nform.\n\nOgni volta che `username` o `password` cambiano, il bloc creerà una variante\n`dirty` del modello `Username`/`Password` e aggiornerà lo stato del form tramite\nl'API `Formz.validate`.\n\nQuando viene aggiunto l'evento `LoginSubmitted`, se lo stato corrente del form è\nvalido, il bloc fa una chiamata a `logIn` e aggiorna lo stato in base al\nrisultato della richiesta.\n\nDiamo ora un'occhiata alla `LoginPage` e alla `LoginForm`.\n\n### Login Page\n\nLa `LoginPage` è responsabile di esporre la `Route` così come creare e fornire\nil `LoginBloc` al `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\n`context.read<AuthenticationRepository>()` è usato per cercare l'istanza di\n`AuthenticationRepository` tramite il `BuildContext`.\n\n:::\n\n### Login Form\n\nIl `LoginForm` notifica gli eventi generati dall'utente al `LoginBloc` risponde\nai cambiamenti di stato attraverso `BlocBuilder` e `BlocListener`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`BlocListener` è usato per mostrare uno `SnackBar` se l'invio del login\nfallisce. In aggiunta, `context.select` è usato in ogni widget per accedere\nefficientemente a parti specifiche del `LoginState`, prevenendo aggiornamenti\nnon necessari. La callback `onChanged` è usato per notificare il `LoginBloc` dei\ncambiamenti a username/password.\n\nIl widget `_LoginButton` è abilitato solo se lo stato del form è valido e un\n`CircularProgressIndicator` è mostrato al suo posto mentre il form viene\ninviato.\n\n## Home\n\nDopo una richiesta `logIn` di successo, lo stato dell'`AuthenticationBloc`\ncambierà a `authenticated` e l'utente sarà riportato alla `HomePage` dove\nvisualizziamo l'`id` dell'utente così come un pulsante per disconnettersi.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### Home Page\n\nLa `HomePage` può accedere all'id utente corrente tramite\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` e lo\nvisualizza tramite un widget `Text`. Inoltre, quando viene toccato il pulsante\nlogout, un evento `AuthenticationLogoutPressed` viene aggiunto\nall'`AuthenticationBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` renderizzerà\nnuovamente `Text` solo se l'id utente cambia.\n\n:::\n\nA questo punto abbiamo un'implementazione di login abbastanza solida e abbiamo\ndisaccoppiato il nostro livello di presentazione dal livello di logica\napplicativa usando Bloc.\n\nIl codice sorgente completo per questo esempio (inclusi test unitari e widget)\npuò essere trovato\n[qui](https://github.com/felangel/Bloc/tree/master/examples/flutter_login).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: Flutter Timer\ndescription:\n  Una guida approfondita su come costruire un'app Flutter timer con bloc.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn questo tutorial vedremo come costruire un timer usando la libreria bloc.\nL'applicazione risultante dovrebbe essere così:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## Argomenti Chiave\n\n- Osservare i cambiamenti di stato con\n  [BlocObserver](/it/bloc-concepts#blocobserver);\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- Imparare a usare `StreamSubscription` in un Bloc;\n- Prevenire aggiornamenti non necessari con `buildWhen`.\n\n## Configurazione\n\nInizieremo creando un nuovo progetto Flutter:\n\n<FlutterCreateSnippet />\n\nPossiamo poi sostituire il contenuto di pubspec.yaml con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nUseremo i pacchetti [flutter_bloc](https://pub.dev/packages/flutter_bloc) e\n[equatable](https://pub.dev/packages/equatable) in questa app.\n\n:::\n\nSuccessivamente, esegui `flutter pub get` per installare tutte le dipendenze.\n\n## Struttura del Progetto\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## Ticker\n\nIl ticker sarà la sorgente di dati per l'applicazione timer. Esporrà uno stream\ndi \"tick\" a cui possiamo sottoscriverci e reagire.\n\nInizia creando `ticker.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\nTutto ciò che fa la classe `Ticker` è esporre una funzione `tick` che prende il\nnumero di tick (secondi) che vogliamo e restituisce uno stream che emette i\nsecondi rimanenti ogni secondo.\n\nSuccessivamente, dobbiamo creare il `TimerBloc` che consumerà il `Ticker`.\n\n## Timer Bloc\n\n### TimerState\n\nInizieremo definendo i `TimerStates` in cui il `TimerBloc` può trovarsi.\n\nLo stato del `TimerBloc` può essere uno dei seguenti:\n\n- `TimerInitial`: pronto per iniziare il conteggio alla rovescia dalla durata\n  specificata.\n- `TimerRunInProgress`: sta attivamente contando alla rovescia dalla durata\n  specificata.\n- `TimerRunPause`: in pausa a una durata rimanente.\n- `TimerRunComplete`: completato con una durata rimanente di 0.\n\nOgnuno di questi stati avrà un'implicazione sull'interfaccia utente e sulle\nazioni che l'utente può eseguire. Ad esempio:\n\n- se lo stato è `TimerInitial` l'utente sarà in grado di avviare il timer;\n- se lo stato è `TimerRunInProgress` l'utente sarà in grado di mettere in pausa\n  e resettare il timer così come vedere la durata rimanente;\n- se lo stato è `TimerRunPause` l'utente sarà in grado di riprendere il timer e\n  resettare il timer;\n- se lo stato è `TimerRunComplete` l'utente sarà in grado di resettare il timer.\n\nPer mantenere tutti i nostri file bloc insieme, creiamo una directory bloc con\n`bloc/timer_state.dart`.\n\n:::tip\n\nPuoi usare le\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) o\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nestensioni per autogenerare i seguenti file bloc per te.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\nNota che tutti i `TimerStates` estendono la classe base astratta `TimerState`\nche ha una proprietà duration. Questo è perché non importa in quale stato il\nnostro `TimerBloc` sia, vogliamo sempre sapere quanto tempo rimane. Inoltre,\n`TimerState` estende `Equatable` per ottimizzare il nostro codice assicurandoci\nche la nostra app non attivi aggiornamenti se viene emesso uno stato uguale al\nprecedente.\n\nSuccessivamente, definiamo e implementiamo i `TimerEvents` che il nostro\n`TimerBloc` processerà.\n\n### TimerEvent\n\nIl nostro `TimerBloc` dovrà sapere come processare i seguenti eventi:\n\n- `TimerStarted`: informa il TimerBloc che il timer dovrebbe essere avviato;\n- `TimerPaused`: informa il TimerBloc che il timer dovrebbe essere messo in\n  pausa;\n- `TimerResumed`: informa il TimerBloc che il timer dovrebbe essere ripreso;\n- `TimerReset`: informa il TimerBloc che il timer dovrebbe essere resettato allo\n  stato originale;\n- `_TimerTicked`: informa il TimerBloc che si è verificato un tick e che deve\n  aggiornare il suo stato di conseguenza.\n\nSe non hai usato le\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) o\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nestensioni, crea `bloc/timer_event.dart` e implementiamo quegli eventi.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\nSuccessivamente, implementiamo il `TimerBloc`!\n\n### TimerBloc\n\nSe non l'hai già fatto, crea `bloc/timer_bloc.dart` e crea un `TimerBloc` vuoto.\n\n<TimerBlocEmptySnippet />\n\nLa prima cosa che dobbiamo fare è definire lo stato iniziale del nostro\n`TimerBloc`. In questo caso, vogliamo che il `TimerBloc` inizi nello stato\n`TimerInitial` con una durata preimpostata di 1 minuto (60 secondi).\n\n<TimerBlocInitialStateSnippet />\n\nSuccessivamente, dobbiamo definire la dipendenza sul nostro `Ticker`.\n\n<TimerBlocTickerSnippet />\n\nStiamo anche definendo una `StreamSubscription` per il nostro `Ticker` che\nimplementeremo tra poco.\n\nA questo punto, tutto ciò che resta da fare è implementare i gestori di eventi.\nPer migliorare la leggibilità, mi piace separare ogni gestore di eventi in una\nfunzione dedicata. Inizieremo con l'evento `TimerStarted`.\n\n<TimerBlocOnStartedSnippet />\n\nSe il `TimerBloc` riceve un evento `TimerStarted`, emette uno stato\n`TimerRunInProgress` con la durata iniziale. Inoltre, se c'era già una\n`_tickerSubscription` aperta dobbiamo cancellarla per deallocare la memoria.\nDobbiamo anche sovrascrivere il metodo `close` sul nostro `TimerBloc` così che\npossiamo cancellare la `_tickerSubscription` quando il `TimerBloc` è chiuso.\nInfine, ascoltiamo lo stream `_ticker.tick` e su ogni tick aggiungiamo un evento\n`_TimerTicked` con la durata rimanente.\n\nSuccessivamente, implementiamo il gestore di eventi `_TimerTicked`.\n\n<TimerBlocOnTickedSnippet />\n\nOgni volta che viene ricevuto un evento `_TimerTicked`, se la durata del tick è\nmaggiore di 0, dobbiamo emettere uno stato `TimerRunInProgress` aggiornato con\nla nuova durata. Altrimenti, se la durata del tick è 0, il nostro timer è\nterminato e dobbiamo emettere uno stato `TimerRunComplete`.\n\nOra implementiamo il gestore di eventi `TimerPaused`.\n\n<TimerBlocOnPausedSnippet />\n\nIn `_onPaused` se lo `state` del nostro `TimerBloc` è `TimerRunInProgress`,\nallora possiamo mettere in pausa la `_tickerSubscription` ed emettere uno stato\n`TimerRunPause` con la durata del timer corrente.\n\nSuccessivamente, implementiamo il gestore di eventi `TimerResumed` così possiamo\nriprendere il timer.\n\n<TimerBlocOnResumedSnippet />\n\nIl gestore di eventi `TimerResumed` è molto simile al gestore di eventi\n`TimerPaused`. Se il `TimerBloc` ha uno `state` di `TimerRunPause` e riceve un\nevento `TimerResumed`, riprende la `_tickerSubscription` ed emette uno stato\n`TimerRunInProgress` con la durata corrente.\n\nInfine, dobbiamo implementare il gestore di eventi `TimerReset`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\nSe il `TimerBloc` riceve un evento `TimerReset`, deve cancellare la corrente\n`_tickerSubscription` così che non viene notificato di tick aggiuntivi ed emette\nuno stato `TimerInitial` con la durata originale.\n\nQuesto è tutto per il `TimerBloc`. Ora tutto ciò che resta è implementare l'UI\nper il nostro Timer.\n\n## UI dell'Applicazione\n\n### MyApp\n\nPossiamo iniziare cancellando il contenuto di `main.dart` e sostituendolo con il\nseguente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nSuccessivamente, creiamo il nostro widget `App` in `app.dart`, che sarà la\nradice della nostra applicazione.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nSuccessivamente, dobbiamo implementare il nostro widget `Timer`.\n\n### Timer\n\nIl nostro widget `Timer` (`lib/timer/view/timer_page.dart`) sarà responsabile di\nvisualizzare il tempo rimanente insieme ai pulsanti appropriati che\npermetteranno agli utenti di avviare, mettere in pausa e resettare il timer.\n\n<TimerPageSnippet />\n\nFinora, stiamo solo usando `BlocProvider` per accedere all'istanza del nostro\n`TimerBloc`.\n\nSuccessivamente, implementeremo il nostro widget `Actions` che avrà le azioni\nappropriate (avvia, pausa e reset).\n\n### Barrel\n\nPer pulire i nostri import dalla sezione `Timer`, dobbiamo creare un file\n\"barrel\" `timer/timer.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### Actions\n\n<ActionsSnippet />\n\nIl widget `Actions` è solo un altro `StatelessWidget` che usa un `BlocBuilder`\nper ricostruire la UI ogni volta che otteniamo un nuovo `TimerState`. `Actions`\nusa `context.read<TimerBloc>()` per accedere all'istanza del `TimerBloc` e\nrestituisce diversi `FloatingActionButtons` in base allo stato corrente del\n`TimerBloc`. Ognuno dei `FloatingActionButtons` aggiunge un evento nel suo\ncallback `onPressed` per notificare il `TimerBloc`.\n\nSe vuoi un controllo granulare su quando la funzione `builder` viene chiamata\npuoi fornire opzionalmente un `buildWhen` a `BlocBuilder`. Il `buildWhen` prende\nlo stato precedente del bloc e lo stato corrente del bloc e restituisce un\n`booleano`. Se `buildWhen` restituisce `true`, `builder` sarà chiamato con\n`state` e il widget si ricostruirà. Se `buildWhen` restituisce `false`,\n`builder` non sarà chiamato con `state` e non si verificherà alcun\naggiornamento.\n\nIn questo caso, non vogliamo che il widget `Actions` sia ricostruito su ogni\ntick perché sarebbe inefficiente. Invece, vogliamo che `Actions` si ricostruisca\nsolo se il `runtimeType` del `TimerState` cambia (TimerInitial =>\nTimerRunInProgress, TimerRunInProgress => TimerRunPause, ecc...).\n\nDi conseguenza, se colorassimo casualmente i widget su ogngli aggiornamenti,\nsarebbe:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\nAnche se il widget `Text` è ricostruito su ogni tick, ricostruiamo le `Actions`\nsolo se necessario.\n\n:::\n\n### Background\n\nInfine, aggiungi il widget background come segue:\n\n<BackgroundSnippet />\n\n### Riassumendo\n\nQuesto è tutto! A questo punto abbiamo un'applicazione timer abbastanza solida\nche ricostruisce efficientemente i widget solo quando necessario.\n\nIl codice sorgente completo per questo esempio può essere trovato\n[qui](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Flutter Todos\ndescription: Guida completa alla creazione di un'app Flutter todos con bloc.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn questo tutorial costruiremo una applicazione in Flutter per spuntare le cose\nda fare usando la libreria bloc.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## Argomenti Chiave\n\n- [Bloc e Cubit](/it/bloc-concepts#cubit-vs-bloc) per gestire i vari stati delle\n  funzionalità;\n- [Architettura a Livelli](/it/architecture) per separazione delle\n  responsabilità e per facilitare la riutilizzabilità;\n- [BlocObserver](/it/bloc-concepts#blocobserver) per osservare i cambiamenti di\n  stato;\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- [BlocListener](/it/flutter-bloc-concepts#bloclistener), widget Flutter che\n  gestisce l'esecuzione di effetti collaterali (side-effects) in risposta ai\n  cambiamenti di stato;\n- [RepositoryProvider](/it/flutter-bloc-concepts#repositoryprovider), widget\n  Flutter per fornire un repository ai suoi figli;\n- [Equatable](/it/faqs/#quando-usare-equatable) per prevenire aggiornamenti non\n  necessari;\n- [MultiBlocListener](/it/flutter-bloc-concepts#multibloclistener), widget\n  Flutter che riduce l'annidamento quando si usano più BlocListener.\n\n## Configurazione\n\nInizieremo creando un nuovo progetto Flutter usando\n[very_good_cli](https://pub.dev/packages/very_good_cli).\n\n<FlutterCreateSnippet />\n\n:::note\n\nInstalla `very_good_cli` usando il seguente comando:\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\nSuccessivamente creeremo i pacchetti `todos_api`, `local_storage_todos_api` e\n`todos_repository` usando `very_good_cli`:\n\n<FlutterCreatePackagesSnippet />\n\nPossiamo poi sostituire il contenuto di `pubspec.yaml` con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nInfine, installiamo tutte le dipendenze:\n\n<VeryGoodPackagesGetSnippet />\n\n## Struttura del Progetto\n\nLa struttura del progetto della nostra applicazione dovrebbe essere:\n\n<ProjectStructureSnippet />\n\nDividiamo il progetto in più pacchetti per mantenere esplicite le dipendenze di\nognuno e chiari i confini, rispettando il\n[principio della singola responsabilità](https://it.wikipedia.org/wiki/Principio_di_singola_responsabilit%C3%A0).\n\nModularizzare il progetto in questo modo offre molti vantaggi, inclusi:\n\n- facilitare il riutilizzo dei pacchetti tra più progetti;\n- migliorare CI/CD in termini di efficienza (eseguire controlli solo sul codice\n  modificato);\n- facilità nella manutenzione dei pacchetti, con suite di test dedicate,\n  versionamento semantico e cicli di rilascio indipendenti.\n\n## Architettura\n\n![Todos Architecture Diagram](~/assets/tutorials/todos-architecture.png)\n\nLa stratificazione del codice è fondamentale per iterare rapidamente e in\nsicurezza. Ogni strato ha una singola responsabilità e può essere usato e\ntestato in isolamento.\n\nQuesto ci permette di contenere le modifiche in uno strato specifico,\nminimizzando l'impatto sull'intera applicazione. Inoltre, stratificare\nl'applicazione ci consente di riutilizzare facilmente le librerie tra più\nprogetti (specialmente per quanto riguarda il livello dati).\n\nLa nostra applicazione consiste in tre strati principali:\n\n- livello dati;\n- livello dominio;\n- livello funzionalità (feature layer).\n  - presentazione/UI (widget)\n  - logica applicativa (bloc/cubit)\n\n**Livello Dati**\n\nQuesto è il livello più basso ed è responsabile del recupero di dati grezzi da\nsorgenti esterne come database, API e altro. I pacchetti nel livello dati non\ndovrebbero dipendere da alcuna UI e possono essere riutilizzati e persino\npubblicati su [pub.dev](https://pub.dev) come pacchetti standalone.\n\nIn questo esempio, il nostro livello dati consiste nei pacchetti `todos_api` e\n`local_storage_todos_api`.\n\n**Livello Dominio**\n\nQuesto strato combina uno o più data provider e applica \"regole di business\" ai\ndati. Ogni componente in questo strato è chiamato repository e ogni repository\ngestisce generalmente un singolo dominio. I pacchetti nel livello repository\ndovrebbero interagire solo con il livello dati.\n\nIn questo esempio, il livello repository consiste nel pacchetto\n`todos_repository`.\n\n**Livello Funzionalità**\n\nQuesto strato contiene tutte le funzionalità e i casi d'uso specifici\ndell'applicazione. Ogni funzionalità è generalmente composta da una parte UI e\ndalla logica applicativa. Le funzionalità dovrebbero essere indipendenti tra\nloro per poter essere aggiunte o rimosse facilmente senza impattare il resto del\nprogetto.\n\nAll'interno di ogni funzionalità, lo stato e la logica applicativa sono gestiti\ndai bloc. I bloc interagiscono con zero o più repository, reagiscono agli eventi\ned emettono stati che attivano cambiamenti nella UI.\n\nI widget all'interno di ogni funzionalità dovrebbero dipendere solo dal bloc\ncorrispondente e renderizzare l'interfaccia basata sullo stato corrente. La UI\npuò notificare il bloc dell'input utente tramite eventi.\n\nIn questo esempio, l'applicazione consisterà delle funzionalità `home`,\n`todos_overview`, `stats` e `edit_todos`.\n\nOra che abbiamo visto gli strati ad alto livello, iniziamo a costruire la nostra\napplicazione partendo dal livello dati!\n\n## Livello Dati\n\nIl livello dati è lo strato più basso dell'applicazione e consiste in data\nprovider grezzi. I pacchetti in questo strato si occupano principalmente della\nprovenienza e del recupero dei dati.\n\nIn questo caso il livello dati consisterà del `TodosApi`, che è un'interfaccia,\ne del `LocalStorageTodosApi`, un'implementazione del `TodosApi` basata su\n`shared_preferences`.\n\n### TodosApi\n\nIl pacchetto `todos_api` esporterà un'interfaccia generica per interagire e\ngestire i todos. Successivamente implementeremo il `TodosApi` usando\n`shared_preferences`.\n\nAvere un'astrazione renderà facile supportare altre implementazioni senza dover\nmodificare altre parti dell'applicazione. Ad esempio, potremmo aggiungere un\n`FirestoreTodosApi` (che usa `cloud_firestore` invece di `shared_preferences`)\ncon modifiche minime al resto del codice.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### Modello Todo\n\nSuccessivamente definiremo il modello `Todo`.\n\nLa prima cosa da notare è che il modello `Todo` non vive nell'app, ma è parte\ndel pacchetto `todos_api`. Questo perché il `TodosApi` definisce API che\nrestituiscono e accettano oggetti `Todo`. Il modello è una rappresentazione Dart\ndell'oggetto Todo grezzo che sarà memorizzato e recuperato.\n\nIl modello `Todo` usa\n[json_serializable](https://pub.dev/packages/json_serializable) per gestire la\n(de)serializzazione JSON. Se stai seguendo il tutorial, dovrai eseguire la\n[generazione del codice](https://pub.dev/packages/json_serializable#running-the-code-generator)\nper risolvere gli errori del compilatore.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\n`json_map.dart` fornisce un `typedef` per il controllo del codice e il linting.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\nIl modello `Todo` è definito in `todos_api/models/todo.dart` ed è esportato da\n`package:todos_api/todos_api.dart`.\n\n#### Aggiornare Esportazioni\n\nIl modello `Todo` e il `TodosApi` sono esportati tramite file \"barrel\". Nota\ncome non importiamo il modello direttamente, ma lo importiamo in\n`lib/src/todos_api.dart` con un riferimento al file \"barrel\" del pacchetto:\n`import 'package:todos_api/todos_api.dart';`.\n\nAggiorna i file \"barrel\" per risolvere eventuali errori di import rimanenti:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Stream vs Futures\n\nIn una versione precedente di questo tutorial, il `TodosApi` era basato su\n`Future` piuttosto che su `Stream`.\n\nPer un esempio di API basata su `Future`, vedi\n[l'implementazione di Brian Egan nei suoi Architecture Samples](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core).\n\nUn'implementazione basata su `Future` potrebbe consistere in due metodi:\n`loadTodos` e `saveTodos` (nota il plurale). Questo significa che una lista\ncompleta di todos deve essere fornita al metodo ogni volta.\n\n- Una limitazione di questo approccio è che le operazioni CRUD standard (Create,\n  Read, Update e Delete) richiedono l'invio dell'intera lista di todos a ogni\n  chiamata. Ad esempio, in una schermata \"Aggiungi Todo\", non si può\n  semplicemente inviare il singolo todo aggiunto. Dobbiamo tenere traccia\n  dell'intera lista e fornirla completa quando persistiamo i dati.\n- Una seconda limitazione è che `loadTodos` è un recupero dati _una tantum_.\n  L'app deve contenere logica per richiedere aggiornamenti periodicamente.\n\nNell'implementazione corrente, il `TodosApi` espone uno `Stream<List<Todo>>`\ntramite `getTodos()` che segnalerà aggiornamenti in tempo reale a tutti i\nsottoscritti quando la lista di todos cambia. Inoltre, i todos possono essere\ncreati, eliminati o aggiornati individualmente. Ad esempio, sia eliminare che\nsalvare un todo viene fatto passando solo il `todo` come argomento. Non è\nnecessario fornire la lista appena aggiornata di tutti i todos ogni volta.\n\n### LocalStorageTodosApi\n\nQuesto pacchetto implementa il `todos_api` usando il pacchetto\n[`shared_preferences`](https://pub.dev/packages/shared_preferences).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## Livello Repository\n\nUn [repository](/it/architecture#repository) è parte del livello applicativo. Un\nrepository dipende da uno o più data provider privi di logica di dominio e\ncombina le loro API pubbliche in API che forniscono valore applicativo\n(\"business value\").\n\nIn aggiunta, avere un livello repository aiuta ad astrarre l'acquisizione dati\ndal resto dell'applicazione, permettendoci di cambiare dove e come i dati\nvengono memorizzati senza influenzare altre parti dell'app.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\nPer istanziare il repository è necessario specificare un `TodosApi`, che abbiamo\ndiscusso prima in questo tutorial; l'abbiamo quindi aggiunto come dipendenza nel\n`pubspec.yaml`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### Esportazioni Libreria\n\nOltre a esportare la classe `TodosRepository`, esportiamo anche il modello\n`Todo` dal pacchetto `todos_api`. Questo passo previene l'accoppiamento stretto\ntra l'applicazione e i data provider.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\nAbbiamo deciso di riesportare lo stesso modello `Todo` dal `todos_api`,\npiuttosto che ridefinire un modello separato nel `todos_repository`, perché in\nquesto caso abbiamo controllo completo del modello dati. In molti casi, il data\nprovider non sarà qualcosa sotto il tuo controllo. In quei casi, diventa\nimportante mantenere le proprie definizioni di modello nel livello repository\nper mantenere il pieno controllo dell'interfaccia e del contratto API.\n\n## Livello Funzionalità\n\n### Entrypoint\n\nIl punto di ingresso della nostra app è `main.dart`. In questo caso, ci sono tre\nversioni:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\nLa cosa più notevole è che l'implementazione concreta del\n`local_storage_todos_api` viene istanziata all'interno di ogni entrypoint.\n\n### Bootstrapping\n\n`bootstrap.dart` carica il `BlocObserver` e crea l'istanza di `TodosRepository`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### App\n\n`App` avvolge un widget `RepositoryProvider` che fornisce il repository a tutti\ni figli. Poiché sia i sottoalberi `EditTodoPage` che `HomePage` sono\ndiscendenti, tutti i bloc e cubit possono accedere al repository.\n\n`AppView` crea la `MaterialApp` e configura tema e localizzazioni.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### Theme\n\nFornisce la definizione del tema per la modalità chiara e scura.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### Home\n\nLa funzionalità home è responsabile della gestione della tab attualmente\nselezionata e della visualizzazione del sottoalbero corretto.\n\n#### HomeState\n\nCi sono solo due stati associati alle due schermate: `todos` e `stats`.\n\n:::note\n\n`EditTodo` è una route separata, quindi non fa parte dello `HomeState`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### HomeCubit\n\nUn cubit è appropriato in questo caso data la semplicità della logica\napplicativa. Abbiamo un metodo `setTab` per cambiare la tab selezionata.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### HomeView\n\n`view.dart` è un file \"barrel\" che esporta tutti i componenti UI rilevanti per\nla funzionalità home.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\n`home_page.dart` contiene la UI per la pagina root che l'utente vedrà all'avvio\ndell'app.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\nUna rappresentazione semplificata dell'albero dei widget per la `HomePage` è:\n\n<HomePageTreeSnippet />\n\nLa `HomePage` fornisce un'istanza di `HomeCubit` a `HomeView`. `HomeView` usa\n`context.select` per ricostruire selettivamente la view ogni volta che la tab\ncambia. Questo ci permette di testare facilmente il widget `HomeView` fornendo\nun `HomeCubit` fittizio (mock) e simulando lo stato.\n\nIl `BottomAppBar` contiene widget `HomeTabButton` che chiamano `setTab` sul\n`HomeCubit`. L'istanza del cubit viene recuperata tramite `context.read` e il\nmetodo appropriato viene invocato.\n\n:::caution\n\n`context.read` non ascolta i cambiamenti, è usato solo per accedere al\n`HomeCubit` e chiamare `setTab`.\n\n:::\n\n### TodosOverview\n\nLa funzionalità \"todos overview\" permette agli utenti di gestire i propri todos\ncreando, modificando, eliminando e filtrando gli elementi.\n\n#### TodosOverviewEvent\n\nCreiamo `todos_overview/bloc/todos_overview_event.dart` e definiamo gli eventi.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: Questo è l'evento di avvio. In risposta,\n  il bloc si sottoscrive allo stream di todos dal `TodosRepository`;\n- `TodosOverviewTodoDeleted`: Elimina un Todo;\n- `TodosOverviewTodoCompletionToggled`: Attiva/disattiva lo stato completato di\n  un todo;\n- `TodosOverviewToggleAllRequested`: Attiva/disattiva il completamento per tutti\n  i todos;\n- `TodosOverviewClearCompletedRequested`: Elimina tutti i todos completati;\n- `TodosOverviewUndoDeletionRequested`: Annulla un'eliminazione di todo (es.\n  eliminazione accidentale);\n- `TodosOverviewFilterChanged`: Prende un `TodosViewFilter` come argomento e\n  cambia la vista applicando un filtro.\n\n#### TodosOverviewState\n\nCreiamo `todos_overview/bloc/todos_overview_state.dart` e definiamo lo stato.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState` terrà traccia di una lista di todos, del filtro attivo, del\n`lastDeletedTodo` e dello status.\n\n:::note\n\nOltre ai getter e setter di default, abbiamo un getter personalizzato chiamato\n`filteredTodos`. L'interfaccia usa `BlocBuilder` per accedere a\n`state.filteredTodos` o `state.todos`.\n\n:::\n\n#### TodosOverviewBloc\n\nCreiamo `todos_overview/bloc/todos_overview_bloc.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nIl bloc non crea un'istanza del `TodosRepository` internamente. Si affida invece\na un'istanza del repository iniettata tramite costruttore.\n\n:::\n\n##### onSubscriptionRequested\n\nQuando viene aggiunto `TodosOverviewSubscriptionRequested`, il bloc inizia\nemettendo uno stato `loading`. In risposta, la UI può renderizzare un indicatore\ndi caricamento. Successivamente, usiamo `emit.forEach<List<Todo>>( ... )` che\ncrea una sottoscrizione allo stream di todos dal `TodosRepository`.\n\n:::caution\n\n`emit.forEach()` non è lo stesso `forEach()` usato dalle liste. Questo `forEach`\npermette al bloc di sottoscriversi a uno `Stream` ed emettere un nuovo stato per\nogni aggiornamento dello stream.\n\n:::\n\n:::note\n\n`stream.listen` non è mai chiamato direttamente in questo tutorial. Usare\n`await emit.forEach()` è un pattern più recente per sottoscriversi a uno stream\nche permette al bloc di gestire la sottoscrizione internamente.\n\n:::\n\nOra che la sottoscrizione è attiva, gestiremo gli altri eventi come aggiungere,\nmodificare ed eliminare i todos.\n\n##### onTodoSaved\n\n`_onTodoSaved` chiama semplicemente `_todosRepository.saveTodo(event.todo)`.\n\n:::note\n\n`emit` non è mai chiamato dentro `onTodoSaved` e in molti altri gestori di\neventi. Invece, questi notificano il repository che emette una lista aggiornata\ntramite lo stream todos. Vedi la sezione [flusso dati](#flusso-dati) per\nmaggiori informazioni.\n\n:::\n\n##### Undo\n\nLa funzionalità undo permette agli utenti di ripristinare l'ultimo elemento\neliminato. `_onTodoDeleted` fa due cose:\n\n1. Emette un nuovo stato con il `Todo` da eliminare (per tenerne traccia).\n2. Elimina il `Todo` tramite una chiamata al repository.\n\n`_onUndoDeletionRequested` viene eseguito quando arriva la richiesta di\nannullamento dalla interfaccia utente. Questo metodo:\n\n- Salva temporaneamente una copia dell'ultimo todo eliminato;\n- Aggiorna lo stato rimuovendo il `lastDeletedTodo`;\n- Ripristina l'eliminazione chiamando il repository.\n\n##### Filtraggio\n\n`_onFilterChanged` emette un nuovo stato con il nuovo filtro dell'evento.\n\n#### Modelli\n\nC'è un file modello che si occupa del filtraggio della vista.\n`todos_view_filter.dart` è un enum che rappresenta i tre filtri di vista e i\nmetodi per applicarli.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart` è il file \"barrel\" per le esportazioni.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\nPassiamo ora alla `TodosOverviewPage`.\n\n#### TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\nUna rappresentazione semplificata dell'albero dei widget per la\n`TodosOverviewPage` è:\n\n<TodosOverviewPageTreeSnippet />\n\nProprio come con la funzionalità `Home`, `TodosOverviewPage` fornisce un'istanza\ndel `TodosOverviewBloc` al sottoalbero tramite\n`BlocProvider<TodosOverviewBloc>`. Questo limita il `TodosOverviewBloc` solo ai\nwidget sotto `TodosOverviewPage`.\n\nCi sono tre widget che ascoltano i cambiamenti nel `TodosOverviewBloc`:\n\n1. Un `BlocListener` che ascolta gli errori. Il `listener` sarà chiamato solo\n   quando `listenWhen` restituisce `true`. Se lo status è\n   `TodosOverviewStatus.failure`, viene visualizzato uno `SnackBar`;\n2. Un secondo `BlocListener` che ascolta le eliminazioni. Quando un todo viene\n   eliminato, viene visualizzato uno `SnackBar` con un pulsante undo. Se\n   l'utente tocca undo, l'evento `TodosOverviewUndoDeletionRequested` sarà\n   aggiunto al bloc;\n3. Infine, usiamo un `BlocBuilder` per costruire la ListView che visualizza i\n   todos.\n\nL'`AppBar` contiene due azioni (dropdown) per filtrare e manipolare i todos.\n\n:::note\n\n`TodosOverviewTodoCompletionToggled` e `TodosOverviewTodoDeleted` vengono\naggiunti al bloc tramite `context.read`.\n\n:::\n\n`view.dart` è il file \"barrel\" che esporta `todos_overview_page.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Widget\n\n`widgets.dart` è un altro file \"barrel\" che esporta tutti i componenti usati\nall'interno della funzionalità `todos_overview`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart` è il `ListTile` per ogni elemento todo.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart` espone due opzioni per manipolare i todos:\n\n- `toggleAll`;\n- `clearCompleted`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart` espone tre opzioni di filtro:\n\n- `all`;\n- `activeOnly`;\n- `completedOnly`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### Stats\n\nLa funzionalità stats visualizza statistiche sui todos attivi e completati.\n\n#### StatsState\n\n`StatsState` tiene traccia delle informazioni di riepilogo e dello `StatsStatus`\ncorrente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### StatsEvent\n\n`StatsEvent` ha solo un evento chiamato `StatsSubscriptionRequested`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### StatsBloc\n\n`StatsBloc` dipende dal `TodosRepository` proprio come `TodosOverviewBloc`. Si\nsottoscrive allo stream todos tramite `_todosRepository.getTodos`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### Stats View\n\n`view.dart` è il file \"barrel\" per `stats_page`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart` contiene la interffacia utente per la pagina che visualizza le\nstatistiche dei todos.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\nUna rappresentazione semplificata dell'albero dei widget per la `StatsPage` è:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\n`TodosOverviewBloc` e `StatsBloc` comunicano entrambi con il `TodosRepository`,\nma è importante notare che non c'è comunicazione diretta tra i bloc. Vedi la\nsezione [flusso dati](#flusso-dati) per maggiori informazioni.\n\n:::\n\n### EditTodo\n\nLa funzionalità `EditTodo` permette agli utenti di modificare un elemento todo\nesistente e salvare le modifiche.\n\n#### EditTodoState\n\n`EditTodoState` tiene traccia delle informazioni necessarie durante la modifica\ndi un todo.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### EditTodoEvent\n\nGli eventi a cui il bloc reagirà sono:\n\n- `EditTodoTitleChanged`;\n- `EditTodoDescriptionChanged`;\n- `EditTodoSubmitted`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc` dipende dal `TodosRepository`, proprio come `TodosOverviewBloc` e\n`StatsBloc`.\n\n:::caution\n\nA differenza degli altri bloc, `EditTodoBloc` non si sottoscrive a\n`_todosRepository.getTodos`. È un bloc di \"sola scrittura\", il che significa che\nnon ha bisogno di leggere informazioni dal repository.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### Flusso Dati\n\nAnche se ci sono molte funzionalità che dipendono dalla stessa lista di todos,\nnon c'è comunicazione bloc-to-bloc. Invece, tutte le funzionalità sono\nindipendenti tra loro e si affidano al `TodosRepository` per ascoltare i\ncambiamenti nella lista di todos ed eseguire aggiornamenti.\n\nAd esempio, `EditTodo` non sa nulla delle funzionalità `TodosOverview` o\n`Stats`.\n\nQuando la UI invia un evento `EditTodoSubmitted`:\n\n1. `EditTodoBloc` gestisce la logica applicativa per aggiornare il\n   `TodosRepository`;\n2. `TodosRepository` notifica `TodosOverviewBloc` e `StatsBloc`;\n3. `TodosOverviewBloc` e `StatsBloc` notificano l'interfaccia che si aggiorna\n   con il nuovo stato.\n\n#### EditTodoPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\nCome per le funzionalità precedenti, `EditTodoPage` fornisce un'istanza del\n`EditTodoBloc` tramite `BlocProvider`. A differenza delle altre funzionalità,\n`EditTodoPage` è una route separata, motivo per cui espone un metodo `static`\n`route`. Questo rende facile spingere `EditTodoPage` sullo stack di navigazione\ntramite `Navigator.of(context).push(...)`.\n\nUna rappresentazione semplificata dell'albero dei widget per `EditTodoPage` è:\n\n<EditTodosPageTreeSnippet />\n\n## Riepilogo\n\nQuesto è tutto, abbiamo completato il tutorial! 🎉\n\nIl codice sorgente completo per questo esempio, inclusi test unitari e widget,\npuò essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/flutter_todos).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: Flutter Weather\ndescription:\n  Una guida approfondita su come costruire un'app Flutter weather con bloc.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn questo tutorial, costruiremo un'app per vedere le previsioni meteo in Flutter\nche dimostra come gestire più cubit per implementare temi dinamici,\npull-to-refresh e molto altro. L'app meteo recupererà dati meteo in tempo reale\ndall' API pubblica OpenMeteo e dimostrerà come separare l'applicazione in\nlivelli (dati, repository, logica applicativa e presentazione).\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## Requisiti del Progetto\n\nL'app dovrebbe permettere agli utenti di\n\n- Cercare una città su una pagina di ricerca dedicata;\n- Vedere una piacevole rappresentazione dei dati meteo restituiti da\n  [Open Meteo API](https://open-meteo.com);\n- Cambiare le unità visualizzate (metriche vs imperiali).\n\nInoltre,\n\n- Il tema dell'applicazione dovrebbe riflettere il meteo per la città scelta;\n- Lo stato dell'applicazione dovrebbe persistere tra le sessioni: cioè, l'app\n  dovrebbe ricordare il suo stato dopo la chiusura e la riapertura (usando\n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)).\n\n## Concetti Chiave\n\n- Osservare i cambiamenti di stato con\n  [BlocObserver](/it/bloc-concepts#blocobserver);\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- [RepositoryProvider](/it/flutter-bloc-concepts#repositoryprovider), widget\n  Flutter che fornisce un repository ai suoi figli;\n- [BlocListener](/it/flutter-bloc-concepts#bloclistener), widget Flutter che\n  invoca il codice listener in risposta ai cambiamenti di stato nel bloc;\n- [MultiBlocProvider](/it/flutter-bloc-concepts#multiblocprovider), widget\n  Flutter che unisce più widget BlocProvider in uno;\n- [BlocConsumer](/it/flutter-bloc-concepts#blocconsumer), widget Flutter che\n  espone un builder e listener per reagire a nuovi stati;\n- [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  per gestire e persistere lo stato.\n\n## Configurazione\n\nPer iniziare, crea un nuovo progetto flutter\n\n<FlutterCreateSnippet />\n\n### Struttura del Progetto\n\nL'app consisterà di funzionalità isolate in directory corrispondenti. Questo ci\npermette di scalare man mano che il numero di funzionalità aumenta e permette\nagli sviluppatori di lavorare su funzionalità diverse in parallelo.\n\nL'app può essere suddivisa in quattro funzionalità principali: **search,\nsettings, theme, weather**. Creiamo quelle directory.\n\n<FeatureTreeSnippet />\n\n### Architettura\n\nSeguendo le linee guida dell'[architettura bloc](/it/architecture),\nl'applicazione consisterà di diversi livelli.\n\nIn questo tutorial, i diversi livelli avranno i seguenti compiti:\n\n- **Dati**: recuperare dati meteo grezzi dall'API;\n- **Repository**: astrarre il livello dati ed esporre modelli di dominio per\n  l'applicazione da consumare;\n- **Logica Applicativa**: gestire lo stato di ogni funzionalità (informazioni\n  unità, dettagli città, temi, ecc.);\n- **Presentazione**: visualizzare informazioni meteo e raccogliere input dagli\n  utenti (pagina impostazioni, pagina ricerca ecc.).\n\n## Livello Dati\n\nPer questa applicazione useremo l' [API Open Meteo](https://open-meteo.com).\n\nCi concentreremo su due endpoint:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` per\n  ottenere una posizione per un dato nome città;\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true`\n  per ottenere il meteo per una data posizione.\n\nApri\n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)\nnel tuo browser per vedere la risposta per la città di Chicago. Useremo la\n`latitude` e `longitude` nella risposta per chiamare l'endpoint meteo.\n\nLa `latitude`/`longitutde` per Chicago è `41.85003`/`-87.65005`. Naviga a\n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)\nnel tuo browser e vedrai che la risposta per il meteo a Chicago conterrà tutti i\ndati di cui avremo bisogno per la nostra app.\n\n### OpenMeteo API Client\n\nL'OpenMeteo API Client è indipendente dalla nostra applicazione. Di conseguenza,\nlo creeremo come pacchetto interno (e potremmo persino pubblicarlo su\n[pub.dev](https://pub.dev)). Possiamo poi usare il pacchetto aggiungendolo al\n`pubspec.yaml` per il livello repository, che gestirà le richieste dati per la\nnostra applicazione meteo principale.\n\nCrea una nuova directory a livello progetto chiamata `packages`. Questa\ndirectory memorizzerà tutti i nostri pacchetti interni.\n\nAll'interno di questa directory, esegui il comando `flutter create` per creare\nun nuovo pacchetto chiamato `open_meteo_api` per il nostro client API.\n\n<FlutterCreateApiClientSnippet />\n\n### Modello Dati Meteo\n\nSuccessivamente, creiamo `location.dart` e `weather.dart` che conterranno i\nmodelli per le risposte degli endpoint API `location` e `weather`.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### Modello Location\n\nIl modello `location.dart` dovrà memorizzare i dati restituiti dall'API\nlocation, la cui risposta dovrebbe essere simile alla seguente:\n\n<LocationJsonSnippet />\n\nEcco il file `location.dart` che memorizza la risposta mostrata sopra:\n\n<LocationDartSnippet />\n\n#### Modello Weather\n\nSuccessivamente, lavoriamo su `weather.dart`. Il nostro modello meteo dovrebbe\nmemorizzare i dati restituiti dall'API meteo, la cui risposta dovrebbe essere\nsimile alla seguente:\n\n<WeatherJsonSnippet />\n\nEcco il file `weather.dart` che memorizza la risposta di cui sopra:\n\n<WeatherDartSnippet />\n\n### File Barrel\n\nCreiamo rapidamente un\n[file \"barrel\"](https://adrianfaciu.dev/posts/barrel-files/) `models.dart` per\navere un unico file di esportazione per tutti i modelli.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\nCreiamo anche un file \"barrel\" a livello pacchetto, `open_meteo_api.dart`\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\nNel livello superiore, `open_meteo_api.dart` esportiamo i modelli:\n\n<OpenMeteoLibrarySnippet />\n\n### Configurazione\n\nDobbiamo essere in grado di\n[serializzare e deserializzare](https://en.wikipedia.org/wiki/Serialization) i\nnostri modelli per lavorare con i dati delle API. Per fare questo, aggiungeremo\ni metodi `toJson` e `fromJson` ai nostri modelli.\n\nInoltre, abbiamo bisogno di un modo per\n[fare richieste HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)\nper recuperare i dati da un'API remota. Fortunatamente, ci sono numerosi\npacchetti popolari per fare proprio questo.\n\nUseremo i pacchetti [json_annotation](https://pub.dev/packages/json_annotation),\n[json_serializable](https://pub.dev/packages/json_serializable), e\n[build_runner](https://pub.dev/packages/build_runner) per generare le\nimplementazioni `toJson` e `fromJson` per noi.\n\nSuccessivamente, useremo anche il pacchetto\n[http](https://pub.dev/packages/http) per inviare richieste di rete all'API\nmeteo permettendo alla nostra applicazione di visualizzare i dati meteo\ncorrenti.\n\nAggiungiamo queste dipendenze al `pubspec.yaml`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\nRicorda di eseguire `flutter pub get` dopo aver aggiunto le dipendenze.\n\n:::\n\n### (De)Serializzazione\n\nAffinché la generazione del codice funzioni, dobbiamo annotare il nostro codice\nusando il seguente:\n\n- `@JsonSerializable` per etichettare classi che possono essere serializzate;\n- `@JsonKey` per fornire la rappresentazione dei nomi dei campi;\n- `@JsonValue` per fornire la rappresentazioni dei valori dei campi;\n- Implementare `JSONConverter` per creare dei convertitori ad-hoc.\n\nPer ogni file dobbiamo anche:\n\n- Importare `json_annotation`;\n- Includere il codice generato usando la parola chiave\n  [part](https://dart.dev/tools/pub/create-packages#organizing-a-package);\n- Includere il metodo `fromJson` per la deserializzazione.\n\n#### Modello Location\n\nEcco il nostro file `location.dart` completo:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### Modello Weather\n\nEcco il nostro file `weather.dart` completo:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### Crea File Build\n\nNella cartella `open_meteo_api`, crea un file `build.yaml`. Lo scopo di questo\nfile è gestire discrepanze tra convenzioni di nomenclatura nei nomi dei campi\n`json_serializable`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### Generazione Codice\n\nUsiamo `build_runner` per generare il codice.\n\n<BuildRunnerBuildSnippet />\n\n`build_runner` dovrebbe generare i file `location.g.dart` e `weather.g.dart`.\n\n### OpenMeteo API Client\n\nCreiamo il nostro client API in `open_meteo_api_client.dart` all'interno della\ndirectory `src` . La struttura del nostro progetto dovrebbe ora essere così:\n\n<OpenMeteoApiClientTreeSnippet />\n\nPossiamo ora usare il pacchetto [http](https://pub.dev/packages/http) che\nabbiamo aggiunto prima al file `pubspec.yaml` per fare richieste HTTP all'API\nmeteo e usare queste informazioni nella nostra applicazione.\n\nIl nostro client API esporrà due metodi:\n\n- `locationSearch` che restituisce un `Future<Location>`;\n- `getWeather` che restituisce un `Future<Weather>`.\n\n#### Location Search\n\nIl metodo `locationSearch` chiama l'API location e in caso di errore lancia il\nfallimento `LocationRequestFailure`. Il metodo completato è riportato di\nseguito:\n\n<LocationSearchMethodSnippet />\n\n#### Get Weather\n\nAllo stesso modo, il metodo `getWeather` chiama l'API meteo e in caso di errore\nlancia `WeatherRequestFailure`. Il metodo completato è così definito:\n\n<GetWeatherMethodSnippet />\n\nL'intero file dovrebbe risultare così:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### Aggiornamenti File Barrel\n\nConcludiamo questo pacchetto aggiungendo il nostro client API al file \"barrel\".\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### Test Unitari\n\nÈ particolarmente importante scrivere test unitari per il livello dati poiché è\nla fondazione della nostra applicazione. I test unitari ci daranno fiducia che\nil pacchetto si comporti come previsto.\n\n#### Configurazione\n\nPrima, abbiamo aggiunto il pacchetto [test](https://pub.dev/packages/test) al\nnostro pubspec.yaml che permette di scrivere facilmente test unitari.\n\nCreeremo un file di test per il client API così come per i due modelli.\n\n#### Test Location\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### Test Weather\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### Test API Client\n\nSuccessivamente, testiamo il nostro client API. Per assicurarci il corretto\nfunzionamento dovremmo testare che il nostro client API gestisca entrambe le\nchiamate API correttamente, inclusi casi limite.\n\n:::note\n\nNon vogliamo che i nostri test facciano chiamate API reali poiché il nostro\nobiettivo è testare la logica del client API (inclusi tutti i casi limite) e non\nl'API stessa. Per avere un ambiente di test consistente e controllato, useremo\n[mocktail](https://github.com/felangel/mocktail) (che abbiamo aggiunto al file\npubspec.yaml prima) per mockare il client `http`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### Copertura Test\n\nInfine, verifichiamo la copertura dei test per assicurarci che ogni riga di\ncodice sia testata almeno una volta.\n\n<FlutterTestCoverageSnippet />\n\n## Livello Repository\n\nL'obiettivo del nostro livello repository è astrarre il nostro livello dati e\nfacilitare la comunicazione con il livello bloc. Facendo questo, il resto del\nnostro progetto dipenderà solo dalle funzioni esposte e non dalle\nimplementazioni specifiche dei data provider. Questo ci permette di cambiare\ndata provider senza invalidare alcuna implementazione al livello applicativo. Ad\nesempio, se decidiamo di migrare da questa particolare API meteo, dovremmo\nessere in grado di creare un nuovo client API e sostituirlo senza dover fare\ncambiamenti all'API pubblica del repository o al livello applicativo.\n\n### Configurazione\n\nAll'interno della directory packages, esegui il seguente comando:\n\n<FlutterCreateRepositorySnippet />\n\nUseremo gli stessi pacchetti del pacchetto `open_meteo_api` incluso il pacchetto\n`open_meteo_api` definito nel passo precedente. Aggiorna il tuo `pubspec.yaml`\ned esegui `flutter pub get`.\n\n:::note\n\nStiamo usando un `path` per specificare la posizione di `open_meteo_api` che ci\npermette di trattarlo proprio come un pacchetto esterno da `pub.dev`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### Modelli Weather Repository\n\nCreeremo un nuovo file `weather.dart` per esporre un modello meteo specifico del\ndominio. Questo modello conterrà solo dati rilevanti per i nostri casi\napplicativi -- in altre parole dovrebbe essere completamente disaccoppiato dal\nclient API . Come al solito, creeremo anche un file \"barrel\" `models.dart`.\n\n<RepositoryModelsBarrelTreeSnippet />\n\nQuesta volta, il nostro modello meteo memorizzerà solo le proprietà\n`location, temperature, condition`. Continueremo anche ad annotare il nostro\ncodice per generare il codice di serializzazione e deserializzazione.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\nAggiorna il file \"barrel\" che abbiamo creato prima per includere i modelli.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### Crea File Build\n\nCome prima, dobbiamo creare un file `build.yaml` con il seguente contenuto:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### Generazione Codice\n\nCome abbiamo fatto prima, esegui il seguente comando per generare l'\nimplementazione (de)serializzazione.\n\n<BuildRunnerBuildSnippet />\n\n#### File Barrel\n\nCreiamo anche un file \"barrel\" a livello di pacchetto chiamato\n`packages/weather_repository/lib/weather_repository.dart` per esportare i nostri\nmodelli:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\nL'obiettivo principale del `WeatherRepository` è fornire un'interfaccia che\nastrae il data provider. In questo caso, il `WeatherRepository` avrà una\ndipendenza sul `WeatherApiClient` ed esporrà un singolo metodo pubblico,\n`getWeather(String city)`.\n\n:::note\n\nI consumatori del `WeatherRepository` non sono a conoscenza dei dettagli di\nimplementazione sottostanti come il fatto che vengono fatte due richieste di\nrete all' API meteo. L'obiettivo del `WeatherRepository` è separare il \"cosa\"\ndal \"come\" -- in altre parole, vogliamo avere un modo per recuperare il meteo\nper una data città, ma non ci interessa come o da dove provengono quei dati.\n\n:::\n\n#### Configurazione\n\nCreiamo il file `weather_repository.dart` all'interno della directory `src` del\nnostro pacchetto e lavoriamo sull'implementazione del repository.\n\nIl metodo principale su cui ci concentreremo è `getWeather(String city)`.\nPossiamo implementarlo usando due chiamate al client API come segue:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### File Barrel\n\nAggiorna il file \"barrel\" che abbiamo creato prima.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### Test Unitari\n\nProprio come con il livello dati, è critico testare il livello repository per\nassicurarci che la logica a livello dominio sia corretta. Per testare il nostro\n`WeatherRepository`, useremo la libreria\n[mocktail](https://github.com/felangel/mocktail). Mockeremo il client API\nsottostante per testare unitariamente la logica `WeatherRepository` in un\nambiente isolato e controllato.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## Livello Logica Applicativa\n\nNel livello di logica applicativa, consumeremo il modello meteo di dominio dal\n`WeatherRepository` ed esporremo un nuovo modello a livello di funzionalità che\nsarà mostrato all'utente tramite l'interfaccia.\n\n:::note\n\nQuesto è il terzo diverso tipo di modello meteo che stiamo implementando. Nel\nclient API , il nostro modello meteo conteneva tutte le informazioni restituite\ndall'API. Nel livello repository, il nostro modello meteo conteneva solo il\nmodello astratto basato sul nostro caso applicativo. In questo strato, il nostro\nmodello meteo conterrà le sole informazioni rilevanti necessarie per il set di\nfunzionalità corrente.\n\n:::\n\n### Configurazione\n\nPoiché il nostro livello di logica applicativa risiede nella nostra app\nprincipale, dobbiamo modificare il `pubspec.yaml` per l'intero progetto\n`flutter_weather` e includere tutti i pacchetti che useremo.\n\n- Usare [equatable](https://pub.dev/packages/equatable) permetterà alle istanze\n  delle classi di essere confrontate (`==`) per valore. Sotto il cofano, bloc\n  confronterà i nostri stati per vedere se sono uguali, e se non lo sono,\n  attiverà un aggiornamento. Questo garantisce che il nostro albero dei widget\n  si ricostruisca solo quando necessario per mantenere le prestazioni veloci e\n  reattive;\n- Possiamo ravvivare la nostra interfaccia utente con\n  [google_fonts](https://pub.dev/packages/google_fonts);\n- [HydratedBloc](https://pub.dev/packages/hydrated_bloc) ci permette di\n  persistere lo stato dell'applicazione quando l'app viene chiusa e riaperta;\n- Includeremo il pacchetto `weather_repository` che abbiamo appena creato per\n  permetterci di recuperare i dati meteo correnti.\n\nPer i test, vorremmo includere il solito pacchetto `test`, insieme a `mocktail`\nper mockare dipendenze e [bloc_test](https://pub.dev/packages/bloc_test), per\nabilitare test facili di unità di logica applicativa, o bloc!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nSuccessivamente, lavoreremo sul livello applicativo all'interno della directory\n`weather` .\n\n### Modello Weather\n\nL'obiettivo del nostro modello meteo è tenere traccia dei dati meteo\nvisualizzati dalla nostra app, così come l'unità di misura della temperatura\n(Celsius o Fahrenheit).\n\nCrea `flutter_weather/lib/weather/models/weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### Crea File Build\n\nCrea un file `build.yaml` per il livello di logica applicativa.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### Generazione Codice\n\nEsegui `build_runner` per generare le implementazioni (de)serializzazione.\n\n<BuildRunnerBuildSnippet />\n\n### File Barrel\n\nEsportiamo i nostri modelli dal file \"barrel\"\n(`flutter_weather/lib/weather/models/models.dart`):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\nPoi, creiamo un file \"barrel\" weather di livello superiore\n(`flutter_weather/lib/weather/weather.dart`);\n\n<WeatherBarrelDartSnippet />\n\n### Weather\n\nUseremo `HydratedCubit` per abilitare la nostra app a ricordare il suo stato\ndell'applicazione, anche dopo che è stata chiusa e riaperta.\n\n:::note\n\n`HydratedCubit` è un'estensione di `Cubit` che gestisce la persistenza e\nripristino dello stato tra le sessioni.\n\n:::\n\n#### Weather State\n\nUsando l'\n[Estensione Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\no [Plugin Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc), fai\nclic destro sulla directory `weather` e crea un nuovo cubit chiamato `Weather`.\nLa struttura del progetto dovrebbe essere così:\n\n<WeatherCubitTreeSnippet />\n\nCi sono quattro stati in cui la nostra app meteo può essere:\n\n- `initial` prima che qualcosa si carichi;\n- `loading` durante la chiamata API;\n- `success` se la chiamata API ha successo;\n- `failure` se la chiamata API non ha successo.\n\nL'enum `WeatherStatus` rappresenterà quanto sopra.\n\nLo stato meteo completo dovrebbe essere così:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\nOra che abbiamo definito il `WeatherState`, scriviamo il `WeatherCubit` che\nesporrà i seguenti metodi:\n\n- `fetchWeather(String? city)` usa il nostro weather repository per provare a\n  recuperare un oggetto meteo per la città data;\n- `refreshWeather()` recupera un nuovo oggetto meteo usando il weather\n  repository dato lo stato meteo corrente;\n- `toggleUnits()` attiva/disattiva lo stato tra Celsius e Fahrenheit;\n- `fromJson(Map<String, dynamic> json)`, `toJson(WeatherState state)` usati per\n  persistenza.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\nRicorda di generare il codice (de)serializzazione tramite:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### Test Unitari\n\nSimile ai livelli dati e repository, è critico testare unitariamente il livello\ndi logica applicativa per assicurarci che la logica a livello di funzionalità si\ncomporti come ci aspettiamo. Ci affideremo a\n[bloc_test](https://pub.dev/packages/bloc_test) in aggiunta a `mocktail` e\n`test`.\n\nAggiungiamo i pacchetti `test`, `bloc_test`, e `mocktail` alle\n`dev_dependencies`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nIl pacchetto [bloc_test](https://pub.dev/packages/bloc_test) ci permette di\npreparare facilmente i nostri bloc per i test, gestire i cambiamenti di stato e\ncontrollare i risultati in un modo consistente.\n\n:::\n\n#### Test Weather Cubit\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## Livello Presentazione\n\n### Weather Page\n\nInizieremo con la `WeatherPage` che usa `BlocProvider` per fornire un'istanza\ndel `WeatherCubit` all'albero dei widget.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\nNoterai che la pagina dipende dai widget `SettingsPage` e `SearchPage`, che\ncreeremo successivamente.\n\n### SettingsPage\n\nLa pagina impostazioni permette agli utenti di aggiornare le loro preferenze per\nl'unità di misura della temperatura.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### SearchPage\n\nLa pagina ricerca permette agli utenti di inserire il nome della loro città\ndesiderata e fornisce il risultato della ricerca alla route precedente tramite\n`Navigator.of(context).pop`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Widget Weather\n\nL'app visualizzerà componenti diversi a seconda dei quattro possibili stati del\n`WeatherCubit`.\n\n#### WeatherEmpty\n\nQuesto widget verrà mostrato quando non ci sono dati da visualizzare perché\nl'utente non ha ancora selezionato una città.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### WeatherError\n\nQuesto componente verrà visualizzato in presenza di un errore.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### WeatherLoading\n\nQuesto widget verrà visualizzato mentre l'applicazione recupera i dati.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### WeatherPopulated\n\nQuesta componente verrà visualizzato una volta che l'utente ha selezionato una\ncittà e vengono recuperati i dati.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### File Barrel\n\nAggiungiamo questi widget a un file \"barrel\" per pulire i nostri import.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### Entrypoint\n\nIl nostro file `main.dart` dovrebbe inizializzare la nostra `WeatherApp` e\n`BlocObserver` (per scopi di debug), così come impostare il nostro\n`HydratedStorage` per persistere lo stato tra le sessioni.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nIl nostro widget `app.dart` gestirà la costruzione della vista `WeatherPage` che\nabbiamo precedentemente creato e userà `BlocProvider` per iniettare il nostro\n`WeatherCubit`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### Test Widget\n\nLa libreria [`bloc_test`](https://pub.dev/packages/bloc_test) espone anche\n`MockBlocs` e `MockCubits` che rendono facile testare l'UI. Possiamo mockare gli\nstati dei vari cubit e assicurarci che l'UI reagisca correttamente.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\nStiamo usando un `MockWeatherCubit` insieme all'API `when` da `mocktail` per\nsimulare (stub) lo stato del cubit in ognuno dei casi di test. Questo ci\npermette di simulare tutti gli stati e verificare che l'interfaccia si comporti\ncorrettamente in tutte le circostanze.\n\n:::\n\n## Riepilogo\n\nQuesto è tutto, abbiamo completato il tutorial! 🎉\n\nPossiamo eseguire l'app finale usando il comando `flutter run`.\n\nIl codice sorgente completo per questo esempio, inclusi test unitari e widget,\npuò essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/flutter_weather).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/github-search.mdx",
    "content": "---\ntitle: GitHub Search\ndescription:\n  Guida completa alla creazione di un'app GitHub Search in Flutter e AngularDart\n  con bloc.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn questo tutorial costruiremo un'app GitHub Search in Flutter e AngularDart per\ndimostrare come condividere i livelli dati e la logica applicativa tra i due\nprogetti.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## Argomenti Chiave\n\n- [BlocProvider](/it/flutter-bloc-concepts#blocprovider), widget Flutter che\n  fornisce un'istanza di bloc ai suoi figli;\n- [BlocBuilder](/it/flutter-bloc-concepts#blocbuilder), widget Flutter che\n  gestisce la costruzione del widget in risposta a nuovi stati;\n- Usare Cubit invece di Bloc.\n  [Qual è la differenza?](/it/bloc-concepts#cubit-vs-bloc);\n- Prevenire aggiornamenti non necessari con\n  [Equatable](/it/faqs/#quando-usare-equatable);\n- Usare un `EventTransformer` personalizzato con\n  [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency);\n- Eseguire richieste di rete usando il pacchetto `http`.\n\n## Libreria Condivisa GitHub Search\n\nLa libreria \"Common GitHub Search\" conterrà i modelli, il data provider, il\nrepository e il bloc che saranno condivisi tra AngularDart e Flutter.\n\n### Configurazione\n\nInizieremo creando una nuova directory per l'applicazione.\n\n<SetupSnippet />\n\n:::note\n\nLa directory `common_github_search` conterrà la libreria condivisa.\n\n:::\n\nDobbiamo creare un `pubspec.yaml` con le dipendenze richieste.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\nInfine, installiamo le dipendenze.\n\n<DartPubGetSnippet />\n\nQuesto è tutto per il setup del progetto! Ora possiamo iniziare a lavorare sulla\ncostruzione del pacchetto `common_github_search`.\n\n### Github Client\n\nIl `GithubClient` fornirà i dati grezzi\ndall'[API GitHub](https://developer.github.com/v3/).\n\n:::note\n\nPuoi vedere un esempio dei dati che otterremo\n[qui](https://api.github.com/search/repositories?q=dartlang).\n\n:::\n\nCreiamo `github_client.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\nIl nostro `GithubClient` esegue semplicemente una richiesta di rete all'API\nRepository Search di Github e converte il risultato in un `SearchResult` o\n`SearchResultError` come `Future`.\n\n:::\n\n:::note\n\nL'implementazione di `GithubClient` dipende da `SearchResult.fromJson`, che non\nabbiamo ancora implementato.\n\n:::\n\nSuccessivamente dobbiamo definire i nostri modelli `SearchResult` e\n`SearchResultError`.\n\n#### Modello Search Result\n\nCrea `search_result.dart`, che rappresenta una lista di `SearchResultItems`\nbasata sulla query dell'utente:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\nL'implementazione di `SearchResult` dipende da `SearchResultItem.fromJson`, che\nnon abbiamo ancora implementato.\n\n:::\n\n:::note\n\nNon includiamo proprietà che non verranno utilizzate nel nostro modello.\n\n:::\n\n#### Modello Search Result Item\n\nSuccessivamente, creeremo `search_result_item.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\nAnche qui, l'implementazione di `SearchResultItem` dipende da\n`GithubUser.fromJson`, che non abbiamo ancora implementato.\n\n:::\n\n#### Modello GitHub User\n\nCreiamo `github_user.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\nA questo punto abbiamo finito di implementare `SearchResult` e le sue\ndipendenze. Ora passeremo a `SearchResultError`.\n\n#### Modello Search Result Error\n\nCrea `search_result_error.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\nIl nostro `GithubClient` è finito. Passeremo ora al `GithubCache`, che sarà\nresponsabile della [memoizzazione](https://it.wikipedia.org/wiki/Memoizzazione)\nper ottimizzare le prestazioni.\n\n### GitHub Cache\n\nIl nostro `GithubCache` sarà responsabile di ricordare tutte le query passate,\nevitando così di fare richieste di rete non necessarie all'API GitHub. Questo\naiuterà anche a migliorare le prestazioni della nostra applicazione.\n\nCrea `github_cache.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\nOra siamo pronti a creare il nostro `GithubRepository`!\n\n### GitHub Repository\n\nIl Github Repository ha il compito di creare un'astrazione tra il livello dati\n(`GithubClient`) e il livello di logica applicativa (`Bloc`). È qui che\nutilizzeremo il nostro `GithubCache`.\n\nCrea `github_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\nIl `GithubRepository` ha una dipendenza da `GithubCache` e `GithubClient` e\nastrae l'implementazione sottostante. La nostra applicazione non deve mai sapere\ncome i dati vengono recuperati o da dove provengono. Possiamo cambiare il\nfunzionamento del repository in qualsiasi momento e, finché non cambiamo\nl'interfaccia, non dovremo modificare alcun codice client.\n\n:::\n\nA questo punto abbiamo completato il livello data provider e il livello\nrepository; siamo pronti a passare al livello di logica applicativa.\n\n### GitHub Search Event\n\nIl nostro Bloc sarà notificato quando un utente digita il nome di un repository;\nrappresenteremo questa azione come un `GithubSearchEvent` di tipo `TextChanged`.\n\nCrea `github_search_event.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\nEstendiamo [`Equatable`](https://pub.dev/packages/equatable) per poter\nconfrontare per valore istanze di `GithubSearchEvent`. Di default, l'operatore\ndi uguaglianza restituisce true se e solo se le istanze puntano allo stesso\nriferimento in memoria.\n\n:::\n\n### Github Search State\n\nIl nostro livello di presentazione necessita di diverse informazioni per\nrenderizzarsi correttamente:\n\n- `SearchStateEmpty`: indica al livello di presentazione che l'utente non ha\n  fornito input;\n- `SearchStateLoading`: indica al livello di presentazione di visualizzare un\n  indicatore di caricamento;\n- `SearchStateSuccess`: indica che ci sono dati da presentare.\n  - `items`: la `List<SearchResultItem>` che sarà visualizzata;\n- `SearchStateError`: indica che si è verificato un errore durante il recupero\n  dei repository.\n  - `error`: l'errore esatto che si è verificato.\n\nPossiamo ora creare `github_search_state.dart` e implementarlo così:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\nEstendiamo [`Equatable`](https://pub.dev/packages/equatable) per poter\nconfrontare istanze di `GithubSearchState` per valore e non per riferimento.\n\n:::\n\nOra che abbiamo implementato i nostri Eventi e Stati, possiamo creare il nostro\n`GithubSearchBloc`.\n\n### GitHub Search Bloc\n\nCrea `github_search_bloc.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\nIl nostro `GithubSearchBloc` converte `GithubSearchEvent` in `GithubSearchState`\ne ha una dipendenza da `GithubRepository`.\n\n:::\n\n:::note\n\nCreiamo un `EventTransformer` personalizzato per il\n[\"debounce\"](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)\ndei `GithubSearchEvent`. Una delle ragioni per cui abbiamo creato un `Bloc`\ninvece di un `Cubit` è proprio per sfruttare i trasformatori di stream.\n\n:::\n\nFantastico! Abbiamo finito con il nostro pacchetto `common_github_search`. Il\nprodotto finito dovrebbe essere simile a\n[questo](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search).\n\nSuccessivamente, lavoreremo sull'implementazione Flutter.\n\n## Flutter GitHub Search\n\nFlutter Github Search sarà un'applicazione Flutter che riutilizza modelli, data\nprovider, repository e bloc da `common_github_search` per implementare la\nricerca GitHub.\n\n### Configurazione\n\nIniziamo creando un nuovo progetto Flutter nella nostra directory\n`github_search` allo stesso livello di `common_github_search`.\n\n<FlutterCreateSnippet />\n\nDobbiamo aggiornare il nostro `pubspec.yaml` per includere tutte le dipendenze\nnecessarie.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\nStiamo includendo la nostra libreria `common_github_search` appena creata come\ndipendenza.\n\n:::\n\nOra installiamo le dipendenze.\n\n<FlutterPubGetSnippet />\n\nQuesto è tutto per il setup del progetto. Poiché il pacchetto\n`common_github_search` contiene il nostro livello dati e la logica applicativa,\ntutto ciò che dobbiamo costruire è il livello di presentazione.\n\n### Search Form\n\nDovremo creare un form con i widget `_SearchBar` e `_SearchBody`.\n\n- `_SearchBar` sarà responsabile della gestione dell'input utente;\n- `_SearchBody` sarà responsabile della visualizzazione dei risultati di\n  ricerca, indicatori di caricamento ed errori.\n\nCreiamo `search_form.dart`. Il nostro `SearchForm` sarà un `StatelessWidget` che\nrenderizza i widget `_SearchBar` e `_SearchBody`.\n\n`_SearchBar` sarà uno `StatefulWidget` perché dovrà mantenere il proprio\n`TextEditingController` per tracciare l'input dell'utente.\n\n`_SearchBody` è un `StatelessWidget` responsabile di mostrare risultati, errori\ne caricamenti. Sarà il consumatore del `GithubSearchBloc`.\n\nSe il nostro stato è `SearchStateSuccess`, renderizziamo `_SearchResults` (che\nimplementeremo successivamente). `_SearchResults` è un `StatelessWidget` che\nprende una `List<SearchResultItem>` e la visualizza come lista di\n`_SearchResultItem`.\n\n`_SearchResultItem` è un `StatelessWidget` responsabile del rendering delle\ninformazioni per un singolo risultato. Gestisce anche l'interazione dell'utente,\nnavigando all'url del repository al tocco.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar` accede a `GitHubSearchBloc` tramite\n`context.read<GithubSearchBloc>()` e notifica il bloc degli eventi\n`TextChanged`.\n\n:::\n\n:::note\n\n`_SearchBody` usa `BlocBuilder` per ricostruire la UI in risposta ai cambiamenti\ndi stato. Poiché il parametro bloc dell'oggetto `BlocBuilder` è stato omesso,\n`BlocBuilder` eseguirà automaticamente un lookup usando `BlocProvider` e il\n`BuildContext` corrente. Leggi di più\n[qui](/it/flutter-bloc-concepts#blocbuilder).\n\n:::\n\n:::note\n\nUsiamo `ListView.builder` per costruire una lista scrollabile di\n`_SearchResultItem`.\n\n:::\n\n:::note\n\nUsiamo il pacchetto [url_launcher](https://pub.dev/packages/url_launcher) per\naprire url esterni.\n\n:::\n\n### Riassumendo\n\nOra tutto ciò che resta da fare è implementare la nostra app principale in\n`main.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\nIl nostro `GithubRepository` è creato in `main` e iniettato nel nostro `App`. Il\nnostro `SearchForm` è avvolto in un `BlocProvider` che è responsabile di\ninizializzare, chiudere e rendere disponibile l'istanza di `GithubSearchBloc` al\nwidget `SearchForm` e ai suoi figli.\n\n:::\n\nQuesto è tutto! Abbiamo implementato con successo un'app di ricerca GitHub in\nFlutter usando i pacchetti [bloc](https://pub.dev/packages/bloc) e\n[flutter_bloc](https://pub.dev/packages/flutter_bloc), separando il livello di\npresentazione dalla logica applicativa.\n\nIl codice sorgente completo può essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search).\n\nInfine, costruiremo la nostra app AngularDart GitHub Search.\n\n## AngularDart GitHub Search\n\nAngularDart GitHub Search sarà un'applicazione AngularDart che riutilizza i\nmodelli, data provider, repository e bloc da `common_github_search` per\nimplementare Github Search.\n\n### Configurazione\n\nDobbiamo iniziare creando un nuovo progetto AngularDart nella nostra directory\ngithub_search, allo stesso livello di `common_github_search`.\n\n<StagehandSnippet />\n\n:::note\n\nPuoi installare `stagehand` tramite:\n\n<ActivateStagehandSnippet />\n\n:::\n\nPossiamo poi sostituire il contenuto di `pubspec.yaml` con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### Search Form\n\nProprio come nella nostra app Flutter, dovremo creare un `SearchForm` con un\ncomponente `SearchBar` e `SearchBody`.\n\nIl nostro componente `SearchForm` implementerà `OnInit` e `OnDestroy` perché\ndovrà creare e chiudere un `GithubSearchBloc`.\n\n- `SearchBar` sarà responsabile della gestione dell'input utente;\n- `SearchBody` sarà responsabile della visualizzazione dei risultati di ricerca,\n  indicatori di caricamento ed errori.\n\nCreiamo `search_form_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\nIl `GithubRepository` è iniettato nel `SearchFormComponent`.\n\n:::\n\n:::note\n\nIl `GithubSearchBloc` è creato e chiuso dal `SearchFormComponent`.\n\n:::\n\nIl nostro template (`search_form_component.html`) sarà:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\nSuccessivamente, implementeremo il componente `SearchBar`.\n\n### Search Bar\n\n`SearchBar` è un componente responsabile di prendere l'input dell'utente e\nnotificare il `GithubSearchBloc` dei cambiamenti di testo.\n\nCrea `search_bar_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent` ha una dipendenza su `GitHubSearchBloc` perché è\nresponsabile di notificare il bloc degli eventi `TextChanged`.\n\n:::\n\nSuccessivamente, possiamo creare `search_bar_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\nAbbiamo finito con `SearchBar`, ora passiamo a `SearchBody`.\n\n### Search Body\n\n`SearchBody` è il componente responsabile della visualizzazione di risultati,\nerrori e indicatori di caricamento. Sarà il consumatore del `GithubSearchBloc`.\n\nCrea `search_body_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent` ha una dipendenza su `GithubSearchState` che viene fornito\ndal `GithubSearchBloc` usando la bloc pipe di `angular_bloc`.\n\n:::\n\nCrea `search_body_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\nSe il nostro stato `isSuccess`, renderizziamo `SearchResults`. Lo implementeremo\nora.\n\n### Search Results\n\n`SearchResults` è un componente che prende una `List<SearchResultItem>` e la\nvisualizza come una lista di `SearchResultItem`.\n\nCrea `search_results_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\nSuccessivamente creeremo `search_results_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\nUsiamo `ngFor` per costruire una lista di componenti `SearchResultItem`.\n\n:::\n\nÈ ora di implementare `SearchResultItem`.\n\n### Search Result Item\n\n`SearchResultItem` è un componente responsabile del rendering delle informazioni\nper un singolo risultato di ricerca. Gestisce anche l'interazione dell'utente e\nla navigazione all'URL del repository.\n\nCrea `search_result_item_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\ne il template corrispondente in `search_result_item_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### Riassumendo\n\nAbbiamo tutti i nostri componenti ed è il momento di assemblarli nel nostro\n`app_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\nStiamo creando il `GithubRepository` nell'`AppComponent` e lo iniettiamo nel\ncomponente `SearchForm`.\n\n:::\n\nQuesto è tutto! Abbiamo implementato con successo un'app di ricerca GitHub in\nAngularDart usando i pacchetti `bloc` e `angular_bloc`, separando il livello di\npresentazione dalla logica applicativa.\n\nIl codice sorgente completo può essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search).\n\n## Riepilogo\n\nIn questo tutorial abbiamo creato un'app Flutter e una AngularDart condividendo\ntutti i modelli, data provider e bloc tra le due. L'unica cosa che abbiamo\ndovuto scrivere due volte è stato il livello di presentazione (UI), il che è\nfantastico in termini di efficienza e velocità di sviluppo.\n\nInoltre, è piuttosto comune che app web e mobile abbiano esperienze utente e\nstili diversi; questo approccio dimostra quanto sia facile costruire due app che\nappaiono totalmente diverse ma condividono gli stessi livelli di dati e logica\napplicativa.\n\nIl codice sorgente completo può essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/github_search).\n"
  },
  {
    "path": "docs/src/content/docs/it/tutorials/ngdart-counter.mdx",
    "content": "---\ntitle: AngularDart Counter\ndescription:\n  Una guida approfondita su come costruire un'app AngularDart counter con bloc.\nsidebar:\n  order: 8\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/ngdart-counter/StagehandSnippet.astro';\nimport InstallDependenciesSnippet from '~/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn questo tutorial, costruiremo un counter in AngularDart usando la libreria\nbloc.\n\n![demo](~/assets/tutorials/ngdart-counter.gif)\n\n## Configurazione\n\nInizieremo creando un nuovo progetto AngularDart con\n[stagehand](https://github.com/dart-lang/stagehand).\n\nSe non hai stagehand installato, attivalo tramite:\n\n<ActivateStagehandSnippet />\n\nPoi genera un nuovo progetto tramite:\n\n<StagehandSnippet />\n\nPossiamo poi sostituire il contenuto di `pubspec.yaml` con:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\ne poi installare tutte le nostre dipendenze\n\n<InstallDependenciesSnippet />\n\nLa nostra app counter avrà solo due pulsanti per incrementare/decrementare il\nvalore del counter e un elemento per visualizzare il valore corrente. Iniziamo\nprogettando i `CounterEvents`.\n\n## Counter Bloc\n\nPoiché lo stato del nostro counter può essere rappresentato da un intero non\nabbiamo bisogno di creare una classe personalizzata e possiamo co-localizzare\ngli eventi e il bloc.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_bloc.dart\"\n\ttitle=\"lib/src/counter_page/counter_bloc.dart\"\n/>\n\n:::note\n\nSolo dalla dichiarazione della classe possiamo dire che il nostro `CounterBloc`\nprenderà `CounterEvents` come input e produrrà interi.\n\n:::\n\n## Counter App\n\nOra che abbiamo il nostro `CounterBloc` completamente implementato, possiamo\niniziare creando il nostro Componente App AngularDart.\n\nIl nostro `app.component.dart` dovrebbe essere:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.dart\"\n\ttitle=\"lib/app_component.dart\"\n/>\n\ne il nostro `app.component.html` dovrebbe essere:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.html\"\n\ttitle=\"lib/app_component.html\"\n/>\n\n## Counter Page\n\nInfine, tutto quello che resta è costruire il nostro Componente Counter Page.\n\nIl nostro `counter_page_component.dart` dovrebbe essere:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.dart\"\n\ttitle=\"lib/src/counter_page/counter_page_component.dart\"\n/>\n\n:::note\n\nSiamo in grado di accedere all'istanza del `CounterBloc` usando il sistema di\ndependency injection di AngularDart. Poiché l'abbiamo registrato come\n`Provider`, AngularDart può risolvere correttamente `CounterBloc`.\n\n:::\n\n:::note\n\nStiamo chiudendo il `CounterBloc` in `ngOnDestroy`.\n\n:::\n\n:::note\n\nStiamo importando il `BlocPipe` così possiamo usarlo nel nostro template.\n\n:::\n\nInfine, il nostro `counter_page_component.html` dovrebbe essere:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.html\"\n\ttitle=\"lib/src/counter_page/counter_page_component.html\"\n/>\n\n:::note\n\nStiamo usando il `BlocPipe` così possiamo visualizzare lo stato del nostro\n`CounterBloc` mentre viene aggiornato.\n\n:::\n\nQuesto è tutto! Abbiamo separato il nostro livello di presentazione dal nostro\nlivello di logica applicativa. Il nostro `CounterPageComponent` non ha idea di\ncosa succede quando un utente preme un pulsante; aggiunge semplicemente un\nevento per notificare il `CounterBloc`. Inoltre, il nostro `CounterBloc` non ha\nidea di cosa sta succedendo con lo stato (valore del counter); sta semplicemente\nconvertendo i `CounterEvents` in interi.\n\nPossiamo eseguire la nostra app con `webdev serve` e visualizzarla localmente.\n\nIl codice sorgente completo per questo esempio può essere trovato\n[qui](https://github.com/felangel/bloc/tree/master/examples/angular_counter).\n"
  },
  {
    "path": "docs/src/content/docs/it/why-bloc.mdx",
    "content": "---\ntitle: Perché Bloc?\ndescription:\n  Una panoramica di cosa rende Bloc una soluzione solida per la gestione dello\n  stato.\nsidebar:\n  order: 1\n---\n\nBloc rende facile separare la presentazione dalla logica applicativa, rendendo\nil tuo codice _veloce_, _facile da testare_ e _riutilizzabile_.\n\nNella realizzazione di applicazioni \"ready-for-production\", la gestione dello\nstato diventa un aspetto cruciale.\n\nCome sviluppatori vogliamo:\n\n- sapere in qualsiasi momento in quale stato si trova l'applicazione;\n- testare facilmente ogni caso per assicurarci che l'app risponda\n  appropriatamente;\n- registrare ogni singola interazione dell'utente nell'applicazione in modo da\n  poter prendere decisioni \"data-driven\";\n- lavorare nel modo più efficiente possibile e riutilizzare componenti sia\n  all'interno dell'applicazione che attraverso altre applicazioni;\n- avere molti sviluppatori che lavorano senza problemi all'interno di un'unico\n  sorgente seguendo gli stessi pattern e convenzioni;\n- sviluppare app veloci e reattive.\n\nBloc è stato progettato per soddisfare tutte queste esigenze e molte altre\nancora.\n\nCi sono molte soluzioni di \"state management\" e decidere quale usare può essere\nun compito scoraggiante. Non esiste una soluzione perfetta di \"state\nmanagement\"! Quello che è importante è che tu scelga quella che funziona meglio\nper il tuo team e per il tuo progetto.\n\nBloc è stato progettato con tre valori fondamentali in mente:\n\n- **Semplice:** Facile da capire e utilizzabile da sviluppatori con diversi\n  livelli di competenza;\n- **Potente:** Aiuta a creare applicazioni fantastiche e complesse componendole\n  con elementi più piccoli;\n- **Testabile:** Permette di testare facilmente ogni aspetto di un'applicazione\n  così da poter evolvere il progetto in totale sicurezza.\n\nL'obiettivo di Bloc è rendere i cambiamenti di stato deterministici: lo fa\ncontrollando quando questi possono avvenire e garantendo che esista un solo modo\nper aggiornare lo stato nell'intera app.\n"
  },
  {
    "path": "docs/src/content/docs/ja/bloc-concepts.mdx",
    "content": "---\ntitle: Blocのコンセプト\ndescription: package:blocを構成する主要なコンセプトの概要です。\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\n[`package:bloc`](https://pub.dev/packages/bloc)を使用して作業する前に、本セクションを必ずお読みください。\n\n:::\n\nblocパッケージを使いこなすには、押さえておくべき重要なコンセプト（概念）がいくつかあります。\n\n本セクションでは、それぞれのコンセプトについて詳しく説明し、実際にカウンターアプリを実装していくことで、その仕組みがどのように使われるのかを解説していきます。\n\n## Stream\n\n:::note\n\n`Stream`の詳細については、公式の[Dartドキュメント](https://dart.dev/tutorials/language/streams)\nを確認してください。\n\n:::\n\n`Stream`とは、非同期なデータが順番に届く仕組みです。\n\nblocパッケージを使用するためには、`Stream`の基本的な仕組みを理解する必要があります。\n\n`Stream`に馴染みがない方は、水が流れているパイプを想像してみてください。パイプが`Stream`であり、水が非同期なデータです。\n\nDartでは、`async*`(async generator)関数を書くことで`Stream`を作成できます。\n\n<CountStreamSnippet />\n\n関数に`async*`を付けることで`yield`キーワードが使用できるようになり、データの`Stream`が返せるようになります。上記の例では、`0`から引数`max`に渡された値の1つ前まで、int型の値が順番に流れる`Stream`を返しています。\n\n`async*`関数内で`yield`するたびに、その値を`Stream`に送り出しています。\n\n上記の`Stream`はいくつかの方法で使用できます。このint型の`Stream`の合計値を返す関数を書きたい場合、次のようになります。\n\n<SumStreamSnippet />\n\n上記の関数に`async`を付けることで`await`キーワードが使用できるようになり、int型の`Future`を返すことが出来ます。この例では、`Stream`から値が流れてくるまで待機(`await`)し、流れてきたすべての整数の合計を返しています。\n\n<StreamsMainSnippet />\n\nさて、これでDartにおける`Stream`の基本的な仕組みについて理解できたので、blocパッケージの中核となるコンポーネントである`Cubit`について学ぶ準備が整いました。\n\n## Cubit\n\n`Cubit`は`BlocBase`を継承するクラスで、さまざまな型の状態(state)を管理するために拡張することが出来ます。\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nCubitは、状態を変化させるための関数を公開できます。この状態は`Cubit`の出力であり、アプリケーションの状態の一部を表します。UIコンポーネントは状態が変化したという通知を受け取り、現在の状態に基づいて自身の一部を再描画することが出来ます。\n\n:::note\n\n`Cubit`の起源について知りたい方は\n[こちらのIssue](https://github.com/felangel/cubit/issues/69) をご覧ください。\n\n:::\n\n### Cubitの作成\n\n`CounterCubit`は以下のようにして作成できます。\n\n<CounterCubitSnippet />\n\n`Cubit`を作成するためには、`Cubit`が管理する状態の型を定義する必要があります。上記の`CounterCubit`の場合、状態は`int`型で十分ですが、もっと複雑なケースでは、\n`int`型のようなプリミティブ型の代わりに`class`を使用する必要があるかもしれません。\n\nまた、初期状態も指定する必要があります。これは初期値の`super`を呼び出すことで行えます。上記のスニペットでは、初期状態を内部的に`0`に設定していますが、外部の値を受け入れることで`Cubit`をもっと柔軟にすることもできます。\n\n<CounterCubitInitialStateSnippet />\n\nこれにより、異なる初期状態で`CounterCubit`をインスタンス化できるようになります。\n\n<CounterCubitInstantiationSnippet />\n\n### Cubitの状態変化\n\n各`Cubit`は`emit`を使用することで新しい状態を出力する機能を持っています。\n\n<CounterCubitIncrementSnippet />\n\n上記のスニペットでは、`CounterCubit`が`increment`というメソッドを公開しています。このメソッドは外部から呼び出すことが可能で、\n`CounterCubit`に対し、状態をインクリメント（`1`を加算）するよう通知します。\n`increment`が呼び出されると、`getter`である`state`を介して`Cubit`の現在の状態にアクセスし、現在の状態に`1`を加えることで新しい状態を`emit`できます。\n\n:::caution\n\n`emit`メソッドはprotectedであり、`Cubit`の内部でのみ使用されるべきです。\n\n:::\n\n### Cubitを使用する\n\nそれでは、実装した`CounterCubit`を実際に使ってみましょう！\n\n#### 基本的な使い方\n\n<CounterCubitBasicUsageSnippet />\n\n上記のスニペットでは、まず`CounterCubit`のインスタンスを作成しています。その次の行の`print`では、まだ新しい状態が`emit`されていないため、\n`CounterCubit`の初期状態である`0`が出力されます。その後、`increment`関数を呼び出すことで状態変化を引き起こしています。その次の`print`で`Cubit`の状態が`0`から`1`に変わっていることが確認でき、最後に`Cubit`内の`Stream`を閉じるために`close`を呼び出しています。\n\n#### Streamの使い方\n\n`Cubit`は`Stream`を公開しており、購読することで状態の更新をリアルタイムに受け取れます。\n\n<CounterCubitStreamUsageSnippet />\n\n上記のスニペットでは、`CounterCubit`を購読することで、状態が変化するたびに`print`が呼び出されるようにしています。次に、新しい状態を`emit`する`increment`関数を呼び出しています。最後に、更新の受信が不要になったので購読をキャンセルしてから`Cubit`を閉じています。\n\n:::note\n\nこの例の`await Future.delayed(Duration.zero)`は、購読がすぐにキャンセルされてしまうのを避けるために追加されています。\n\n:::\n\n:::caution\n\n`Cubit`の`listen`を呼び出した際、それまでの状態変化は受け取れず、呼び出した以降の状態変化のみが受信できます。\n\n:::\n\n### Cubitの監視\n\n`Cubit`が新しい状態を`emit`すると、状態変化として`Change`が発生します。\n`onChange`をオーバーライドすることで、特定の`Cubit`のすべての`Change`を監視できます。\n\n<CounterCubitOnChangeSnippet />\n\nこれで、`Cubit`を使用することで、すべての変更がコンソールに出力されるのを確認できるようになります。\n\n<CounterCubitOnChangeUsageSnippet />\n\n上記の例は以下のように出力されます。\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change`は`Cubit`の状態が更新される直前に発生します。\n`Change`は`currentState`と`nextState`から構成されます。\n\n:::\n\n#### BlocObserver\n\nblocパッケージの使用には、すべての状態変化(`Change`)に一箇所でアクセスできるという利点もあります。このアプリケーションでは`Cubit`が1つしかありませんが、大規模なアプリケーションでは、さまざまな状態を管理するいくつもの`Cubit`を持つことが一般的です。\n\nすべての`Change`に反応して何らかの処理を実行したい場合、独自の`BlocObserver`を作成するだけで実現できます。\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\n必要なのは、`BlocObserver`を拡張して`onChange`メソッドをオーバーライドすることだけです。\n\n:::\n\n`SimpleBlocObserver`を使用するには、`main`関数を少しだけ調整する必要があります。\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\n上記のスニペットは次のように出力されます。\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nオーバーライドされた`onChange`メソッドが最初に呼び出され、そこで\n`super.onChange`を実行することで`BlocObserver`内の`onChange`メソッドを呼び出しています。\n\n:::\n\n:::tip\n\n`BlocObserver`では、`Change`に加えて`Cubit`インスタンス自体にもアクセスできます。\n\n:::\n\n### Cubitのエラーハンドリング\n\nすべての`Cubit`には、エラーを通知する`addError`メソッドが用意されています。\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError`を`Cubit`内でオーバーライドすることで、その`Cubit`のすべてのエラーを処理できます。\n\n:::\n\n`onError`は`BlocObserver`でもオーバーライド可能なので、アプリケーション内で通知されたすべてのエラーを処理できます。\n\n<SimpleBlocObserverOnErrorSnippet />\n\nプログラムを再度実行すると、以下の出力が表示されるはずです。\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n`Bloc`は、関数の代わりにイベント(`event`)を使って状態(`state`)の変化を引き起こす、\n`Cubit`よりも高度なクラスです。\n`Bloc`は`BlocBase`を継承しているため、`Cubit`と同様の公開メソッドを持っています。しかし、`Bloc`上で関数を呼び出して直接新しい状態を`emit`するのではなく、\n`Bloc`はイベントを受け取り、受信したイベントを状態に変換します。\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Blocの作成\n\n`Bloc`の作成は`Cubit`の作成と似ていますが、管理する状態(`state`)を定義することに加えて、\n`Bloc`が処理できるイベント(`event`)も定義する必要があります。\n\nイベントは`Bloc`への入力です。これらは通常、ボタンタップなどのユーザー操作や、ページ読み込みなどのライフサイクルイベントに応答する形で発生します。\n\n<CounterBlocSnippet />\n\n`CounterCubit`を作成した時と同様に、`super`を通じて親クラスに初期状態を渡す必要があります。\n\n### Blocの状態変化\n\n`Bloc`では、`Cubit`の関数とは異なり、`on<Event>`APIを使用してイベントハンドラーを登録する必要があります。このイベントハンドラーが、受信したイベントを新しい状態(`state`)に変換します。一つのイベントによって発生する状態変化は一つだけとは限らず、イベントの処理中に二回以上状態が変化することや、逆に一度も状態変化しないこともあります。\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\n`EventHandler`は、受け取ったイベントにアクセスでき、また、受け取ったイベントを状態(`state`)に変換する役割を担う`Emitter`にもアクセスできます。\n\n:::\n\nでは、`CounterIncrementPressed`イベントを処理できるように`EventHandler`を更新してみましょう。\n\n<CounterBlocIncrementSnippet />\n\n上記のスニペットでは、すべての`CounterIncrementPressed`イベントを管理するための`EventHandler`を登録しています。受信した各`CounterIncrementPressed`イベントに対して、\n`getter`である`state`を介して`CounterBloc`の現在の状態にアクセスし、\n`emit(state + 1)`を実行しています。\n\n:::note\n\n`Bloc`クラスは`BlocBase`を継承しているため、`Cubit`と同様に、\n`getter`である`state`を介していつでもBlocの現在の状態にアクセスできます。\n\n:::\n\n:::caution\n\n`Bloc`は新しい状態を直接`emit`してはいけません。その代わり、すべての状態変化は`EventHandler`内で行い、受信したイベントに応答する形で`emit`される必要があります。\n\n:::\n\n:::caution\n\n`Bloc`と`Cubit`は両方とも、重複した状態を無視します。\n`state == nextState`となるような`State nextState`を`emit`した場合、状態変化は発生しません。\n\n:::\n\n### Blocを使用する\n\nこれで、`CounterBloc`のインスタンスを作成して使用できるようになりました！\n\n#### 基本的な使い方\n\n<CounterBlocUsageSnippet />\n\n上記のスニペットでは、まず`CounterBloc`のインスタンスを作成しています。その次の行で`Bloc`の現在の状態を`print`していますが、ここではまだ新しい状態が`emit`されていないため、初期値の`0`が出力されます。次に、状態変化に応じた処理を行うために`CounterIncrementPressed`イベントを発生させています。最後に、`0`から`1`に変化した`Bloc`の状態を`print`し、\n`Bloc`内の`Stream`を閉じるために`close`を呼び出しています。\n\n:::note\n\n`await Future.delayed(Duration.zero)`を追加することで、処理を一旦終了し、次の処理サイクルで再開しています。これにより、`EventHandler`に処理の順番をゆずり、状態変化が適切に反映されるようになります。\n\n:::\n\n#### Streamの使い方\n\n`Cubit`と同様に、`Bloc`は特殊な`Stream`であるため、状態のリアルタイム更新のためにBlocを購読することも出来ます。\n\n<CounterBlocStreamUsageSnippet />\n\n上記のスニペットでは、`CounterBloc`を購読し、状態変化が起きるたびに`print`を呼び出しています。次に、`CounterIncrementPressed`イベントを発生させることで、登録された`on<CounterIncrementPressed>`ハンドラーが実行され、新しい状態を`emit`します。最後に、更新の受信が不要になったので、購読をキャンセルしてから`Bloc`を閉じています。\n\n:::note\n\nこの例における`await Future.delayed(Duration.zero)`は、購読がすぐにキャンセルされてしまうのを避けるために追加されています。\n\n:::\n\n### Blocの監視\n\n`Bloc`は`BlocBase`を継承しているため、`onChange`を使用することで`Bloc`のすべての状態変化を監視できます。\n\n<CounterBlocOnChangeSnippet />\n\nこの実装により、`main.dart`を以下のように更新できます。\n\n<CounterBlocOnChangeUsageSnippet />\n\n上記のスニペットを実行すると、出力は以下のようになります。\n\n<CounterBlocOnChangeOutputSnippet />\n\n`Bloc`と`Cubit`の重要な違いは、`Bloc`がイベント駆動であるため、状態変化を引き起こしたイベントの情報も取得できる点です。\n\nこれは`onTransition`をオーバーライドすることで実現できます。\n\nある状態から別の状態への変化は遷移(`Transition`)と呼ばれます。\n`Transition`は現在の状態、イベント、次の状態で構成されます。\n\n<CounterBlocOnTransitionSnippet />\n\n前と同じ`main.dart`スニペットを再実行すると、以下の出力が表示されるはずです。\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition`は`onChange`の前に呼び出され、\n`currentState`から`nextState`への変化を引き起こしたイベントも保持しています。\n\n:::\n\n#### BlocObserver\n\n前回と同様に、カスタマイズされた`BlocObserver`内で`onTransition`をオーバーライドすることで、発生するすべての遷移を一箇所で監視できます。\n\n<SimpleBlocObserverOnTransitionSnippet />\n\n以前と同じように`SimpleBlocObserver`を初期化します。\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\n上記のスニペットを実行すると、出力は以下のようになるはずです。\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition`が最初に呼び出され（`BlocObserver`よりも`Bloc`の`onTransition`が先）、その後に`onChange`が続きます。\n\n:::\n\n`Bloc`インスタンスのもう一つのユニークな機能は、\n`Bloc`に新しいイベントが発生するたびに呼び出される`onEvent`をオーバーライドできることです。\n`onChange`や`onTransition`と同様に、`onEvent`も`Bloc`と`BlocObserver`の両方でオーバーライドできます。\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\n前と同じ`main.dart`を実行すると、以下のような出力が表示されるはずです。\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent`はイベントが追加されるとすぐに呼び出されます。\n`Bloc`内の`onEvent`は`BlocObserver`内の`onEvent`よりも先に呼び出されます。\n\n:::\n\n### Blocのエラーハンドリング\n\n`Cubit`と同様に、各`Bloc`には`addError`と`onError`メソッドがあります。\n`addError`を呼び出すことで、`Bloc`内のどこからでもエラーが発生したことを通知できます。そして、`Cubit`と同じように`onError`をオーバーライドすることで、すべてのエラーに対応できます。\n\n<CounterBlocOnErrorSnippet />\n\n前回と同じ`main.dart`を再実行すると、エラーが通知されたときの様子が確認できます。\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\n`Bloc`内の`onError`が最初に呼び出され、その後に`BlocObserver`内の`onError`が呼び出されます。\n\n:::\n\n:::note\n\n`onError`と`onChange`の動作は、`Bloc`でも`Cubit`でも同じです。\n\n:::\n\n:::caution\n\n`EventHandler`内で発生した未処理の例外(`Unhandled exception`)も`onError`に通知されます。\n\n:::\n\n## Cubit vs Bloc\n\n`Cubit`と`Bloc`クラスの基本について説明しましたが、いつ`Cubit`を使用し、いつ`Bloc`を使用するべきか悩む人がいるかもしれません。\n\n### Cubitの利点\n\n#### シンプルさ\n\n`Cubit`を使用する最大の利点に「シンプルさ」があります。\n`Cubit`を作成する際は、状態と、状態を変化させるために公開する関数を定義するだけです。一方、`Bloc`を作成する場合は、状態、イベント、そして`EventHandler`の実装を定義する必要があります。なので、`Cubit`の方が理解しやすく、コード量も少なくて済みます。\n\n以下の`CounterCubit`と`CounterBloc`の実装を見てみましょう。\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\n`Cubit`の実装の方が簡潔です。イベントを別途定義する必要がなく、関数がイベントのように動作します。さらに、`Cubit`の場合、`emit`を呼ぶだけで、どこからでも簡単に状態変化を発生させられます。\n\n### Blocの利点\n\n#### トレーサビリティ（状態遷移の可視化）\n\n`BLoC`が特に優れているのは、状態がどう変わったか、何がきっかけだったのかを明確に把握できる点です。アプリケーションの機能における重要な状態の場合、イベント駆動型のように、すべてのイベントと状態変化を把握する手法が非常に有効かもしれません。\n\nよくある例としては`AuthenticationState`の管理があります。実装を簡単にするため、`AuthenticationState`を`enum`で表現したとしましょう。\n\n<AuthenticationStateSnippet />\n\nしかし、アプリケーションの状態が`authenticated`から`unauthenticated`に変わる理由は多数考えられます。例えば、ユーザーがログアウトボタンをタップし、アプリケーションからサインアウトすることを要求したかもしれません。他には、ユーザーのアクセストークンが取り消され、強制的にログアウトされた可能性もあります。\n`Bloc`を使用すると、アプリケーションの状態がどのようにしてその状態に至ったのかを明確に追跡できます。\n\n<AuthenticationTransitionSnippet />\n\n上記の`Transition`は、なぜ状態が変化したのかを知るために必要なすべての情報を提供します。もし`Cubit`を使用して`AuthenticationState`を管理していた場合、ログは以下のようになります。\n\n<AuthenticationChangeSnippet />\n\nこのログはユーザーがログアウトしたことを示していますが、なぜログアウトしたのかは分かりません。これはデバッグや、アプリケーションの状態がどのように変化したのかを理解する上で問題になるかもしれません。\n\n#### 高度なイベント変換\n\n`buffer`、`debounceTime`、`throttle`などのリアクティブ演算子を活用する必要のある場合も、\n`Bloc`の方が`Cubit`より優れています。\n\n:::tip\n\n`Stream`の変換については\n[`package:stream_transform`](https://pub.dev/packages/stream_transform)と\n[`package:rxdart`](https://pub.dev/packages/rxdart)を参照してください。\n\n:::\n\n`Bloc`にはイベントの`Sink`があるので、受信したイベントの流れを制御したり変換したりできます。\n\n例えば、リアルタイム検索を実装する際には、レート制限を避けるためや、バックエンドのコストや負荷を抑えるために、リクエストにデバウンス処理を適用したくなるはずです。\n\n`Bloc`を使用する場合、独自の`EventTransformer`を定義することで、`Bloc`が受け取ったイベントの処理を変更できます。\n\n<DebounceEventTransformerSnippet />\n\n上記のように、わずかなコードの追加で、簡単にイベントの受信時にデバウンスできるようになります。\n\n:::tip\n\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)は、よく使われる`EventTransformer`を集めたパッケージです。\n\n:::\n\nどちらを使うべきか迷った場合は、まず`Cubit`から始めましょう。後から必要に応じて`Bloc`へのリファクタリングや拡張が可能です。\n"
  },
  {
    "path": "docs/src/content/docs/ja/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Flutter Blocのコンセプト\ndescription: package:flutter_blocを構成する主要なコンセプトの概要です。\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc)を使用して作業する前に、本セクションを必ずお読みください。\n\n:::\n\n:::note\n\n`flutter_bloc`パッケージによって公開されているすべてのウィジェットは、`Cubit`と`Bloc`の両方に対応しています。\n\n:::\n\n## Bloc Widgets\n\n### BlocBuilder\n\n**BlocBuilder**は、`Bloc`と`builder`関数を必要とするFlutterウィジェットです。\n`BlocBuilder`は、最新の状態(state)に応じてウィジェットを構築(build)し、再描画する役割を持ちます。\n`StreamBuilder`と似ていますが、こちらの方がボイラープレート（冗長的なコード）が少ないため短く書けます。\n\n`builder`は、状態に応じてウィジェットを返す\n[純粋関数](https://en.wikipedia.org/wiki/Pure_function)である必要があります。\n\n状態の変化に応じて画面遷移を行ったり、ダイアログを表示したりといった「何らかの処理」を実行したい場合は`BlocListener`をご覧ください。\n\n`bloc`引数を指定しない場合、`BlocBuilder`は現在の`BuildContext`をもとに、親の`BlocProvider`から提供された`Bloc`を自動的に取得します。\n\n<BlocBuilderSnippet />\n\n親の`BlocProvider`や現在の`BuildContext`から取得できない、単一のウィジェットに限定された`Bloc`を渡したい時にのみ、`bloc`引数を指定してください。\n\n<BlocBuilderExplicitBlocSnippet />\n\nオプションの`buildWhen`引数に関数を渡すと、\n`builder`関数の呼び出されるタイミングが細かく制御できます。\n\n`buildWhen`関数では、前回の`Bloc`の状態と現在の`Bloc`の状態を引数として受け取れ、戻り値として`bool`を返します。\n\n`buildWhen`が`true`を返した場合、\n`builder`は`state`（現在の状態）とともに呼び出され、ウィジェットは再構築(rebuild)されます。`false`の場合は再構築されません。\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector**は`BlocBuilder`に類似したFlutterのウィジェットですが、現在の`Bloc`の状態に基づいた値を選択し、戻り値として返すことで、更新のフィルタリングが行える特徴があります。\n\n選択した値が変わらない限り、再構築は行われません。\n`BlocSelector`が`builder`を再度呼び出すべきかどうかを正確に判定できるように、選択される値は不変(immutable)である必要があります。\n\n`bloc`引数を指定しない場合、`BlocSelector`は現在の`BuildContext`をもとに、親の`BlocProvider`から提供された`Bloc`を自動的に取得します。\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider**は、`BlocProvider.of<T>(context)`を通じて子ウィジェットに`Bloc`を提供(provide)するFlutterウィジェットです。依存性注入(DI)ウィジェットとして使用され、サブツリー内の様々なウィジェットに対し、単一の`Bloc`インスタンスが提供できます。\n\nほとんどの場合、`BlocProvider`を使って新しい`Bloc`を作成し、サブツリーの残りの部分で利用できるようにするべきです。\n`BlocProvider`を用いて`Bloc`を作成した場合、破棄も自動的に行われるためです。\n\n<BlocProviderSnippet />\n\nデフォルトでは、`BlocProvider`は`Bloc`を遅延生成します。つまり、`create`は`BlocProvider.of<BlocA>(context)`を通じて\n`Bloc`が参照された際に初めて実行されます。\n\n`create`を即座に実行させたい場合は、`lazy`を`false`に設定します。\n\n<BlocProviderEagerSnippet />\n\n`BlocProvider`を使って既存の`Bloc`を他のウィジェットツリーに提供することも可能です。これは、既存の`Bloc`を新しいルート(route)で利用したいときに最もよく使われます。この場合、`BlocProvider`自身がその`Bloc`を作成した訳ではないため、自動的に破棄されません。\n\n<BlocProviderValueSnippet />\n\n`ChildA`と`ScreenA`のどちらからでも、以下のようにすることで`BlocA`が取得できます。\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n`MultiBlocProvider`はFlutterのウィジェットで、二つ以上の`BlocProvider`ウィジェットを一つにまとめるためものです。いくつもの`BlocProvider`をネストする必要がなくなるため、可読性が向上します。\n\n<NestedBlocProviderSnippet />\n\n`MultiBlocProvider`を使えば、以下のように書けます。\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\n`MultiBlocProvider`内で定義された`BlocProvider`の`child`は無視されます。\n\n:::\n\n### BlocListener\n\n**BlocListener**は、`BlocWidgetListener`とオプションの`Bloc`を受け取り、\n`Bloc`の状態変化に応じて`listener`を呼び出すFlutterウィジェットです。ナビゲーション、`SnackBar`の表示、`Dialog`の表示など、状態変化ごとに一度だけ処理を実行したい場合に使用します。\n\n`listener`は、`BlocBuilder`の`builder`とは異なり、各状態変化に対して一度だけ呼び出されます（ただし初期状態は**含みません**）。また、`void`関数です。\n\n`bloc`引数を指定しない場合、`BlocListener`は現在の`BuildContext`をもとに、親の`BlocProvider`から提供された`Bloc`を自動的に取得します。\n\n<BlocListenerSnippet />\n\n現在の`BuildContext`では取得できない`Bloc`を渡したい場合にのみ、\n`bloc`引数を指定してください。\n\n<BlocListenerExplicitBlocSnippet />\n\n`listener`関数が呼び出されるタイミングを細かく制御するには、オプションの`listenWhen`を指定します。\n`listenWhen`は前回の`Bloc`の状態と現在の`Bloc`の状態を受け取り、戻り値として`bool`を返します。\n`listenWhen`が`true`を返した場合、\n`listener`は`state`（現在の状態）とともに呼び出されます。`false`を返した場合は呼び出されません。\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n`MultiBlocListener`はFlutterのウィジェットで、二つ以上の`BlocListener`ウィジェットを1つにまとめるためのものです。いくつもの`BlocListener`をネストする必要がなくなるため、可読性が向上します。\n\n<NestedBlocListenerSnippet />\n\n`MultiBlocListener`を使用すると、上記のコードを以下のように簡略化できます。\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\n`MultiBlocListener`内で定義された`BlocListener`の`child`は無視されます。\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer**は、状態変化に応じて処理を行う`listener`と、ウィジェットを再構築する`builder`を引数に持ちます。\n`BlocConsumer`はネストされた`BlocListener`と`BlocBuilder`に相当しますが、ボイラープレート（冗長的なコード）が少ないため短く書けます。\n`BlocConsumer`は`Bloc`の状態変化に対する何らかの処理と、ウィジェットの再構築の両方が必要な場合にのみ使用してください。\n`BlocConsumer`は、必須の`BlocWidgetBuilder`と`BlocWidgetListener`、およびオプションの`bloc`、`BlocBuilderCondition`、`BlocListenerCondition`を受け取ります。\n\n`bloc`引数を指定しない場合、`BlocConsumer`は現在の`BuildContext`をもとに、親の`BlocProvider`から提供された`Bloc`を自動的に取得します。\n\n<BlocConsumerSnippet />\n\n`listener`と`builder`が呼び出されるタイミングを細かく制御するために、オプションの`listenWhen`と`buildWhen`が利用できます。\n`listenWhen`と`buildWhen`は、`Bloc`の状態が変化するたびに呼び出されます。それぞれ前回の状態と現在の状態を受け取り、\n`builder`や`listener`関数を呼び出すかどうかを決定する`bool`を返す必要があります。前回の状態の初期値は、`BlocConsumer`が初期化された時点の`Bloc`の状態になります。\n`listenWhen`引数と`buildWhen`引数はオプションであり、指定されていない場合は常に`true`を返しているとみなされます。\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider**は、`RepositoryProvider.of<T>(context)`を通じて子ウィジェットにリポジトリーを提供(provide)するFlutterウィジェットです。依存性注入(DI)ウィジェットとして使用され、サブツリー内の様々なウィジェットに対し、単一のリポジトリーインスタンスが提供できます。\n`Bloc`の提供には`BlocProvider`を使用し、`RepositoryProvider`はリポジトリーの提供にのみ使用してください。\n\n**BlocProvider**は、`BlocProvider.of<T>(context)`を通じて子ウィジェットに`Bloc`を提供するFlutterウィジェットです。依存性注入(DI)ウィジェットとして使用され、サブツリー内の様々なウィジェットに対し、単一の`Bloc`インスタンスを提供することが出来ます。\n\n<RepositoryProviderSnippet />\n\n`ChildA`からは、以下のようにして`Repository`インスタンスが取得できます。\n\n<RepositoryProviderLookupSnippet />\n\nリポジトリが管理するリソースの破棄が必要な場合は、`dispose`コールバックで処理できます。\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n`MultiRepositoryProvider`はFlutterのウィジェットで、二つ以上の`RepositoryProvider`ウィジェットを1つにまとめるためのものです。いくつもの`RepositoryProvider`をネストする必要がなくなるため、可読性が向上します。\n\n<NestedRepositoryProviderSnippet />\n\n`MultiRepositoryProvider`を使用すると、上記のコードを以下のように簡略化できます。\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\n`MultiRepositoryProvider`内で定義された`RepositoryProvider`の`child`は無視されます。\n\n:::\n\n## BlocProviderの使い方\n\n`BlocProvider`を使って`CounterBloc`を`CounterPage`に提供し、\n`BlocBuilder`で状態変化に応じた処理をする方法を見ていきましょう。\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nこの時点で、プレゼンテーション層とビジネスロジック層の分離に成功しました。\n`CounterPage`ウィジェットは、ユーザーがボタンをタップしたときに何が起こるかについて一切関知しません。このウィジェットは単に、ユーザーが＋ボタンまたは－ボタンを押したことを`CounterBloc`に伝えるだけです。\n\n## RepositoryProviderの使い方\n\n[`flutter_weather`][flutter_weather_link]の例を通して、`RepositoryProvider`の使い方を見ていきましょう。\n\n<WeatherRepositorySnippet />\n\n`main.dart`では、`WeatherApp`ウィジェットを引数として`runApp`を呼び出します。\n\n<WeatherMainSnippet />\n\n`RepositoryProvider`を使って、`WeatherRepository`のインスタンスをウィジェットツリーに注入します。\n\n`Bloc`をインスタンス化する際には、`context.read`でリポジトリーのインスタンスにアクセスし、コンストラクターを通じて`Bloc`にリポジトリーを注入できます。\n\n<WeatherAppSnippet />\n\n:::tip\n\n二つ以上のリポジトリーがある場合は、`MultiRepositoryProvider`を使うことで、サブツリーにまとめてリポジトリーインスタンスが提供できます。\n\n:::\n\n:::note\n\n`RepositoryProvider`がアンマウントされる際にリソースを解放するには、`dispose`コールバックを使用してください。\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## 拡張関数(extension)\n\nDart\n2.7で導入された[拡張関数](https://dart.dev/guides/language/extension-methods)は、既存のライブラリーに機能を追加する方法です。このセクションでは、`package:flutter_bloc`に含まれる拡張関数と、その使い方について見ていきます。\n\n`flutter_bloc`は[package:provider](https://pub.dev/packages/provider)に依存しており、これにより[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)の利用が簡潔になっています。\n\n内部的には、`package:flutter_bloc`は`package:provider`を使って、\n`BlocProvider`、`MultiBlocProvider`、`RepositoryProvider`、`MultiRepositoryProvider`の各ウィジェットを実装しています。また、`package:flutter_bloc`は`package:provider`の\n`ReadContext`、`WatchContext`、`SelectContext`拡張を`export`しています。\n\n:::note\n\n`package:provider`の詳細は[こちら](https://pub.dev/packages/provider)をご覧ください。\n\n:::\n\n### context.read\n\n`context.read<T>()`は、呼び出された地点から最も近い位置で提供された型`T`のインスタンスを返します。機能的には`BlocProvider.of<T>(context)`と同等です。\n`context.read`は、`onPressed`コールバック内でイベントを追加するために\n`Bloc`インスタンスを取得する用途で最もよく使われます。\n\n:::note\n\n`context.read<T>()`は`T`を`listen`しません。そのため、型`T`の提供されたオブジェクトが変更されても、\n`context.read`はウィジェットの再構築を引き起こしません。\n\n:::\n\n#### 使い方\n\n✅ コールバック内でイベントを追加するときに`context.read`を使用してください。\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ `build`メソッド内で状態を取得するために`context.read`を使用しないでください。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n上記の使い方はエラーが発生しやすくなります。 `Bloc`の状態が変化しても\n`Text`ウィジェットが再構築されないためです。\n\n:::caution\n\n状態の変化に応じて再構築するには、代わりに`BlocBuilder`または`context.watch`を使用してください。\n\n:::\n\n### context.watch\n\n`context.read<T>()`と同様に、\n`context.watch<T>()`は呼び出された地点から最も近い位置で提供された型`T`のインスタンスを返しますが、そのインスタンスの変更も監視します。機能的には\n`BlocProvider.of<T>(context, listen: true)`と同等です。\n\n提供された型`T`の`Object`が変更されると、\n`context.watch`は再構築を引き起こします。\n\n:::caution\n\n`context.watch`は`StatelessWidget`または`State`クラスの`build`メソッド内でのみ使用できます。\n\n:::\n\n#### 使い方\n\n✅ 再構築の範囲を明示的に限定するには、\n`context.watch`の代わりに`BlocBuilder`を使用してください。\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // stateが変更されるたびに、Textのみが再構築されます。\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nあるいは、`Builder`を使って再構築する範囲を限定することもできます。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // stateが変更されるたびに、Textのみが再構築されます。\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ `Builder`と`context.watch`を`MultiBlocBuilder`として使用してください。\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // BlocA、BlocB、BlocC のstateに依存するWidgetを返す\n  }\n);\n```\n\n❌\n`build`メソッド内の親ウィジェットが`state`に依存していない場合、`context.watch`は使用しないでください。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // 実際にはTextウィジェットでしか使われていないのに、\n  // stateが変更されるたびにMaterialAppが再構築されてしまいます。\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build`メソッドの先頭で`context.watch`を使用すると、\n`Bloc`の状態が変更されるたびに、そのウィジェット全体が再構築されてしまいます。\n\n:::\n\n### context.select\n\n`context.watch<T>()`と同様に、`context.select<T, R>(R function(T value))`は呼び出された地点から最も近い位置で提供された型`T`のインスタンスを返し、`T`の変更を監視します。\n`context.watch`とは異なり、`context.select`は`Bloc`の状態内の限られた部分の変更のみを監視することが可能です。\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\n上記のコードは、`ProfileBloc`の`state`の`name`プロパティーが変更されたときにのみウィジェットを再構築します。\n\n#### 使い方\n\n✅ 再構築の範囲を明示的に限定するために、`context.select`の代わりに`BlocSelector`を使用してください。\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // state.nameが変更されるたびに、Textのみが再構築されます。\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nあるいは、`Builder`を使って再構築の範囲を限定することも出来ます。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // state.nameが変更されるたびに、Textのみが再構築されます。\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ `build`メソッド内の親ウィジェットが`state`に依存していない場合、\n`context.select`を使用することは避けてください。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // nameはTextウィジェットでしか使われていないのに、\n  // state.nameが変更されるたびにMaterialAppが再構築されてしまいます。\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build`メソッドの先頭で`context.select`を使用すると、\n`Bloc`の状態内の選択した値が変更されるたびに、そのウィジェット全体が再構築されてしまいます。\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ja/getting-started.mdx",
    "content": "---\ntitle: はじめに\ndescription: Blocで開発を始めるためのすべてが揃っています。\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## パッケージ\n\nblocのエコシステムは以下のパッケージで構成されています。\n\n| Package                                                                                    | Description                  | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDartコンポーネント    | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | コアDart API                 | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | イベントのトランスフォーマー | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | カスタムLinter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | APIのテスト                  | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | コマンドラインツール         | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutterウィジェット          | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | キャッシュ/永続化のサポート  | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo機能のサポート      | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## インストール\n\n<InstallationTabs />\n\n:::note\n\nBlocを使って開発を始めるにはまず[Dart SDK](https://dart.dev/get-dart)\nをインストールする必要があります。\n\n:::\n\n## importする\n\nblocが正常にインストールされたので、`main.dart`を作成し、それぞれの`bloc`パッケージを`import`できます。\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/ja/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc 状態管理ライブラリ\ndescription: 状態管理ライブラリBlocの公式ドキュメントです。Dart、Flutter、AngularDartをサポートしています。サンプルとチュートリアルが含まれています。\nbanner:\n  content: |\n    ✨ <a href=\"https://shop.bloclibrary.dev\">Blocショップ</a>にアクセス ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: 思い通りに動くDartの状態管理用ライブラリ\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: はじめる\n      link: /ja/getting-started/\n      variant: primary\n      icon: rocket\n    - text: GitHubで詳細を見る\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"💖のあるスポンサーの方々\"\n\tbecomeASponsor=\"スポンサーになる\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"はじめる\" icon=\"rocket\">\n\t```sh\n\t# プロジェクトにblocを追加します。\n\tdart pub add bloc\n\t```\n[はじめに](/ja/getting-started)では、数分でBlocを使い始められるようになるための方法が順番に解説されています。\n\n</SplitCard>\n\n<Card title=\"ガイド付きツアーに参加\" icon=\"star\">\n\t[公式チュートリアル](/ja/tutorials/flutter-counter)でベストプラクティスを学び、\n\tBlocを利用したさまざまなアプリを作成してみましょう。\n</Card>\n\n<Card title=\"Blocで構築\" icon=\"laptop\">\n\tカウンター、タイマー、無限にスクロール可能なリスト、天気、ToDoなど、完全にテストされた高品質な\n\t[サンプルアプリ](https://github.com/felangel/bloc/tree/master/examples)\n\tをご覧ください！\n</Card>\n\n<ListCard title=\"学ぶ\" icon=\"open-book\">\n\n    - [なぜBloc？](/ja/why-bloc)\n    - [基本的なコンセプト](/ja/bloc-concepts)\n    - [アーキテクチャー](/ja/architecture)\n    - [テスト](/ja/testing)\n    - [命名規則](/ja/naming-conventions)\n    - [よくある質問](/ja/faqs)\n\n</ListCard>\n\n  <ListCard title=\"拡張機能\" icon=\"puzzle\">\n    - [VSCode拡張機能](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ拡張機能](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim拡張機能](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason拡張機能](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [カスタムテンプレート](https://brickhub.dev/search?q=bloc)\n    - [開発者ツール](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Discordに参加する\" />\n"
  },
  {
    "path": "docs/src/content/docs/ja/why-bloc.mdx",
    "content": "---\ntitle: なぜBloc？\ndescription: Blocが優れた状態管理手法である理由を解説しています。\nsidebar:\n  order: 1\n---\n\nBlocはプレゼンテーション層からビジネスロジックを簡単に分離できるので、コードが*高速で*、_テストしやすく_、\n*再利用可能に*なります。\n\n本番環境向けのアプリケーション開発において、状態管理は非常に重要です。\n\n開発者としては\n\n- アプリケーションの状態を常に把握したい\n- 全てのケースにおいて正しく動作しているかを簡単にテストしたい\n- アプリケーション内でユーザーが行った操作を全て記録し、データに基づいた意思決定を可能にしたい\n- 特定のアプリケーションのコンポーネントを再利用することで、ほかのアプリケーションでも効率よく開発をしたい\n- 大勢が携わる開発でも同じパターンとルールに従って、単一のコードベースでもシームレスに作業したい\n- サクサク動くリアクティブなアプリケーションを開発したい\n\nですよね。\n\nBlocはそんなニーズに応えるために開発されました。\n\n世の中には多数の状態管理手法があり、どれを使うか判断するのは大変です。\n\nBlocは以下の三つのコアバリューを元に作成されました。\n\n- シンプル\n  - 簡単に使え、開発者の技術レベルを問わず使ってもらえる\n- パワフル\n  - 複雑なアプリケーションを小さなコンポーネントに分けることで、高品質なアプリケーションを作ることが出来る\n- テストのしやすさ\n  - 品質に自信を持って開発を進められるように、アプリケーションを構築する全ての要素を簡単にテストできるようにする\n\nまとめると、Blocは状態変化のタイミングを制御し、アプリケーション全体で統一された方法で状態を管理することで、状態の変化を予測可能にします。\n"
  },
  {
    "path": "docs/src/content/docs/ko/architecture.mdx",
    "content": "---\ntitle: 아키텍쳐\ndescription: Bloc을 사용할 때 권장되는 아키텍쳐(디자인) 패턴에 대한 개요입니다.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\nBloc 라이브러리를 사용하면 애플리케이션을 세 가지 레이어로 분리할 수 있습니다:\n\n- Presentation\n- Business Logic\n- Data\n  - Repository\n  - Data Provider\n\n이제 가장 아래 수준(사용자 인터페이스로부터 가장 멀리 떨어진)의 레이어 부터\n시작하여 Presentation 레이어까지 작업해 보겠습니다.\n\n## Data Layer\n\nData 레이어는 하나 이상의 소스에서 데이터를 검색/조작하는 책임을 맡습니다.\n\n따라서 Data 레이어는 두 부분으로 나뉠 수 있습니다:\n\n- Repository\n- Data Provider\n\n이 레이어는 애플리케이션의 가장 아래 수준이며 데이터베이스, 네트워크 요청 및\n기타 비동기 데이터 소스와 상호 작용합니다.\n\n### Data Provider\n\nData Provider는 원시(raw) 데이터를 제공하는 것입니다. Data Provider는 일반적이고\n가변적이어야 합니다.\n\n일반적으로 Data Provider는 간단한 API를 노출하여\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 작업을\n수행합니다. 따라서 Data 레이어의 일부로 `createData`, `readData`, `updateData`,\n그리고 `deleteData` 메서드가 있을 수 있습니다.\n\n<DataProviderSnippet />\n\n### Repository\n\nRepository는 Bloc 계층이 통신하는 하나 이상의 Data Provider를 감싸는\n래퍼(Wrapper) 입니다.\n\n<RepositorySnippet />\n\n보이시는 바와 같이, Repository 계층은 여러 Data Provider와 상호 작용하고,\n데이터에 대한 변환을 수행한 후 그 결과를 Business Logic 레이어로 전달할 수\n있습니다.\n\n## Business Logic Layer\n\nBusiness Logic 레이어는 Presentation 레이어의 입력에 대해 새로운 상태로 응답하는\n책임을 갖습니다.\n\nBusiness Logic 레이어를 사용자 인터페이스(Presentation 레이어)와 Data 레이어\n사이의 다리라고 생각해도 좋습니다. Business Logic 레이어는 Presentation\n레이어로부터 events/actions에 대한 알림을 받은 다음, Repository와 통신하여\nPresentation 레이어가 사용할 새 state를 구축합니다.\n\n<BusinessLogicComponentSnippet />\n\n### Bloc간 통신\n\nBloc은 스트림(Stream)을 노출하기 때문에 다른 bloc을 수신하는 bloc을 만들고 싶은\n경우가 있을 수 있겠습니다만, **절대로** 이렇게 하면 안 됩니다. 아래에 소개되는\n코드보다 더 나은 대안이 있습니다.\n\n<BlocTightCouplingSnippet />\n\n위에 소개된 코드에 오류는 없지만(심지어 스트림 구독 해체 로직도 있지만) 더 큰\n문제가 존재합니다: 두 bloc간 종속성을 생성하는 문제\n\n일반적으로, 동일한 아키텍쳐 레이어이 있는 두 엔티티(Entity)간 남매\n종속성(Sibling dependencies)은 유지보수하기 어려운 긴밀한 결합을 생성하기 때문에\n어떤 대가를 치르더라도 반드시 피해야 합니다. Bloc은 Business Logic 아키텍쳐\n레이어에 존재하기 때문에 어떠한 bloc도 다른 bloc에 대해 알면 안 됩니다.\n\n![Application Architecture Layers](~/assets/architecture/architecture.png)\n\nBloc은 events와 종속성 주입된 Repository(즉, 생성자에서 bloc에 주입된\nrepository)를 통해서만 정보를 수신해야 합니다.\n\n한 bloc이 다른 bloc에 응답해야 하는 상황에 처한 경우 두 가지 다른 대안이\n있습니다. 연결 문제를 한 레이어 위로(Presentation 레이어로) 올리거나, 한 레이어\n아래로(Domain 레이어로) 내릴 수 있습니다.\n\n#### Presentation를 통한 bloc 연결\n\n`BlocListener`를 사용하여 한 bloc(FirstBloc)을 수신하고, 이 bloc이 변경될 때\n마다 다른 bloc(SecondBloc)에 event를 추가할 수 있습니다.\n\n<BlocLooseCouplingPresentationSnippet />\n\n위의 코드는 `SecondBloc`이 `FirstBloc`에 대해 알 필요가 없도록 하여 느슨한\n결합을 유도합니다. [flutter_weather](/ko/tutorials/flutter-weather)\n애플리케이션은\n[이 기법을 사용하여](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\n수신된 날씨 정보에 따라 앱 테마를 변경합니다.\n\n어떤 상황에서는 Presentation 레이어에서 두 bloc을 연결하고 싶지 않을 수\n있습니다. 이런 경우에는 두 bloc이 동일한 데이터 소스를 공유하고 데이터가 변경될\n때 마다 업데이트하는 것이 합리적일 수 있습니다.\n\n#### Domain을 통한 bloc 연결\n\n두 bloc은 Repository에서 Stream을 수신하고 Repository 데이터가 변경될 때 마다\n서로 독립적으로 상태를 업데이트 할 수 있습니다. Reactive repository를 사용하여\nstate를 동기화 하는 것은 대규모 기업체 애플리케이션에서 흔히 볼 수 있습니다.\n\n우선, 데이터 `Stream`을 제공하는 Repository를 만들거나 사용합니다. 예를 들어,\n아래에 소개되는 Repository는 몇 가지 앱 아이디어에 대한 무한히 반복되는 Stream을\n노출합니다:\n\n<AppIdeasRepositorySnippet />\n\n새로운 앱 아이디어에 반응해야 하는 각 bloc에 동일한 Repository를 종속성 주입할\n수 있습니다. 아래의 코드는 위의 Repository에서 들어오는 각 앱 아이디어에 대한\nstate를 yield하는 `AppIdeaRankingBloc`입니다:\n\n<AppIdeaRankingBlocSnippet />\n\nBloc에서 Stream을 사용하는 방법에 대한 자세한 내용은\n[Streams 그리고 Concurrency에서 Bloc을 사용하는 방법](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency)을\n참조하세요.\n\n## Presentation Layer\n\nPresentation 레이어는 하나 이상의 bloc state를 기반하여 렌더링하는 방법을\n알아내야 하는 책임을 갖습니다.\n\n대부분의 애플리케이션 흐름은 애플리케이션이 사용자에게 표시할 일부 데이터를\n가져오도록 촉발하는 `AppStart` event로부터 시작합니다.\n\n이 시나리오에서 Presentation 레이어는 `AppStart` event를 추가합니다.\n\n또한, Presentation 레이어는 bloc 레이어의 state를 기반으로 화면에 렌더링할\n내용을 파악해야 합니다.\n\n<PresentationComponentSnippet />\n\n지금까지 몇 가지 코드 스니펫을 살펴봤지만, 이 모든것은 상당히 높은\n수준이었습니다. 튜토리얼 섹션에서는 여러 가지 예제 앱을 빌드하면서 이 모든 것을\n종합해 보겠습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/bloc-concepts.mdx",
    "content": "---\ntitle: 핵심 컨셉\ndescription: package:bloc의 핵심 개념에 대한 개요입니다.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\n[`package:bloc`](https://pub.dev/packages/bloc)으로 작업하기 전에 다음 섹션을\n주의 깊게 읽어주세요.\n\n:::\n\nBloc 패키지 사용 방법을 이해하는 데 중요한 몇 가지 핵심 개념이 있습니다.\n\n다음 섹션에서는 각 항목에 대해 자세하게 살펴보고 카운터 앱에 적용하는 방법을\n살펴보겠습니다.\n\n## Streams\n\n:::note\n\n`Streams`에 대한 자세한 내용은\n[Dart 문서](https://dart.dev/tutorials/language/streams)를 참조하세요.\n\n:::\n\nStream은 연속적인 비동기 데이터입니다.\n\nBloc 라이브러리를 사용하려면 `Streams`과 그 작동 방식에 대한 기본적인 이해가\n필요합니다.\n\n만약 당신이 `Streams`이 익숙하지 않다면, 물이 흐르는 파이프를 생각하면 됩니다.\n파이프는 `Streams`이고 물은 비동기 데이터 입니다.\n\n`async*` (비동기 생성기) 함수를 작성하여 Dart에서 `Stream`을 생성할 수 있습니다.\n\n<CountStreamSnippet />\n\n함수를 `async*`로 표시하면 `yield` 키워드를 사용하여 데이터의 `Stream`을 반환할\n수 있습니다. 위 예시에서는 `max` 정수 파라미터까지의 정수 `Stream`을 반환하고\n있습니다.\n\n`async*` 함수에서 `yield` 할 때 마다 해당 데이터를 `Stream`을 통해 푸쉬합니다.\n\n위의 `Stream`을 여러 가지 방법으로 사용할 수 있습니다. 만약 정수로 이루어진\n`Stream`의 합계를 반환하는 함수를 작성하고 싶다면 다음과 같이 작성할 수\n있습니다:\n\n<SumStreamSnippet />\n\n위의 함수를 `async`로 작성하면 `await` 키워드를 사용하여 정수의 `Future`를\n반환할 수 있습니다. 이 예제에서는 Stream의 각 값을 기다렸다가 Stream에 있는 모든\n정수의 합을 반환합니다.\n\n위 모든 코드를 다음과 같이 사용할 수 있습니다:\n\n<StreamsMainSnippet />\n\n이제 Dart에서 `Streams`이 어떻게 작동하는지 기본적인 이해를 했으니, Bloc\n패키지의 핵심 구성 요소: `Cubit`에 대해 알아볼 준비가 되었습니다.\n\n## Cubit\n\n`Cubit`은 `BlocBase`를 extends한 클래스로, 모든 유형의 state를 관리하도록 확장할\n수 있습니다.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\n`Cubit`은 state의 변경을 촉발하기 위해 호출할 수 있는 함수를 외부로 노출시킬 수\n있습니다.\n\nState는 `Cubit`의 출력이며 애플리케이션 state의 일부를 나타냅니다. UI 컴포넌트는\nstate에 대한 notify를 받고, 현재 state에 따라 일부를 다시 그릴 수 있습니다.\n\n:::note\n\n`Cubit`의 기원에 대한 자세한 내용은\n[해당 Issue](https://github.com/felangel/cubit/issues/69)에서 확인하세요.\n\n:::\n\n### Cubit 만들기\n\n다음과 같은 `CounterCubit`을 만들 수 있습니다:\n\n<CounterCubitSnippet />\n\n`Cubit`을 생성할 때, `Cubit`이 관리할 상태의 타입을 정의해야 합니다. 위의\n`CounterCubit`의 경우 state 타입은 `int`로 표현할 수 있지만, 더 복잡한 경우에는\nPrimitive type 대신 `class`를 사용해야 할 수도 있습니다.\n\n`Cubit`을 생성할 때 두 번째로 해야 할 일은 초기 상태를 지정하는 것입니다. 초기\n상태의 값으로 `super`를 호출하여 이를 수행할 수 있습니다. 위의 예시 코드는\n내부적으로 초기 상태를 `0`으로 설정하고 있지만, 다음과 같이 외부의 값을 허용하여\n`Cubit`이 더 유연하게 작동하도록 할 수도 있습니다.\n\n<CounterCubitInitialStateSnippet />\n\n이렇게 하면 다음과 같이 다양한 초기 상태를 가진 `CounterCubit` 인스턴스를 만들\n수 있습니다.\n\n<CounterCubitInstantiationSnippet />\n\n### Cubit의 state변화\n\n각 `Cubit`은 `emit`을 통해 새로운 state를 출력할 수 있습니다.\n\n<CounterCubitIncrementSnippet />\n\n위의 예시 코드에서 `CounterCubit`은 외부에서 호출하여 `CounterCubit`의 state를\n증가시킬 수 있는 `increment` 라는 public 메서드를 노출하고 있습니다.\n`increment`가 호출되면 `state` getter를 통해 `Cubit`의 현재 state에 접근하고,\n현재 상태에 1을 더하여 새로운 state를 `emit`할 수 있습니다.\n\n:::caution\n\n`emit` 메서드는 protected 이므로 `Cubit` 내부에서만 사용해야 합니다.\n\n:::\n\n### Cubit 사용하기\n\n이제 우리가 구현한 `CounterCubit`을 실제로 사용할 수 있습니다!\n\n#### 기본 사용법\n\n<CounterCubitBasicUsageSnippet />\n\n위의 예시 코드에서는 먼저 `CounterCubit`의 인스턴스를 생성합니다. 그런 다음 초기\nstate인 Cubit의 현재 state를 출력합니다 (아직 새로운 state가 emit되지\n않았으므로). 다음으로 `increment` 함수를 호출하여 state의 변경을 촉발합니다.\n마지막으로 `0`에서 `1`로 바뀐 `Cubit`의 state를 다시 출력하고 `Cubit`의\n`close`를 호출하여 내부 state stream을 닫습니다.\n\n#### Stream 사용법\n\n`Cubit`은 실시간 state 업데이트를 받을 수 있는 `Stream`을 노출합니다:\n\n<CounterCubitStreamUsageSnippet />\n\n위의 예시 코드에서는 `CounterCubit`을 구독하고 각 state 변경 시마다 print를\n호출하고 있습니다. 그런 다음 새로운 state를 출력하는 `increment` 함수를 호출하고\n있습니다. 마지막으로, 더 이상 업데이트를 받고 싶지 않을 때 `subscription`의\n`cancel`을 호출하고 `Cubit`을 닫습니다.\n\n:::note\n\n해당 예제에서는 구독이 즉시 취소되지 않도록\n`await Future.delayed(Duration.zero)`을 추가했습니다.\n\n:::\n\n:::caution\n\n`Cubit`에서 `listen`을 호출할 때는 후속 상태 변경 사항만을 수신합니다.\n\n:::\n\n### Cubit 관찰하기\n\n`Cubit`이 새로운 state를 emit하면 `Change`가 발생합니다. `onChange`를\noverride하여 주어진 `Cubit`에 대한 모든 변경 사항을 관찰할 수 있습니다.\n\n<CounterCubitOnChangeSnippet />\n\n그런 다음 `Cubit`과 상호 작용하고, 콘솔로 출력되는 모든 변경 사항을\n관찰해봅시다.\n\n<CounterCubitOnChangeUsageSnippet />\n\n위 예시는 다음과 같이 출력됩니다:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change`는 `Cubit`의 state가 업데이트되기 직전에 발생합니다. `Change`는\n`currentState`와 `nextState`로 구성됩니다.\n\n:::\n\n#### BlocObserver\n\nBloc 라이브러리를 사용하면 한 곳에서 모든 `Change`에 접근할 수 있다는 장점이\n있습니다. 이 애플리케이션은 하나의 `Cubit`만 있지만, 대규모 애플리케이션에서는\n애플리케이션 state의 여러 부분을 관리하는 많은 `Cubit`을 사용하는 것이\n일반적입니다.\n\n모든 `Change`에 대응하여 무언가를 할 수 있도록 하려면 자체적으로\n`BlocObserver`를 만들면 됩니다.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\n`BlocObserver`를 extends 하고 `onChange`메서드를 override 하기만 하면 됩니다.\n\n:::\n\n`SimpleBlocObserver`를 사용하려면 `main`함수를 조금만 수정하면 됩니다:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\n위 예시 코드에 대한 출력입니다:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\n내부의 `onChange` override가 먼저 호출되어 `BlocObserver`의 `onChange`를 알리는\n`super.onChange`를 호출합니다.\n\n:::\n\n:::tip\n\n`BlocObserver`에서는 `Change` 그 자체 외에도 `Cubit` 인스턴스에 접근할 수\n있습니다.\n\n:::\n\n### Cubit의 에러 처리\n\n모든 `Cubit`에는 에러가 발생했음을 나타내는데 사용할 수 있는 `addError` 메서드가\n있습니다.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n특정 `Cubit`에 대한 모든 에러를 처리하기 위해 `onError`를 `Cubit` 내에서\noverride할 수 있습니다.\n\n:::\n\n`BlocObserver`에서 `onError`를 override하여 보고된 모든 에러를 전역적으로 처리할\n수도 있습니다.\n\n<SimpleBlocObserverOnErrorSnippet />\n\n동일한 프로그램을 다시 실행하면 다음과 같은 출력을 볼 수 있습니다:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n`Bloc`은 함수가 아닌 `event`에 의존하여 `state` 변경을 촉발하는 고급 클래스\n입니다. `Bloc`은 또한 `BlocBase`를 extends하여 `Cubit`과 유사한 공용 API를 갖고\n있습니다. 그러나, `Bloc`에서 `함수`를 호출하여 새로운 `state`를 직접 emit하는\n대신, `Bloc`은 `event`를 수신하고 수신된 `event`를 나가는 `state`로 변환합니다.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Bloc 만들기\n\n`Bloc`을 생성하는 것은 `Cubit`를 생성하는 것과 비슷하지만, 관리할 state를\n정의하는 것 외에 `Bloc`이 처리할 event도 정의해야 한다는 점이 다릅니다.\n\nEvent는 Bloc에 대한 입력입니다. 일반적으로 버튼을 누름과 같은 사용자 상호\n작용이나, 페이지 로드와 같은 생명 주기 이벤트에 대한 응답으로 추가됩니다.\n\n<CounterBlocSnippet />\n\n`CounterCubit`을 생성할 때와 마찬가지로 `super`를 통해 부모 클래스에 전달하여\n초기 state를 지정해야 합니다.\n\n### Bloc의 state변화\n\n`Bloc`은 `Cubit`의 함수가 아닌 `on<Event>` API를 통해 이벤트 핸들러를 등록해야\n합니다. 이벤트 핸들러는 들어오는 모든 event를 0개 이상의 나가는 state로 변환하는\n역할을 수행합니다.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\n`EventHandler`는 추가된 event뿐 만 아니라 수신되는 event에 대한 응답으로 0개\n이상의 상태를 emit하는데 사용할 수 있는 `Emitter`에 접근할 수 있습니다.\n\n:::\n\n그런 다음 `EventHandler`를 업데이트하여 `CounterIncrementPressed` event를 처리할\n수 있습니다:\n\n<CounterBlocIncrementSnippet />\n\n위의 예시 코드에서는 모든 `CounterIncrementPressed` event를 관리하기 위해\n`EventHandler`를 등록했습니다. 들어오는 각 `CounterIncrementPressed` event에\n대해 `state` getter와 `emit(state + 1)`를 통해 bloc의 현재 상태에 접근할 수\n있습니다.\n\n:::note\n\n`Bloc` 클래스는 `BlocBase`를 extends 하기 떄문에 `Cubit`와 마찬가지로 `state`\ngetter를 통해 언제든지 bloc의 현재 state에 접근할 수 있습니다.\n\n:::\n\n:::caution\n\nBloc은 새로운 state를 직접 `emit`해서는 안 됩니다. 대신 모든 state 변경은\n`EventHandler` 내에서 들어오는 event에 대한 응답으로 출력되어야 합니다.\n\n:::\n\n:::caution\n\nBloc과 Cubit 모두 복제된 상태를 무시합니다. `state == nextState`에서\n`State nextState`를 emit하면 state 변경이 발생하지 않습니다.\n\n:::\n\n### Bloc 사용하기\n\n이 시점에서 `CounterBloc`의 인스턴스를 생성하여 사용할 수 있습니다!\n\n#### 기본 사용법\n\n<CounterBlocUsageSnippet />\n\n위의 예시 코드에서는 먼저 `CounterBloc`의 인스턴스를 생성합니다. 그런 다음 초기\nstate인 `Bloc`의 현재 state를 출력합니다 (아직 새로운 state가 emit되지\n않았으므로). 다음으로 state 변경을 촉발하기 위해 `CounterIncrementPressed`\nevent를 추가합니다. 마지막으로 `0`에서 `1`로 변경된 `Bloc`의 state를 다시\n출력하고 `Bloc`의 `close`룰 호출하여 내부 state stream을 닫습니다.\n\n:::note\n\n다음 event-loop를 기다리기 위해 `await Future.delayed(Duration.zero)`가\n추가됩니다 (`EventHandler`가 event를 처리할 수 있도록 허용).\n\n:::\n\n#### Stream 사용법\n\n`Cubit`과 마찬가지로 `Bloc`은 `Stream`의 특수한 유형으로, `Bloc`을 구독하여\nstate를 실시간으로 업데이트 할 수도 있습니다:\n\n<CounterBlocStreamUsageSnippet />\n\n위의 예시 코드에서는 `CounterBloc`을 구독하고 각 state 변경 시마다 print를\n호출하고 있습니다. 그런 다음 `on<CounterIncrementPressed>` `EvnetHandler`를\n촉발하고 새 state를 emit하는 `CounterIncrementPressed` event를 추가하고\n있습니다. 마지막으로, 더 이상 업데이트를 받지 않으려면 `subscription`의\n`cancel`을 호출하고 `Bloc`을 닫습니다.\n\n:::note\n\n해당 예제에서는 구독이 즉시 취소되지 않도록\n`await Future.delayed(Duration.zero)`을 추가했습니다.\n\n:::\n\n### Bloc 관찰하기\n\n`Bloc`은 `BlocBase`를 extends 하기 때문에 `onChange`를 사용하여 `Bloc`의 모든\nstate 변화를 관찰할 수 있습니다.\n\n<CounterBlocOnChangeSnippet />\n\n그런 다음 `main.dart`를 다음과 같이 업데이트 합니다:\n\n<CounterBlocOnChangeUsageSnippet />\n\n위 예시 코드에 대한 출력입니다:\n\n<CounterBlocOnChangeOutputSnippet />\n\n`Bloc`과 `Cubit`의 주요 차별화 요소 중 하나는 `Bloc`이 event 기반이기 때문에\nstate 변화를 유발한 원인에 대한 정보도 캡처할 수 있다는 점입니다.\n\n이 작업은 `onTransition`을 override하여 수행할 수 있습니다.\n\n한 state에서 다른 state로 변경되는 것을 `Transition`이라고 합니다.\n`Transition`은 현재 state, event, 다음 state로 구성됩니다.\n\n<CounterBlocOnTransitionSnippet />\n\n그런 다음 이전과 동일한 `main.dart` 예시 코드를 다시 실행하면 다음과 같은 출력이\n표시됩니다.\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition`은 `onChange` 이전에 호출되며 `currentState`에서 `nextState`로\n변경을 촉발한 event를 포함합니다.\n\n:::\n\n#### BlocObserver\n\n이전과 마찬가지로 커스텀 `BlocObserver`에서 `onTransition`을 override하여 단일\n위치에서 발생하는 모든 Transition을 관찰할 수 있습니다.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\n이전과 마찬가지로 `SimpleBlocObserver`를 초기화 할 수 있습니다:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\n이제 위의 예시 코드를 실행하면 다음과 같은 출력을 얻을 수 있습니다:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` 이 먼저 호출되고(global보다 local이 먼저) 그 다음에 `onChange`가\n호출됩니다.\n\n:::\n\n`Bloc` 인스턴스의 또 다른 독특한 특징은 `Bloc`에 새 event가 추가될 때마다\n호출되는 `onEvnet`를 override할 수 있다는 점입니다. `onChange` 및\n`onTransition`과 마찬가지로 `onEvent`는 전역뿐만 아니라 로컬에서도 override할 수\n있습니다.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\n이전과 동일한 `main.dart`를 실행하면 다음과 같은 출력을 볼 수 있습니다:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent`는 event가 추가되는 즉시 호출됩니다. local `onEvent`는 `BlocObserver`의\nglobal `onEvent`보다 먼저 호출됩니다.\n\n:::\n\n### Bloc의 에러 처리\n\n`Cubit`과 마찬가지로 각 `Bloc`에는 `addError`와 `onError` 메서드가 있습니다.\n`Bloc` 내부 어디에서나 `addError`를 호출하여 에러가 발생했음을 알릴 수 있습니다.\n그런 다음 `Cubit`과 마찬가지로 `onError`를 override 하여 모든 에러에 대응할 수\n있습니다.\n\n<CounterBlocOnErrorSnippet />\n\n이전과 동일한 `main.dart`를 다시 실행하면 에러가 보고될 때 어떤 모습인지 확인할\n수 있습니다:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nlocal `onError`가 먼저 호출된 후 `BlocObserver`의 global `onError`가 호출됩니다.\n\n:::\n\n:::note\n\n`onError`와 `onChange`는 `Bloc`과 `Cubit` 인스턴스 모두에 대해 똑같은 방식으로\n작동합니다.\n\n:::\n\n:::caution\n\n`EventHandler` 내에서 발생하는 처리되지 않은 exception도 `onError`에서\n보고됩니다.\n\n:::\n\n## Cubit vs. Bloc\n\n이제 `Cubit`과 `Bloc` 클래스의 기본 사항을 살펴봤으니 언제 `Cubit`을 사용해야\n하는지, 언제 `Bloc`을 사용해야 하는지 궁금하실 것입니다.\n\n### Cubit 장점\n\n#### 단순성\n\n`Cubit` 사용의 가장 큰 장점 중 하나는 단순성입니다. `Cubit`을 생성할 때는\nstate와 state를 변경하기 위해 노출할 함수만 정의하면 됩니다. 반면에 `Bloc`을\n생성할 때는 state, event, `EventHandler` 구현을 정의해야 합니다. 따라서\n`Cubit`을 더 쉽게 이해할 수 있고 관련된 코드도 더 적습니다.\n\n이제 두 가지 카운터 구현을 살펴보겠습니다:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\n`Cubit` 구현은 더 간결하며 event를 별도로 정의하는 대신 함수가 event처럼\n작동합니다. 또한, `Cubit`을 사용할 때는 어디서든 `emit`을 호출하여 state 변경을\n촉발할 수 있습니다.\n\n### Bloc 장점\n\n#### 추적가능성\n\n`Bloc` 사용의 가장 큰 장점 중 하나는 state 변경의 순서와 그 변경을 유발한 원인을\n정확히 파악할 수 있다는 점입니다. 애플리케이션의 기능에 중요한 state의 경우\nstate 변경 외에도 모든 event를 캡처하기 위해, 보다 event 중심적인 접근 방식을\n사용하는 것이 매우 유용할 수 있습니다.\n\n일반적인 사용 사례는 `AuthenticationState`를 관리하는 것입니다. 간단히 설명하기\n위해 `enum`을 통해 `AuthenticationState`를 표현할 수 있다고 가정해 보겠습니다:\n\n<AuthenticationStateSnippet />\n\n애플리케이션의 state가 `authenticated`에서 `unauthenticated`로 변경되는 이유는\n여러 가지가 있을 수 있습니다. 예로 들어 사용자가 로그아웃 버튼을 탭하고\n애플리케이션에서 로그아웃을 요청했을 수 있습니다. 반면에 사용자의 access token이\n해지되어 강제로 로그아웃되었을 수도 있습니다. `Bloc`을 사용하면 애플리케이션\nstate가 특정 state에 도달한 경로를 명확하게 추적할 수 있습니다.\n\n<AuthenticationTransitionSnippet />\n\n위의 `Transition`은 state가 변경된 이유를 이해하는데 필요한 모든 정보를\n제공합니다. 만약 `Cubit`을 사용해 `AuthenticationState`를 관리했다면, 로그는\n다음과 같이 보일 것입니다:\n\n<AuthenticationChangeSnippet />\n\n이는 사용자가 로그아웃되었다는 사실을 알려주지만, 시간이 지남에 따라 상태가\n어떻게 변하는지 디버깅하고 이해하는데 무엇이 중요한지 설명하지 않습니다.\n\n#### 고급 Event Transformations\n\n`Bloc`이 `Cubit`보다 뛰어난 또 다른 영역은 `buffer`, `debounceTime`, `throttle`\n등과 같은 반응형 연산자를 활용해야 하는 경우입니다.\n\n`Bloc`에는 들어오는 event의 흐름을 제어하고 변환할 수 있는 event sink가\n있습니다.\n\n예를 들어, 실시간 검색을 구축하는 경우 속도 제한을 피하고 백엔드의 비용/부하를\n줄이기 위해 백엔드에 대한 요청을 debouncing하고 싶을 것입니다.\n\n`Bloc`을 사용하면 `Bloc`이 수신 event를 처리하는 방식을 변경하는 커스텀\n`EventTransformer`를 제공할 수 있습니다.\n\n<DebounceEventTransformerSnippet />\n\n위의 코드를 사용하면 추가 코드를 거의 추가하지 않고도 수신 event를 쉽게\ndebounce할 수 있습니다.\n\n:::tip\n\nEventTransformer에 대한 자세한 내용은\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)를\n확인하세요.\n\n:::\n\n어떤 것을 사용해야 할지 잘 모르겠다면 `Cubit`으로 시작하고 나중에 필요에 따라\n`Bloc`으로 리펙토링하거나 스케일업할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/faqs.mdx",
    "content": "---\ntitle: 자주 묻는 질문\ndescription: Bloc 라이브러리와 관련하여 자주 묻는 질문에 대한 답변입니다.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## State가 업데이트되지 않아요\n\n❔ **Question**: Bloc에서 state를 emit하는데 UI가 업데이트되지 않아요. 무엇이\n문제인가요?\n\n💡 **Answer**: 만약 Equatable을 사용하는 경우 모든 프로퍼티를 props getter에\n전달해야 합니다.\n\n✅ **GOOD**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **BAD**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\n또한, Bloc에서 state의 새 인스턴스를 emit하고 있는지 확인하세요.\n\n✅ **GOOD**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **BAD**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\n`Equatable` 프로퍼티는 항상 수정하지 말고 복사해야 합니다. `Equatable` 클래스에\n`List` 또는 `Map`이 프로퍼티로 있는 경우, 참조가 아닌 프로퍼티 값을 기준으로\n동등성이 평가되도록 `List.of` 또는 `Map.of`을 각각 사용해야 합니다.\n\n:::\n\n## 언제 Equatable를 사용해야 하나요\n\n❔**Question**: Equatable은 언제 사용해야 하나요?\n\n💡**Answer**:\n\n<EquatableEmitSnippet />\n\n위의 시나리오에서 `StateA`가 `Equatable`을 extends 한다면 하나의 state 변경만\n발생합니다 (두 번째 emit은 무시됩니다). 일반적으로 코드를 최적화하여 리빌드\n횟수를 줄이려면 `Equatable`을 사용해야 합니다. 동일한 state가 연속적으로 여러\nTransition을 촉발하려면 `Equatable`을 사용하면 안 됩니다.\n\n또한, `Equatable`을 사용하면 `Matchers`나 `Predicates`를 사용하는 것보다 Bloc\nstate의 특정 인스턴스를 예상할 수 있으므로 Bloc을 훨씬 쉽게 테스트할 수\n있습니다.\n\n<EquatableBlocTestSnippet />\n\n`Equatable`이 없다면 위의 테스트는 실패할 것이고, 다음과 같이 다시 작성해야\n합니다:\n\n<NoEquatableBlocTestSnippet />\n\n## 에러 처리\n\n❔ **Question**: 이전 데이터를 계속 표시하면서 에러를 처리하려면 어떻게 해야\n하나요?\n\n💡 **Answer**:\n\n이는 Bloc의 state가 어떻게 모델링되었는지에 따라 크게 달라집니다. 에러가\n발생하더라도 데이터를 계속 유지해야 하는 경우에는 단일 state 클래스를 사용하는\n것이 좋습니다.\n\n<SingleStateSnippet />\n\n이렇게 하면 위젯이 `데이터` 및 `에러` 프로퍼티에 동시에 접근할 수 있으며, Bloc은\n`state.copyWith`을 사용하여 에러가 발생한 경우에도 이전 데이터를 유지할 수\n있습니다.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Question**: Bloc과 Redux의 차이점은 무엇인가요?\n\n💡 **Answer**:\n\nBLoC 은 다음 규칙에 의해 정의되는 디자인 패턴입니다:\n\n1. BLoC의 입력과 출력은 간단한 Streams과 Sinks 입니다.\n2. 종속성은 주입이 가능하고 플렛폼에 구애받지 않아야 합니다.\n3. 플랫폼별 분기은 허용되지 않습니다.\n4. 위의 규칙을 따르는 한 원하는 대로 구현할 수 있습니다.\n\nUI 가이드라인은 다음과 같습니다:\n\n1. \"충분히 복잡한\" 각 컴포넌트에는 해당하는 BLoC이 있습니다.\n2. 컴포넌트는 입력을 \"있는 그대로\" 보내야 합니다.\n3. 컴포넌트는 가능한 \"있는 그대로\"에 가까운 출력을 표시해야 합니다.\n4. 모든 분기는 간단한 BLoC boolean 출력을 기반으로 해야 합니다.\n\nBloc 라이브러리는 BLoC 디자인 패턴을 구현하며 개발자 경험을 단순화하기 위해\nRxDart를 추상화하는 것을 목표로 합니다.\n\nRedux의 세 원칙은 다음과 같습니다:\n\n1. 신뢰할 수 있는 단일 소스\n2. State는 읽기 전용\n3. 순수 함수로 Change가 이루어짐\n\nBloc 라이브러리는 bloc state가 여러 bloc에 분산되어 있기 때문에 첫 번째 원칙을\n위반합니다. 또한 bloc에는 미들웨어라는 개념이 없으며 bloc은 비동기 상태 변경을\n매우 쉽게 할 수 있도록 설계되어 단일 event에 대해 여러 state를 emit할 수\n있습니다.\n\n## Bloc vs. Provider\n\n❔ **Question**: Bloc과 Provider의 차이점은 무엇인가요?\n\n💡 **Answer**: `provider`는 종속성 주입을 위해 설계되었습니다\n(`InheritedWidget`을 래핑합니다). 여전히 state를 관리하는 방법을 알아내야 합니다\n(`ChangeNotifier`, `Bloc`, `Mobx` 등을 통해). Bloc 라이브러리는 내부적으로\n`provider`를 사용하여 위젯 트리 전체에서 bloc을 쉽게 제공하고 접근할 수 있도록\n합니다.\n\n## BlocProvider.of()가 Bloc을 못 찾아요\n\n❔ **Question**: `BlocProvider.of(context)`을 사용할 때 bloc을 찾을 수 없어요.\n어떻게 고치면 될까요?\n\n💡 **Answer**: Bloc이 제공한 context와 동일한 context에서는 bloc에 접근할 수\n없으므로, 하위 `BuildContext` 내에서 `BlocProvider.of()`가 호출되는지 확인해야\n합니다.\n\n✅ **GOOD**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **BAD**\n\n<BlocProviderBad1Snippet />\n\n## 프로젝트 구조\n\n❔ **Question**: 프로젝트를 어떻게 구조화하는게 좋을까요?\n\n💡 **Answer**: 이 질문에 대한 정답은 없지만, 몇 가지 권장되는 참고 자료는 다음과\n같습니다:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\n가장 중요한 것은 **일관성**있고 **의도적인** 프로젝트 구조를 갖는 것입니다.\n\n## Bloc 내에서 Event 추가하기\n\n❔ **Question**: Bloc 내에서 event를 추가해도 괜찮은가요?\n\n💡 **Answer**: 대부분의 경우, event는 외부에서 추가해야 하지만 일부 경우에는\nevent를 내부적으로 추가하는 것이 합리적일 수 있습니다.\n\n내부 event가 사용되는 가장 일반적인 상황은 Repository의 실시간 업데이트에 대한\n응답으로 state의 변경이 발생해야 하는 경우입니다. 이러한 상황에서 Repository는\n버튼 탭과 같은 외부 event 대신 state 변경에 대한 자극이 됩니다.\n\n다음 예시에서 `MyBloc`의 state는 `UserRepository`의 `Stream<User>`를 통해\n노출되는 현재 사용자에 따라 달라집니다. `MyBloc`은 현재 사용자의 변경 사항을\n수신하고, 사용자가 사용자 stream에서 방출될 때 마다 내부 `_UserChanged` event를\n추가합니다.\n\n<BlocInternalAddEventSnippet />\n\n내부 event를 추가함으로써 event에 대한 커스텀 `transformer`를 지정하여 여러\n`_UserChanged` event가 처리되는 방식을 결정할 수도 있습니다. 기본적으로 event는\n동시에 처리됩니다.\n\n내부 event는 private로 정의되는 것을 강력히 권장합니다. 이는 특정 event가 bloc\n자체 내에서만 사용한다는 것을 명시적으로 알리는 방법이며, 외부 컴포넌트가\nevent에 대해 아는 것을 방지합니다.\n\n<BlocInternalEventSnippet />\n\n또한 외부 `Started` event를 정의하고 `emit.forEach` API를 사용하여 실시간 사용자\n업데이트에 대한 반응을 처리할 수 있습니다.\n\n<BlocExternalForEachSnippet />\n\n위 접근 방식의 장점은 다음과 같습니다:\n\n- 내부 `_UserChanged` event가 필요하지 않습니다.\n- `StreamSubscription`을 수동으로 관리할 필요가 없습니다.\n- Bloc이 사용자 업데이트 steram을 구독하는 시기를 완전히 제어할 수 있습니다.\n\n위 접근 방식의 단점은 다음과 같습니다:\n\n- 구독을 쉽게 `pause` 하거나 `resume`할 수 없습니다.\n- 외부적으로 추가해야 하는 공개 `Started` event를 노출해야 합니다.\n- 사용자 업데이트에 반응하는 방식을 조정하기 위해 커스텀 `transformer`를 사용할\n  수 없습니다.\n\n## Public 메서드 노출\n\n❔ **Question**: Bloc 및 Cubit 인스턴스에 public 메서드를 노출해도 괜찮을까요?\n\n💡 **Answer**\n\nCubit을 생성할 때 state 변경을 촉발할 목적으로만 public 메서드를 노출하는 것이\n좋습니다. 결과적으로, 일반적인 cubit 인스턴스의 모든 public 메서드는 `void` 또는\n`Future<void>`를 반환해야 합니다.\n\nBloc을 생성할 때 커스텀 public 메서드를 노출하지 않고, 대신 `add`를 호출하여\nevent를 bloc에 알리는 것이 좋습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Flutter Bloc 핵심 컨셉\ndescription: package:flutter_bloc의 핵심 개념에 대한 개요입니다.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc)을 사용하기 전에\n다음 섹션을 주의 깊게 읽어주세요.\n\n:::\n\n:::note\n\n`flutter_bloc` 패키지에서 export된 모든 위젯은 `Cubit` 및 `Bloc` 인스턴스와 모두\n통합됩니다.\n\n:::\n\n## Bloc Widgets\n\n### BlocBuilder\n\n**BlocBuilder**는 `Bloc` 및 `builder` 함수 기능이 필요한 Flutter 위젯입니다.\n`BlocBuilder`는 새로운 state에 대한 응답으로 위젯 빌드를 처리합니다.\n`BlocBuilder`는 `StreamBuilder`와 매우 유사하지만 필요한 보일러플레이트 코드의\n양을 줄이기 위해 더 간단한 API를 갖고 있습니다. `builder` 함수는 잠재적으로 여러\n번 호출될 수 있으며 state에 응답하여 위젯을 반환하는\n[순수 함수](https://en.wikipedia.org/wiki/Pure_function)여야 합니다.\n\nNavigation, Dialog 표시 등과 같은 state 변경에 대한 응답으로 무엇이든 간에\n\"수행\"하려면 `BlocListener`를 참조하세요.\n\n만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재\n`BuildContext`를 사용하여 자동으로 bloc을 조회합니다.\n\n<BlocBuilderSnippet />\n\n단일 위젯으로 범위가 지정되고, 상위 `BlocProvider` 및 현재 `BuildContext`를 통해\n접근할 수 없는 bloc을 제공하려는 경우에만 bloc 파라미터를 지정하세요.\n\n<BlocBuilderExplicitBlocSnippet />\n\n`builder` 함수가 호출되는 시점을 세밀하게 제어하기 위해 선택적 `buildWhen`\n파라미터가 제공됩니다. `buildWhen`은 이전 bloc state와 현재 bloc state를 가져온\n후 boolean을 반환합니다. `buildWhen`이 true를 반환하면 `builder`가 `state`와\n함께 호출되고 위젯이 다시 빌드됩니다. `buildWhen`이 false를 반환하면 `builder`는\n`state`와 함께 호출되지 않으며 리빌드는 일어나지 않습니다.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector**는 `BlocBuilder`와 유사하지만 개발자가 현재 bloc state에 따라 새\n값을 선택하여 업데이트를 필터링할 수 있는 Flutter 위젯입니다. 선택한 값이\n변경되지 않으면 불필요한 빌드가 방지됩니다. `BlocSelector`가 `builder`를 다시\n호출해야 하는지 여부를 정확하게 결정하려면 선택한 값을 변경할 수\n없어야(immutable) 합니다.\n\n만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재\n`BuildContext`를 사용하여 자동으로 bloc을 조회합니다.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider**는 `BlocProvider.of<T>(context)`를 통해 child에게 bloc을\n제공하는 Flutter 위젯입니다. 이는 bloc의 단일 인스턴스가 하위 트리 내의 여러\n위젯에 제공될 수 있도록 종속성 주입(DI) 위젯으로 사용됩니다.\n\n대부분의 경우, `BlocProvider`를 사용하여 나머지 하위 트리에서 사용할 수 있는 새\nbloc을 생성해야 합니다. 이 경우 `BlocProvider`가 bloc 생성을 담당하기 때문에\n자동으로 bloc을 close하는 것도 처리합니다.\n\n<BlocProviderSnippet />\n\n기본적으로, `BlocProvider`는 bloc을 lazy하게 생성합니다. 즉,\n`BlocProvider.of<BlocA>(context)`을 통해 bloc을 조회할 때 `create`가 실행된다는\n의미입니다.\n\n이 동작을 무시하고 `create`가 즉시 실행되도록 하려면 `lazy`를 `false`로 설정하면\n됩니다.\n\n<BlocProviderEagerSnippet />\n\n어떤 경우에는 `BlocProvider`를 사용하여 위젯 트리의 새 부분에 기존 bloc을 제공할\n수 잇습니다. 이는 기존 bloc을 새 route에서 사용할 수 있도록 해야 할 때 가장\n일반적으로 사용됩니다. 이 경우 `BlocProvider`는 bloc을 생성하지 않았으므로\n자동으로 bloc을 close하지 않습니다.\n\n<BlocProviderValueSnippet />\n\n그런 다음 `ChildA` 또는 `ScreenA`애서 다음을 사용하여 `BlocA`를 찾을 수\n있습니다:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider**는 여러 `BlocProvider` 위젯을 하나로 병합하는 Flutter\n위젯입니다. `MultiBlocProvider`는 가독성을 향상시키고 여러 `BlocProvider`를\n중첩할 필요성을 제거합니다. `MultiBlocProvider`를 사용하면 다음과 같던 코드를:\n\n<NestedBlocProviderSnippet />\n\n다음과 같이 변경할 수 있습니다:\n\n<MultiBlocProviderSnippet />\n\n### BlocListener\n\n**BlocListener**는 필수 `BlocWidgetListener`와 선택적 `Bloc` 파라미터를 사용하고\nbloc의 state 변경에 대한 응답으로 `listener`를 호출하는 Flutter 위젯입니다.\nnavigation, `Snackbar` 표시, `Dialog` 표시 등과 같이 state 변경당 한 번 발생해야\n하는 기능에 사용해야 합니다.\n\n`listener`는 `BlocBuilder`의 `builder`와 달리 각 state 변경 (초기 state를\n포함하지 **않음**)에 대해 한 번만 호출되며 `void` 함수입니다.\n\n만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재\n`BuildContext`를 사용하여 자동으로 bloc을 조회합니다.\n\n<BlocListenerSnippet />\n\n`BlocProvider` 및 현재 `BuildContext`를 통해 접근할 수 없는 bloc을 제공하려는\n경우에만 bloc 파라미터를 지정하세요.\n\n<BlocListenerExplicitBlocSnippet />\n\n`listner` 함수가 호출되는 시점을 세밀하게 제어하기 위해 선택적 `listenWhen`\n파라미터가 제공됩니다. `listenWhen`은 이전 bloc state와 현재 bloc state를 가져온\n후 boolean을 반환합니다. `listenWhen`이 true를 반환하면 `listener`는 `state`와\n함께 호출됩니다. `listenWhen`이 false를 반환하면 `listener`는 `state`와 함께\n호출되지 않습니다.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener**는 여러 `BlocListener` 위젯을 하나로 병합하는 Flutter\n위젯입니다. `MultiBlocListener`는 가독성을 향상시키고 여러 `BlocListener`를\n중첩할 필요성을 제거합니다. `MultiBlocListener`를 사용하면 다음과 같던 코드를:\n\n<NestedBlocListenerSnippet />\n\n다음과 같이 변경할 수 있습니다:\n\n<MultiBlocListenerSnippet />\n\n### BlocConsumer\n\n**BlocConsumer**는 새로운 state에 반응하기 위해 `builder`와 `listener`를\n노출합니다. `BlocConsumer`는 중첩된 `BlocListener` 및 `BlocBuilder`와\n유사하지만, 필요한 보일러플레이트 코드의 양을 줄입니다. `BlocConsumer`는 UI를\n다시 빌드하고 `bloc`의 상태 변경에 대한 다른 반응을 실행해야 하는 경우에만\n사용해야 합니다. `BlocConsumer`는 필수 `BlocWidgetBuilder` 및\n`BlocWidgetListener`와 선택적인 `bloc`, `BlocBuilderCondition`,\n`BlocListenerCondition` 파라미터를 사용합니다.\n\n만약 `bloc` 파라미터가 생략되면 `BlocBuilder`는 `BlocProvider`와 현재\n`BuildContext`를 사용하여 자동으로 bloc을 조회합니다.\n\n<BlocConsumerSnippet />\n\n선택적 파라미터인 `listenWhen` 및 `buildWhen`을 구현하면 `listener` 및\n`builder`가 호출되는 시점을 더욱 세밀하게 제어할 수 있습니다. `listenWhen` 및\n`buildWhen`은 각 `bloc` `state` 변경 시 호출됩니다. 이들은 각각 이전 `state`와\n현재 `state`를 취하고 `builder` 및/또는 `listener` 함수가 호출되는지 여부를\n결정하는 `bool`을 반환해야 합니다. `BlocConsumer`가 초기화되면 이전 `state`는\n`bloc`의 `state`로 초기화됩니다. `listenWhen` 및 `buildWhen`은 선택사항이며\n구현되지 않은 경우 기본값은 `true` 입니다.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider**는 `RepositoryProvider.of<T>(context)`를 통해 child에게\nrepository을 제공하는 Flutter 위젯입니다. 이는 repository의 단일 인스턴스가 하위\n트리 내의 여러 위젯에 제공될 수 있도록 종속성 주입(DI) 위젯으로 사용됩니다.\n`BlocProvider`는 bloc을 제공하는 데 사용해야 하는 반면, `RepositoryProvider`는\nrepository를 제공하는 데에만 사용해야 합니다.\n\n<RepositoryProviderSnippet />\n\n그런 다음 `ChildA`에서 다음을 사용하여 `Repository` 인스턴스를 찾을 수 있습니다:\n\n<RepositoryProviderLookupSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider**는 여러 `RepositoryProvider` 위젯을 하나로 병합하는\nFlutter 위젯입니다. `MultiRepositoryProvider`는 가독성을 향상시키고 여러\n`RepositoryProvider`를 중첩할 필요성을 제거합니다. `MultiRepositoryProvider`를\n사용하면 다음과 같던 코드를:\n\n<NestedRepositoryProviderSnippet />\n\n다음과 같이 변경할 수 있습니다:\n\n<MultiRepositoryProviderSnippet />\n\n## BlocProvider 사용법\n\n`BlocProvider`를 사용하여 `CounterPage`에 `CounterBloc`을 제공하고\n`BlocBuilder`를 사용하여 state 변경에 대한 반응을 살펴보겠습니다.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\n이 시점에서 우리는 Presentation 레이어를 Business Logic 레이어에서 성공적으로\n분리했습니다. `CounterPage` 위젯은 사용자가 버튼을 탭할 때 어떤 일이 발생하는지\n전혀 모릅니다. 위젯은 단순히 사용자가 증가 또는 감소 버튼을 눌렀음을\n`CounterBloc`에 알려줄 뿐 입니다.\n\n## RepositoryProvider 사용법\n\n[`flutter_weather`][flutter_weather_link] 예시의 맥락에서 `RepositoryProvider`를\n사용하는 방법을 살펴보겠습니다.\n\n<WeatherRepositorySnippet />\n\n앱이 `WeatherRepository`에 명시적으로 종속되어 있으므로 생성자를 통해 인스턴스를\n주입합니다. 이를 통해 빌드 Flavor나 환경에 따라 `WeatherRepository`의 다양한\n인스턴스를 주입할 수 있습니다.\n\n<WeatherMainSnippet />\n\n우리 앱에는 하나의 Repository만 있으므로 `RepositoryProvider.value`를 통해 이를\n위젯 트리에 삽입합니다. Repository가 두 개 이상인 경우\n`MultiRepositoryProvider`를 사용하여 하위 트리에 여러 repository 인스턴스를\n제공할 수 있습니다.\n\n<WeatherAppSnippet />\n\n대부분의 경우, Root 앱 위젯은 `RepositoryProvider`를 통해 하위 트리에 하나\n이상의 repository를 노출합니다.\n\n<WeatherPageSnippet />\n\n이제 bloc을 인스턴스화 할 때, `context.read`를 통해 repository의 인스턴스에\n접근하고 생성자를 통해 repository를 bloc에 주입할 수 있습니다.\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension Methods\n\nDart 2.7에 도입된\n[Extension methods](https://dart.dev/guides/language/extension-methods)는 기존\n라이브러리에 기능을 추가하는 방법입니다. 이번 섹션에서는\n`package:flutter_bloc`에 포함된 확장 메서드와 이를 사용하는 방법을\n살펴보겠습니다.\n\n`flutter_bloc`은\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)의\n사용을 단순화하는 [package:provider](https://pub.dev/packages/provider)에 대한\n종속성이 있습니다.\n\n내부적으로, `package:flutter_bloc`은 `package:provider`를 사용하여\n`BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` 그리고\n`MultiRepositoryProvider` 위젯을 구현합니다. `package:flutter_bloc`은\n`package:provider`의 확장인 `ReadContext`, `WatchContext` 그리고\n`SelectContext`를 export 합니다.\n\n:::note\n\n[`package:provider`](https://pub.dev/packages/provider)에 대해 자세히\n알아보세요.\n\n:::\n\n### context.read\n\n`context.read<T>()`는 `T`타입에 가장 가까운 상위 인스턴스를 조회하며 기능적으로\n`BlocProvider.of<T>(context)`와 동일합니다. `context.read`는 `onPressed` 콜백\n내에 event를 추가하기 위해 bloc 인스턴스를 검색하는 데 가장 일반적으로\n사용됩니다.\n\n:::note\n\n`context.read<T>()`는 `T`를 listen하지 않습니다. 제공된 `T` 타입의 `Object`가\n변경되면 `context.read`는 위젯 리빌드를 촉발하지 않습니다.\n\n:::\n\n#### 사용법\n\n✅ **DO** 콜백에 event를 추가하려면 `context.read`를 사용하세요.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **AVOID** `context.read`를 사용하여 `build` 메서드 내에서 상태를 찾지 마세요.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n위의 사용법은 bloc의 state가 변경되어도 `Text` 위젯이 다시 리빌드되지 않기\n때문에 오류가 발생하기 쉽습니다.\n\n:::caution\n\nState 변경에 따라 다시 빌드하려면 `BlocBuilder` 또는 `context.watch` 를 대신\n사용하세요.\n\n:::\n\n### context.watch\n\n`context.read<T>()`와 마찬가지로, `context.watch<T>()`는 `T`타입에 가장 가까운\n상위 인스턴스를 조회하며 인스턴스의 변경 사항도 listen 합니다. 기능적으로는\n`BlocProvider.of<T>(context, listening: true)`와 동일합니다.\n\n제공된 `T` 타입의 `Object`가 변경되면 `context.watch`는 위젯 리빌드를\n촉발합니다.\n\n:::caution\n\n`context.watch`는 `StatelessWidget` 또는 `State` 클래스의 `build` 메서드\n내에서만 접근할 수 있습니다.\n\n:::\n\n#### 사용법\n\n✅ **DO** 명시적으로 리빌드 scope를 지정하려면 `context.watch` 대신\n`BlocBuilder`를 사용하세요.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Whenever the state changes, only the Text is rebuilt.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n또는, `Builder`를 사용하여 리빌드 scope를 제한하세요.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever the state changes, only the Text is rebuilt.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **DO** `Builder`와 `context.watch`를 `MultiBlocBuilder`처럼 사용하세요.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n❌ **AVOID** `build` 메서드 내의 상위 위젯이 state에 의존하지 않는 경우\n`context.watch`를 사용하지 마세요.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build` 메서드의 root에서 `context.watch`를 사용하면 bloc state가 변경될 때 전체\n위젯이 다시 빌드됩니다.\n\n:::\n\n### context.select\n\n`context.watch<T>()`와 마찬가지로, `context.select<T, R>(R function(T value))`는\n`T`타입에 가장 가까운 상위 인스턴스를 조회하며 인스턴스의 변경 사항도 listen\n합니다. `context.watch`와 달리 `context.select`를 사용하면 state의 작은\n부분(일부)에서의 변경 사항을 listen할 수 있습니다.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\n위의 내용은 `ProfileBloc` state의 `name` 프로퍼티가 변경될 때만 위젯을 다시\n빌드합니다.\n\n#### 사용법\n\n✅ **DO** 명시적으로 리빌드 scope를 지정하려면 `context.select` 대신\n`BlocSelector`를 사용하세요.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Whenever the state.name changes, only the Text is rebuilt.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n또는, `Builder`를 사용하여 리빌드 scope를 제한하세요.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Whenever state.name changes, only the Text is rebuilt.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **AVOID** `build` 메서드 내의 상위 위젯이 state에 의존하지 않는 경우\n`context.select`를 사용하지 마세요.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Whenever the state.value changes, the MaterialApp is rebuilt\n  // even though it is only used in the Text widget.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\n`build` 메서드의 root에서 `context.select`를 사용하면 bloc state가 변경될 때\n전체 위젯이 다시 빌드됩니다.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ko/getting-started.mdx",
    "content": "---\ntitle: 시작하기 가이드\ndescription: Bloc으로 구축을 시작하는 데 필요한 모든 것.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## 패키지\n\nBloc 생태계는 아래에 나열된 여러 패키지들로 구성됩니다:\n\n| 패키지                                                                                     | Description                 | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart Components      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Core Dart APIs              | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Event Transformers          | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter               | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | Testing APIs                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools          | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter Widgets             | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Caching/Persistence Support | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Undo/Redo Support           | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## 설치\n\n<InstallationTabs />\n\n:::note\n\nBloc 사용을 시작하려면 장치에 [Dart SDK](https://dart.dev/get-dart)가 설치되어\n있어야 합니다.\n\n:::\n\n## Imports\n\n이제 bloc을 성공적으로 설치했으므로 `main.dart`를 만들고 해당 `bloc` 패키지를\n가져올 수 있습니다.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/ko/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc 상태 관리 라이브러리\ndescription:\n  Bloc 상태 관리 라이브러리에 대한 공식 문서입니다. Dart, Flutter, 그리고\n  AngularDart를 지원합니다. 예제 및 튜토리얼이 포함되어 있습니다.\nbanner:\n  content: |\n    ✨ <a href=\"https://shop.bloclibrary.dev\">Bloc Shop</a> \n    을 방문해보세요✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Dart를 위한 예측 가능한 상태관리 라이브러리.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: 시작하기\n      link: /ko/getting-started/\n      variant: primary\n      icon: rocket\n    - text: GitHub에서 보기\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid sponsoredBy=\"의 💖으로 후원\" becomeASponsor=\"후원자가 되세요\" />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"시작하기\" icon=\"rocket\">\n\t```sh\n\t# 프로젝트에 bloc을 추가하세요.\n\tdart pub add bloc\n\t```\n\n여기 [시작하기 가이드](/ko/getting-started) 에는 빠르게 Bloc을 사용하는 방법에\n대한 단계별 지침이 나와 있습니다.\n\n</SplitCard>\n\n<Card title=\"가이드 둘러보기\" icon=\"star\">\n\t[공식 튜토리얼](/ko/tutorials/flutter-counter) 을 완료하여 모범 사례를 배우고,\n\tBloc을 사용하는 다양한 앱을 빌드하세요.\n</Card>\n\n<Card title=\"Bloc으로 빌드하기\" icon=\"laptop\">\n\t카운터, 타이머, 무한 스크롤, 날씨, todo 등과 같은 고품질의, 완벽한 테스트를\n\t거친 [예시 앱](https://github.com/felangel/bloc/tree/master/examples)들을\n\t살펴보세요!\n</Card>\n\n<ListCard title=\"학습\" icon=\"open-book\">\n\n    - [왜 Bloc인가?](/ko/why-bloc)\n    - [핵심 컨셉](/ko/bloc-concepts)\n    - [아키텍쳐](/ko/architecture)\n    - [테스팅](/ko/testing)\n    - [작명 규칙](/ko/naming-conventions)\n    - [FAQs](/ko/faqs)\n\n</ListCard>\n\n  <ListCard title=\"통합 개발도구\" icon=\"puzzle\">\n    - [VSCode Integration](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ Integration](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim Integration](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI Integration](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Custom Templates](https://brickhub.dev/search?q=bloc)\n    - [Developer Tools](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Discord에 가입하세요\" />\n"
  },
  {
    "path": "docs/src/content/docs/ko/modeling-state.mdx",
    "content": "---\ntitle: Modeling State\ndescription:\n  package:bloc을 사용할 때 상태를 모델링하는 다양한 방법에 대한 개요입니다.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\n애플리케이션의 상태를 구조화할 때는 여러 가지 다른 접근 방식이 있습니다. 각각은\n고유한 장점과 단점이 있습니다. 이 섹션에서는 여러 접근 방식, 장단점, 그리고\n각각을 언제 사용해야 하는지 살펴보겠습니다.\n\n다음 접근 방식들은 단순히 권장사항이며 완전히 선택사항입니다. 원하는 접근 방식을\n자유롭게 사용하세요. 일부 예제/문서가 주로 간단함/간결함을 위해 이러한 접근\n방식을 따르지 않을 수 있음을 알 수 있을 것입니다.\n\n:::tip\n\n다음 코드 스니펫들은 상태 구조에 중점을 둡니다. 실제로는 다음도 원할 수\n있습니다:\n\n- [`package:equatable`](https://pub.dev/packages/equatable)의 `Equatable` 확장\n- [`package:data_class`](https://pub.dev/packages/data_class)의 `@Data()`로\n  클래스 주석 처리\n- [`package:meta`](https://pub.dev/packages/meta)의 **@immutable**로 클래스 주석\n  처리\n- `copyWith` 메서드 구현\n- 생성자에 `const` 키워드 사용\n\n:::\n\n## Concrete Class and Status Enum\n\n이 접근 방식은 모든 상태에 대한 **single concreate class**와 다양한 상태를\n나타내는 `enum`으로 구성됩니다. 속성들은 nullable이며 현재 상태에 따라\n처리됩니다. 이 접근 방식은 엄격하게 배타적이지 않고/또는 많은 공유 속성을\n포함하는 상태에 가장 적합합니다.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### 장점\n\n- **간단함**: 단일 클래스와 상태 enum을 관리하기 쉽고 모든 속성에 쉽게 접근할 수\n  있습니다.\n- **간결함**: 일반적으로 다른 접근 방식에 비해 더 적은 코드 라인이 필요합니다.\n\n#### 단점\n\n- **타입 안전하지 않음**: 속성에 접근하기 전에 `상태`를 확인해야 합니다.\n  `emit`으로 잘못된 상태를 발생시킬 가능성이 있어 버그로 이어질 수 있습니다.\n  특정 상태의 속성들은 nullable이므로 처리하기 번거로울 수 있고 강제 언래핑이나\n  null 검사를 수행해야 합니다. 이러한 단점 중 일부는 단위 테스트와 특수화된\n  명명된 생성자를 작성하여 완화할 수 있습니다.\n- **비대함**: 시간이 지남에 따라 많은 속성으로 비대해질 수 있는 단일 상태로\n  이어집니다.\n\n#### 결론\n\n이 접근 방식은 간단한 상태나 요구사항이 배타적이지 않은 상태를 호출할 때(예:\n오류가 발생했을 때 스낵바를 표시하면서 마지막 성공한 상태의 이전 데이터를 계속\n표시) 가장 잘 작동합니다. 이 접근 방식은 타입 안전성을 희생하여 유연성과\n간결성을 제공합니다.\n\n## Sealed Class and Subclasses\n\n이 접근 방식은 공유 속성을 포함하는 **sealed class**와 분리된 상태에 대한 여러\n하위 클래스로 구성됩니다. 이 접근 방식은 분리되고 배타적인 상태에 이상적입니다.\n\n<SealedClassAndSubclassesSnippet />\n\n#### 장점\n\n- **타입 안전함**: 코드는 컴파일 타임에 안전하며 잘못된 속성에 실수로 접근할 수\n  없습니다. 각 하위 클래스는 자체 속성을 포함하므로 어떤 속성이 어떤 상태에\n  속하는지 명확합니다.\n- **명시적**: 공유 속성을 상태별 속성과 분리합니다.\n- **완전함**: 완전한 검사를 위한 `switch` 문 사용은 각 상태가 명시적으로\n  처리되도록 보장합니다.\n  - [완전성 검사](https://dart.dev/language/branches#exhaustiveness-checking)를\n    원하지 않거나 나중에 API를 깨뜨리지 않고 하위 타입을 추가할 수 있게 하려면\n    [final](https://dart.dev/language/class-modifiers#final) 수정자를\n    사용하세요.\n  - 자세한 내용은\n    [봉인 클래스 문서](https://dart.dev/language/class-modifiers#sealed)를\n    참조하세요.\n\n#### 단점\n\n- **장황함**: 더 많은 코드가 필요합니다(상태당 하나의 기본 클래스와 하위\n  클래스). 또한 하위 클래스 간 공유 속성에 대한 중복 코드가 필요할 수 있습니다.\n- **복잡성**: 새 속성을 추가하려면 각 하위 클래스와 기본 클래스를 업데이트해야\n  하므로 번거로울 수 있고 상태의 복잡성이 증가할 수 있습니다. 또한 속성에\n  접근하기 위해 불필요한/과도한 타입 검사가 필요할 수 있습니다.\n\n#### 결론\n\n이 접근 방식은 고유한 속성을 가진 잘 정의되고 배타적인 상태에 가장 잘\n작동합니다. 이 접근 방식은 간결함과 단순성을 희생하여 타입 안전성과 완전한\n검사를 제공하며 안전성을 강조합니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/naming-conventions.mdx",
    "content": "---\ntitle: 작명 규칙\ndescription: Bloc 사용 시 권장되는 작명 규칙 개요입니다.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\n다음의 작명 규칙은 단순히 권장 사항일 뿐이며 완진한 선택 사항입니다. 원하는 작명\n규칙을 자유롭게 사용하세요. 일부 예제/문서는 주로 단순성/간결성을 위해 작명\n규칙을 따르지 않을 수 있습니다. 이러한 규칙은 개발자가 여러 명인 대규모\n프로젝트에 강력히 권장됩니다.\n\n## Event 규칙\n\n이벤트는 bloc의 관점에서 이미 발생한 일이므로 **과거형**으로 이름을 지정해야\n합니다.\n\n### 해부\n\n`BlocSubject` + `명사 (선택)` + `동사 (event)`\n\n초기 로드 event는 다음의 규칙을 따라야 합니다: `BlocSubject` + `Started`\n\n:::note\n\n기본이 되는 클래스 이름은 다음과 같아야 합니다: `BlocSubject` + `Event`.\n\n:::\n\n### 예시\n\n✅ **Good**\n\n<EventExamplesGood1 />\n\n❌ **Bad**\n\n<EventExamplesBad1 />\n\n## State 규칙\n\nState는 특정 시점의 스냅샷일 뿐이므로 state는 명사여야 합니다. State를 나타내는\n두 가지 일반적인 방법은 하위 클래스를 사용하거나 단일 클래스를 사용하는\n것입니다.\n\n### 해부\n\n#### 하위 클래스\n\n`BlocSubject` + `동사 (동작)` + `State`\n\nState를 여러 하위 클래스로 표현하는 경우 `State`는 다음 중 하나여야 합니다:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\n초기 state는 다음의 규칙을 따라야 합니다: `BlocSubject` + `Initial`.\n\n:::\n\n#### 단일 클래스\n\n`BlocSubject` + `State`\n\nState를 단일 기본 클래스로 표시할 때 `BlocSubject` + `Status`라는 enum을\n사용하여 다음과 같은 상태를 표시해야 합니다:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\n기본이 되는 클래스 이름은 항상 다음과 같아야 합니다: `BlocSubject` + `State`.\n\n:::\n\n### 예시\n\n✅ **Good**\n\n##### 하위 클래스\n\n<StateExamplesGood1Snippet />\n\n##### 단일 클래스\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Bad**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/ko/testing.mdx",
    "content": "---\ntitle: 테스팅\ndescription: Bloc에 대한 테스트 작성 방법에 대한 기본 사항입니다.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc은 테스트하기가 매우 쉽도록 설계되었습니다. 이 섹션에서는 bloc을 unit test\n하는 방법을 살펴보겠습니다.\n\n단순화를 위해, [Core Concepts](/ko/bloc-concepts)에서 만든 `CounterBloc`에 대한\n테스트를 작성해보겠습니다.\n\n복습을 위해 요약하면, `CounterBloc` 구현은 다음과 같습니다:\n\n<CounterBlocSnippet />\n\n## 준비\n\n테스트 작성을 시작하기 전에 종속성에 테스트 프레임워크를 추가해야 합니다.\n\n프로젝트에 [test](https://pub.dev/packages/test) 및\n[bloc_test](https://pub.dev/packages/bloc_test)를 추가합니다.\n\n<AddDevDependenciesSnippet />\n\n## 테스팅\n\n`CounterBloc` 테스트용 파일인 `counter_bloc_test.dart`를 만들고 테스트 패키지를\n가져오는 것부터 시작해 보겠습니다.\n\n<CounterBlocTestImportsSnippet />\n\n다음으로, `main`와 테스트 그룹을 만들어야 합니다.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\n그룹은 개별 테스트를 구성하고 모든 개별 테스트에서 공통 `setUp` 및 `tearDown`을\n공유할 수 있는 context를 생성하기 위한 것입니다.\n\n:::\n\n모든 테스트에서 사용될 `CounterBloc`의 인스턴스를 생성하는 것부터 시작해\n보겠습니다.\n\n<CounterBlocTestSetupSnippet />\n\n이제 개별 테스트 작성을 시작하겠습니다.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\n`dart test` 명령어를 사용하여 모든 테스트를 실행할 수 있습니다.\n\n:::\n\n이 시점에서 우리는 첫 번째 통과된 테스트를 받아야 합니다! 이제\n[bloc_test](https://pub.dev/packages/bloc_test) 패키지를 사용하여 좀 더 복잡한\n테스트를 작성해 보겠습니다.\n\n<CounterBlocTestBlocTestSnippet />\n\n이제 우리는 테스트를 실행하고 모든 테스트가 통과되는지 확인할 수 있어야 합니다.\n\n이게 전부입니다. 테스트는 쉬워야 하며 코드를 변경하고 리펙토링할 때 자신감을\n가질 수 있어야 합니다.\n\n완전히 테스트된 애플리케이션의 예제는\n[Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)을\n참조 바랍니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Flutter Counter\ndescription: Bloc을 사용한 Flutter 카운터 앱 만들기 튜토리얼입니다.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\n이 튜토리얼에서는 Bloc 라이브러리를 사용해서 Flutter로 카운터 앱을 만들어\n봅니다.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## 핵심 주제\n\n- [BlocObserver](/ko/bloc-concepts#blocobserver)로 상태 변화 관찰하기.\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider)로 하위 위젯에 bloc\n  제공하기.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder)로 상태 변화에 따라 위젯\n  다시 그리기.\n- Bloc 대신 Cubit 사용하기. [차이점이 뭔가요?](/ko/bloc-concepts#cubit-vs-bloc)\n- [context.read](/ko/flutter-bloc-concepts#contextread)로 이벤트 추가하기.\n\n## 프로젝트 설정\n\n먼저 새로운 Flutter 프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\n그 다음 `pubspec.yaml` 파일을 아래 내용으로 교체합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n의존성을 설치합니다.\n\n<FlutterPubGetSnippet />\n\n## 프로젝트 구조\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n이 프로젝트는 기능 기반 디렉토리 구조를 사용합니다. 이런 구조를 사용하면 각\n기능이 독립적으로 구성되어 프로젝트 확장이 쉬워집니다. 이 예제에서는 카운터 기능\n하나만 있지만, 실제 복잡한 앱에서는 수백 개의 기능이 있을 수 있습니다.\n\n## BlocObserver\n\n먼저 `BlocObserver`를 만들어 봅니다. 이걸 사용하면 앱 전체의 상태 변화를 관찰할\n수 있습니다.\n\n`lib/counter_observer.dart` 파일을 생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\n여기서는 `onChange`만 override해서 모든 상태 변화를 확인합니다.\n\n:::note\n\n`onChange`는 `Bloc`과 `Cubit` 인스턴스 모두에서 동일하게 동작합니다.\n\n:::\n\n## main.dart\n\n다음으로 `lib/main.dart` 파일을 아래 내용으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n방금 만든 `CounterObserver`를 초기화하고, `runApp`에 다음에 만들 `CounterApp`\n위젯을 전달합니다.\n\n## Counter App\n\n`lib/app.dart` 파일을 생성합니다:\n\n`CounterApp`은 `MaterialApp`이고 `home`으로 `CounterPage`를 지정합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`CounterApp`이 `MaterialApp`을 상속하는 이유는 `CounterApp` 자체가\n`MaterialApp`이기 때문입니다. 대부분의 경우 `StatelessWidget`이나\n`StatefulWidget`을 만들고 `build` 메서드에서 위젯을 조합하지만, 여기서는 조합할\n위젯이 없어서 `MaterialApp`을 직접 상속하는 게 더 간단합니다.\n\n:::\n\n다음은 `CounterPage`를 살펴봅니다.\n\n## Counter Page\n\n`lib/counter/view/counter_page.dart` 파일을 생성합니다:\n\n`CounterPage` 위젯은 `CounterCubit`(다음에 살펴볼 예정)을 생성하고\n`CounterView`에 제공하는 역할을 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\n`Cubit`의 생성과 사용을 분리하는 게 중요합니다. 이렇게 하면 코드 테스트와\n재사용이 훨씬 쉬워집니다.\n\n:::\n\n## Counter Cubit\n\n`lib/counter/cubit/counter_cubit.dart` 파일을 생성합니다:\n\n`CounterCubit` 클래스는 두 가지 메서드를 제공합니다:\n\n- `increment`: 현재 상태에 1을 더합니다.\n- `decrement`: 현재 상태에서 1을 뺍니다.\n\n`CounterCubit`이 관리하는 상태 타입은 `int`이고 초기 상태는 `0`입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\n[VSCode Extension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)이나\n[IntelliJ Plugin](https://plugins.jetbrains.com/plugin/12129-bloc)을 사용하면\nCubit을 자동으로 생성할 수 있습니다.\n\n:::\n\n다음으로 상태를 사용하고 `CounterCubit`과 상호작용하는 `CounterView`를\n살펴봅니다.\n\n## Counter View\n\n`lib/counter/view/counter_view.dart` 파일을 생성합니다:\n\n`CounterView`는 현재 카운트 값을 표시하고, 카운터를 증가/감소시키는 두 개의\nFloatingActionButton을 렌더링합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\n`BlocBuilder`로 `Text` 위젯을 감싸서 `CounterCubit` 상태가 바뀔 때마다 텍스트를\n업데이트합니다. 또한 `context.read<CounterCubit>()`을 사용해서 가장 가까운\n`CounterCubit` 인스턴스를 찾습니다.\n\n:::note\n\n`BlocBuilder`로 `Text` 위젯만 감싼 이유는 `CounterCubit` 상태 변화에 반응해서\n다시 그려야 하는 위젯이 `Text`뿐이기 때문입니다. 상태 변화에 다시 그릴 필요가\n없는 위젯은 불필요하게 감싸지 마세요.\n\n:::\n\n## Barrel 파일\n\n`lib/counter/view/view.dart` 파일을 생성합니다:\n\n`view.dart`를 추가해서 counter view의 공개 부분을 export합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\n`lib/counter/counter.dart` 파일을 생성합니다:\n\n`counter.dart`를 추가해서 counter 기능의 모든 공개 부분을 export합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\n끝입니다! 프레젠테이션 레이어와 비즈니스 로직 레이어를 분리했습니다.\n`CounterView`는 사용자가 버튼을 누를 때 무슨 일이 일어나는지 모릅니다. 단지\n`CounterCubit`에 알릴 뿐입니다. 마찬가지로 `CounterCubit`은 상태(카운터 값)가\n어떻게 표시되는지 모릅니다. 메서드 호출에 대한 응답으로 새로운 상태를 emit할\n뿐입니다.\n\n`flutter run`으로 앱을 실행하면 기기나 시뮬레이터/에뮬레이터에서 확인할 수\n있습니다.\n\n이 예제의 전체 소스 코드(단위 테스트와 위젯 테스트 포함)는\n[여기](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: Flutter Firebase Login\ndescription: Bloc과 Firebase를 사용한 Flutter 로그인 플로우 튜토리얼입니다.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\n이 튜토리얼에서는 Bloc 라이브러리를 사용해서 Flutter에서 Firebase 로그인\n플로우를 만들어 봅니다.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## 핵심 주제\n\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider)로 하위 위젯에 bloc\n  제공하기.\n- Bloc 대신 Cubit 사용하기. [차이점이 뭔가요?](/ko/bloc-concepts#cubit-vs-bloc)\n- [context.read](/ko/flutter-bloc-concepts#contextread)로 이벤트 추가하기.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)로 불필요한 rebuild\n  방지하기.\n- [RepositoryProvider](/ko/flutter-bloc-concepts#repositoryprovider)로 하위\n  위젯에 repository 제공하기.\n- [BlocListener](/ko/flutter-bloc-concepts#bloclistener)로 상태 변화에 반응하기.\n- [context.read](/ko/flutter-bloc-concepts#contextselect)로 이벤트 추가하기.\n\n## 프로젝트 설정\n\n새로운 Flutter 프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\n[로그인 튜토리얼](/ko/tutorials/flutter-login)처럼 내부 패키지를 만들어서 앱\n아키텍처를 더 잘 계층화하고, 명확한 경계를 유지하며, 재사용성과 테스트 용이성을\n높입니다.\n\n이 경우 [firebase_auth](https://pub.dev/packages/firebase_auth)와\n[google_sign_in](https://pub.dev/packages/google_sign_in) 패키지가 데이터\n레이어가 되므로, 두 API 클라이언트의 데이터를 조합하는\n`AuthenticationRepository`만 만들면 됩니다.\n\n## Authentication Repository\n\n`AuthenticationRepository`는 사용자 인증과 사용자 정보 가져오기의 내부 구현\n세부사항을 추상화합니다. 지금은 Firebase와 통합하지만, 나중에 내부 구현을 바꿔도\n앱에는 영향이 없습니다.\n\n### 설정\n\n프로젝트 루트에 `packages/authentication_repository`와 `pubspec.yaml`을\n생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n`authentication_repository` 디렉토리에서 다음을 실행해서 의존성을 설치합니다:\n\n<FlutterPubGetSnippet />\n\n대부분의 패키지처럼 `authentication_repository`는\n`packages/authentication_repository/lib/authentication_repository.dart`를 통해\nAPI를 노출합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\n`authentication_repository` 패키지는 `AuthenticationRepository`와 모델들을\n노출합니다.\n\n:::\n\n다음으로 모델을 살펴봅니다.\n\n### User\n\n`User` 모델은 인증 도메인에서 사용자를 설명합니다. 이 예제에서 사용자는 `email`,\n`id`, `name`, `photo`로 구성됩니다.\n\n:::note\n\n도메인에서 사용자가 어떻게 생겼는지 정의하는 건 전적으로 여러분에게 달렸습니다.\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\n`User` 클래스는 [equatable](https://pub.dev/packages/equatable)을 상속해서 다른\n`User` 인스턴스를 값으로 비교할 수 있습니다.\n\n:::\n\n:::tip\n\n`null` User를 처리하지 않고 항상 구체적인 `User` 객체로 작업할 수 있도록\n`static` empty `User`를 정의하는 게 유용합니다.\n\n:::\n\n### Repository\n\n`AuthenticationRepository`는 사용자 인증과 사용자 정보 가져오기의 구현을\n추상화합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository`는 `User`가 바뀔 때 알림을 받을 수 있도록 구독할 수\n있는 `Stream<User>`를 노출합니다. 또한 `signUp`, `logInWithGoogle`,\n`logInWithEmailAndPassword`, `logOut` 메서드를 노출합니다.\n\n:::note\n\n`AuthenticationRepository`는 데이터 레이어에서 발생할 수 있는 저수준 에러도\n처리하고, 도메인에 맞는 깔끔하고 간단한 에러 집합을 노출합니다.\n\n:::\n\n`AuthenticationRepository`는 여기까지입니다. 다음으로 만든 Flutter 프로젝트에\n어떻게 통합하는지 살펴봅니다.\n\n## Firebase 설정\n\n앱을 Firebase에 연결하고\n[google_sign_in](https://pub.dev/packages/google_sign_in)을 활성화하려면\n[firebase_auth 사용 설명서](https://pub.dev/packages/firebase_auth#usage)를\n따라야 합니다.\n\n:::caution\n\nAndroid에서 `google-services.json`을, iOS에서 `GoogleService-Info.plist`와\n`Info.plist`를 업데이트하는 걸 잊지 마세요. 그렇지 않으면 앱이 크래시합니다.\n\n:::\n\n## 프로젝트 의존성\n\n프로젝트 루트에 생성된 `pubspec.yaml`을 다음으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n모든 로컬 에셋을 위한 assets 디렉토리를 지정하고 있습니다. 프로젝트 루트에\n`assets` 디렉토리를 만들고\n[bloc 로고](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\n에셋을 추가합니다(나중에 사용).\n\n그 다음 모든 의존성을 설치합니다:\n\n<FlutterPubGetSnippet />\n\n:::note\n\n명확한 분리를 유지하면서 빠르게 반복할 수 있도록 path를 통해\n`authentication_repository` 패키지에 의존하고 있습니다.\n\n:::\n\n## main.dart\n\n`main.dart` 파일을 다음으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n앱의 전역 설정을 하고 `App` 인스턴스와 함께 `runApp`을 호출합니다.\n\n:::note\n\n`App`에 `AuthenticationRepository`의 단일 인스턴스를 주입하고 있고, 이건\n명시적인 생성자 의존성입니다.\n\n:::\n\n## App\n\n[로그인 튜토리얼](/ko/tutorials/flutter-login)처럼 `app.dart`는\n`RepositoryProvider`를 통해 앱에 `AuthenticationRepository` 인스턴스를 제공하고,\n`AuthenticationBloc` 인스턴스도 생성해서 제공합니다. 그런 다음 `AppView`가\n`AuthenticationBloc`을 사용하고 `AuthenticationState`에 따라 현재 라우트를\n업데이트합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\n`AppBloc`은 앱의 전역 상태를 관리합니다. `AuthenticationRepository`에 의존하고\n현재 사용자의 변경에 대한 응답으로 새 상태를 emit하기 위해 `user` Stream을\n구독합니다.\n\n### State\n\n`AppState`는 `AppStatus`와 `User`로 구성됩니다. 기본 생성자는 선택적 `User`를\n받아서 적절한 인증 상태와 함께 private 생성자로 리다이렉트합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### Event\n\n`AppEvent`는 두 개의 하위 클래스가 있습니다:\n\n- `AppUserSubscriptionRequested`: bloc에게 user 스트림을 구독하라고 알립니다.\n- `AppLogoutPressed`: bloc에게 사용자 로그아웃 액션을 알립니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\n생성자 본문에서 `AppEvent` 하위 클래스가 해당 이벤트 핸들러에 매핑됩니다.\n\n`_onUserSubscriptionRequested` 이벤트 핸들러에서 `AppBloc`은 `emit.onEach`를\n사용해서 `AuthenticationRepository`의 user 스트림을 구독하고 각 `User`에 대한\n응답으로 상태를 emit합니다.\n\n`emit.onEach`는 내부적으로 스트림 구독을 생성하고 `AppBloc`이나 user 스트림이\n닫히면 취소를 처리합니다.\n\nuser 스트림이 에러를 emit하면 `addError`가 에러와 스택 트레이스를 listen하고\n있는 `BlocObserver`에 전달합니다.\n\n:::caution\n\n`onError`가 생략되면 user 스트림의 모든 에러는 처리되지 않은 것으로 간주되어\n`onEach`에서 throw됩니다. 결과적으로 user 스트림에 대한 구독이 취소됩니다.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/ko/bloc-concepts/#blocobserver-1)는 특히 분석과 크래시\n리포팅에서 Bloc 이벤트, 에러, 상태 변화를 로깅하는 데 좋습니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## Models\n\n`Email`과 `Password` 입력 모델은 유효성 검사 로직을 캡슐화하는 데 유용하고\n`LoginForm`과 `SignUpForm`(튜토리얼 후반부) 모두에서 사용됩니다.\n\n두 입력 모델은 [formz](https://pub.dev/packages/formz) 패키지를 사용해서\n만들어지고, `String` 같은 원시 타입 대신 유효성 검사된 객체로 작업할 수 있게\n해줍니다.\n\n### Email\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## Login Page\n\n`LoginPage`는 `LoginCubit` 인스턴스를 생성하고 `LoginForm`에 제공합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nbloc/cubit의 생성과 사용을 분리하는 게 매우 중요합니다. 이렇게 하면 mock\n인스턴스를 쉽게 주입하고 뷰를 독립적으로 테스트할 수 있습니다.\n\n:::\n\n## Login Cubit\n\n`LoginCubit`은 폼의 `LoginState`를 관리합니다. `logInWithCredentials`,\n`logInWithGoogle` API를 노출하고 email/password가 업데이트될 때 알림을 받습니다.\n\n### State\n\n`LoginState`는 `Email`, `Password`, `FormzStatus`로 구성됩니다. `Email`과\n`Password` 모델은 [formz](https://pub.dev/packages/formz) 패키지의\n`FormzInput`을 상속합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\n`LoginCubit`은 credentials나 google 로그인을 통해 사용자를 로그인시키기 위해\n`AuthenticationRepository`에 의존합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\n`LoginState`가 상당히 단순하고 지역적이기 때문에 여기서는 `Bloc` 대신 `Cubit`을\n사용했습니다. 이벤트 없이도 한 상태에서 다른 상태로의 변화를 보는 것만으로 무슨\n일이 일어났는지 꽤 잘 알 수 있고, 코드가 훨씬 단순하고 간결합니다.\n\n:::\n\n## Login Form\n\n`LoginForm`은 `LoginState`에 대한 응답으로 폼을 렌더링하고 사용자 상호작용에\n대한 응답으로 `LoginCubit`의 메서드를 호출합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`LoginForm`은 사용자가 새 계정을 만들 수 있는 `SignUpPage`로 이동하는 \"Create\nAccount\" 버튼도 렌더링합니다.\n\n## Sign Up Page\n\n`SignUp` 구조는 `Login` 구조를 미러링하고 `SignUpPage`, `SignUpView`,\n`SignUpCubit`으로 구성됩니다.\n\n`SignUpPage`는 `SignUpCubit` 인스턴스를 생성하고 `SignUpForm`에\n제공합니다(`LoginPage`와 정확히 같습니다).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\n`LoginCubit`처럼 `SignUpCubit`도 새 사용자 계정을 만들기 위해\n`AuthenticationRepository`에 의존합니다.\n\n:::\n\n## Sign Up Cubit\n\n`SignUpCubit`은 `SignUpForm`의 상태를 관리하고 새 사용자 계정을 만들기 위해\n`AuthenticationRepository`와 통신합니다.\n\n### State\n\n`SignUpState`는 유효성 검사 로직이 같기 때문에 같은 `Email`과 `Password` 폼 입력\n모델을 재사용합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\n`SignUpCubit`은 `LoginCubit`과 매우 비슷하지만 login 대신 폼을 submit하는 API를\n노출한다는 주요 차이점이 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## Sign Up Form\n\n`SignUpForm`은 `SignUpState`에 대한 응답으로 폼을 렌더링하고 사용자 상호작용에\n대한 응답으로 `SignUpCubit`의 메서드를 호출합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## Home Page\n\n사용자가 로그인하거나 가입에 성공하면 `user` 스트림이 업데이트되고, 이는\n`AuthenticationBloc`에서 상태 변화를 트리거하여 `AppView`가 네비게이션 스택에\n`HomePage` 라우트를 push하게 됩니다.\n\n`HomePage`에서 사용자는 프로필 정보를 보고 `AppBar`의 exit 아이콘을 탭해서\n로그아웃할 수 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`home` 기능 내에서 `view` 디렉토리와 함께 해당 기능에 특화된 재사용 가능한\n컴포넌트를 위한 `widgets` 디렉토리가 만들어졌습니다. 이 경우 간단한 `Avatar`\n위젯이 export되어 `HomePage` 내에서 사용됩니다.\n\n:::\n\n:::note\n\nlogout `IconButton`이 탭되면 `AuthenticationBloc`에\n`AuthenticationLogoutRequested` 이벤트가 추가되어 사용자를 로그아웃시키고\n`LoginPage`로 다시 이동합니다.\n\n:::\n\n이 시점에서 Firebase를 사용한 꽤 괜찮은 로그인 구현이 있고, Bloc 라이브러리를\n사용해서 프레젠테이션 레이어와 비즈니스 로직 레이어를 분리했습니다.\n\n이 예제의 전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: Flutter Infinite List\ndescription: Bloc을 사용한 Flutter 무한 스크롤 리스트 만들기 튜토리얼입니다.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\n이 튜토리얼에서는 Flutter와 bloc 라이브러리를 사용해서 사용자가 스크롤할 때\n네트워크에서 데이터를 가져와 로드하는 앱을 만들어 봅니다.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## 핵심 주제\n\n- [BlocObserver](/ko/bloc-concepts#blocobserver)로 상태 변화 관찰하기.\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider)로 하위 위젯에 bloc\n  제공하기.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder)로 상태 변화에 따라 위젯\n  다시 그리기.\n- [context.read](/ko/flutter-bloc-concepts#contextread)로 이벤트 추가하기.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)로 불필요한 rebuild\n  방지하기.\n- Rx로 `transformEvents` 메서드 사용하기.\n\n## 프로젝트 설정\n\n새로운 Flutter 프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\npubspec.yaml 파일을 아래 내용으로 교체합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n의존성을 설치합니다.\n\n<FlutterPubGetSnippet />\n\n## 프로젝트 구조\n\n```\n├── lib\n|   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n|   |   |   └── post_event.dart\n|   |   |   └── post_state.dart\n|   |   └── models\n|   |   |   └── models.dart*\n|   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n|   |   |   └── view.dart*\n|   |   └── widgets\n|   |   |   └── bottom_loader.dart\n|   |   |   └── post_list_item.dart\n|   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n이 프로젝트는 기능 기반 디렉토리 구조를 사용합니다. 이런 구조를 사용하면 각\n기능이 독립적으로 구성되어 프로젝트 확장이 쉬워집니다. 이 예제에서는 post 기능\n하나만 있고, 별표(\\*)로 표시된 barrel 파일과 함께 각각의 폴더로 나뉘어 있습니다.\n\n## REST API\n\n이 데모 앱에서는 데이터 소스로\n[jsonplaceholder](http://jsonplaceholder.typicode.com)를 사용합니다.\n\n:::note\n\njsonplaceholder는 가짜 데이터를 제공하는 온라인 REST API입니다. 프로토타입을\n만들 때 매우 유용합니다.\n\n:::\n\n브라우저에서 새 탭을 열고\nhttps://jsonplaceholder.typicode.com/posts?_start=0&_limit=2 에 접속해서 API가\n뭘 반환하는지 확인해 보세요.\n\n<PostsJsonSnippet />\n\n:::note\n\nURL에서 start와 limit를 GET 요청의 쿼리 파라미터로 지정했습니다.\n\n:::\n\n이제 데이터가 어떻게 생겼는지 알았으니 모델을 만들어 봅니다.\n\n## 데이터 모델\n\n`post.dart`를 생성하고 Post 객체 모델을 만듭니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post`는 `id`, `title`, `body`를 가진 간단한 클래스입니다.\n\n:::note\n\n`Post`를 비교할 수 있도록 [`Equatable`](https://pub.dev/packages/equatable)을\n상속합니다. 이게 없으면 두 `Post` 객체의 차이를 알 수 있도록 equality와\nhashCode를 직접 override해야 합니다. 자세한 내용은\n[패키지 문서](https://pub.dev/packages/equatable)를 참고하세요.\n\n:::\n\n이제 `Post` 객체 모델이 있으니 비즈니스 로직 컴포넌트(bloc)를 만들어 봅니다.\n\n## Post Events\n\n구현에 들어가기 전에 `PostBloc`이 뭘 할 건지 정의해야 합니다.\n\n간단히 말하면 사용자 입력(스크롤)에 반응해서 프레젠테이션 레이어가 표시할 수\n있도록 더 많은 post를 가져옵니다. `Event`부터 만들어 봅니다.\n\n`PostBloc`은 하나의 이벤트에만 반응합니다. `PostFetched`는 프레젠테이션 레이어가\n더 많은 Post를 표시해야 할 때마다 추가됩니다. `PostFetched` 이벤트는\n`PostEvent`의 한 종류이므로 `bloc/post_event.dart`를 생성하고 이벤트를\n구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\n정리하면 `PostBloc`은 `PostEvent`를 받아서 `PostState`로 변환합니다. 모든\n`PostEvent`(PostFetched)를 정의했으니 이제 `PostState`를 정의합니다.\n\n## Post States\n\n프레젠테이션 레이어가 제대로 렌더링하려면 다음 정보가 필요합니다:\n\n- `PostInitial`: 초기 배치의 post가 로드되는 동안 로딩 인디케이터를 렌더링해야\n  함을 알립니다.\n- `PostSuccess`: 렌더링할 콘텐츠가 있음을 알립니다.\n  - `posts`: 표시할 `List<Post>`\n  - `hasReachedMax`: 최대 post 수에 도달했는지 여부\n- `PostFailure`: post를 가져오는 중 에러가 발생했음을 알립니다.\n\n이제 `bloc/post_state.dart`를 생성하고 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\n`PostSuccess` 인스턴스를 복사하고 0개 이상의 속성을 편리하게 업데이트할 수\n있도록 `copyWith`를 구현했습니다(나중에 유용하게 쓰입니다).\n\n:::\n\n이제 `Event`와 `State`가 구현됐으니 `PostBloc`을 만들 수 있습니다.\n\n## Post Bloc\n\n단순함을 위해 `PostBloc`은 `http client`에 직접 의존합니다. 하지만 프로덕션\n앱에서는 api client를 주입하고 repository 패턴([문서](/ko/architecture))을\n사용하는 것을 권장합니다.\n\n`post_bloc.dart`를 생성하고 빈 `PostBloc`을 만듭니다.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\n클래스 선언만 봐도 PostBloc이 PostEvent를 입력으로 받아서 PostState를 출력한다는\n걸 알 수 있습니다.\n\n:::\n\n다음으로 들어오는 `PostFetched` 이벤트를 처리할 이벤트 핸들러를 등록해야 합니다.\n`PostFetched` 이벤트에 대한 응답으로 API에서 post를 가져오는 `_fetchPosts`를\n호출합니다.\n\n<PostBlocOnPostFetchedSnippet />\n\n`PostBloc`은 이벤트 핸들러에 제공된 `Emitter<PostState>`를 통해 새로운 상태를\n`emit`합니다. 자세한 내용은 [핵심 개념](/ko/bloc-concepts#streams)을 참고하세요.\n\n이제 `PostEvent`가 추가될 때마다 `PostFetched` 이벤트이고 가져올 post가 더\n있으면 `PostBloc`이 다음 20개의 post를 가져옵니다.\n\n최대 post 수(100)를 초과해서 가져오려고 하면 API가 빈 배열을 반환합니다. 빈\n배열을 받으면 bloc은 `hasReachedMax`를 true로 설정한 currentState를\n`emit`합니다.\n\npost를 가져올 수 없으면 `PostStatus.failure`를 emit합니다.\n\npost를 가져올 수 있으면 `PostStatus.success`와 전체 post 목록을 emit합니다.\n\nAPI에 불필요하게 스팸을 보내는 것을 방지하기 위해 `PostFetched` 이벤트를\n`throttle`하는 최적화를 할 수 있습니다. `_onFetched` 이벤트 핸들러를 등록할 때\n`transform` 파라미터를 사용하면 됩니다.\n\n:::note\n\n`on<PostFetched>`에 `transformer`를 전달하면 이벤트가 처리되는 방식을\n커스터마이즈할 수 있습니다.\n\n:::\n\n:::note\n\n`throttle` API를 사용하려면\n[`package:stream_transform`](https://pub.dev/packages/stream_transform)을\nimport해야 합니다.\n\n:::\n\n<PostBlocTransformerSnippet />\n\n완성된 `PostBloc`은 다음과 같습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\n비즈니스 로직 구현이 끝났으니 이제 프레젠테이션 레이어만 구현하면 됩니다.\n\n## 프레젠테이션 레이어\n\n`main.dart`에서 main 함수를 구현하고 `runApp`을 호출해서 루트 위젯을\n렌더링합니다. 여기서 transition과 에러를 로깅하기 위한 bloc observer도 포함할 수\n있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n프로젝트의 루트인 `App` 위젯에서 home을 `PostsPage`로 설정합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n`PostsPage` 위젯에서 `BlocProvider`를 사용해서 `PostBloc` 인스턴스를 생성하고\n하위 트리에 제공합니다. 또한 앱이 로드될 때 초기 배치의 Post를 요청하도록\n`PostFetched` 이벤트를 추가합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\n다음으로 post를 표시하고 `PostBloc`에 연결할 `PostsList` 뷰를 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList`는 `ScrollController`를 유지해야 하므로 `StatefulWidget`입니다.\n`initState`에서 스크롤 이벤트에 반응할 수 있도록 `ScrollController`에 listener를\n추가합니다. 또한 `context.read<PostBloc>()`을 통해 `PostBloc` 인스턴스에\n접근합니다.\n\n:::\n\nbuild 메서드는 `BlocBuilder`를 반환합니다. `BlocBuilder`는\n[flutter_bloc 패키지](https://pub.dev/packages/flutter_bloc)의 Flutter 위젯으로,\n새로운 bloc 상태에 대한 응답으로 위젯을 빌드합니다. `PostBloc` 상태가 바뀔\n때마다 새로운 `PostState`와 함께 builder 함수가 호출됩니다.\n\n:::caution\n\nStatefulWidget이 dispose될 때 `ScrollController`를 dispose해서 정리하는 것을\n잊지 마세요.\n\n:::\n\n사용자가 스크롤할 때마다 페이지를 얼마나 스크롤했는지 계산하고, 거리가\n`maxScrollExtent`의 90% 이상이면 더 많은 post를 로드하기 위해 `PostFetched`\n이벤트를 추가합니다.\n\n다음으로 더 많은 post를 로드 중임을 사용자에게 알려주는 `BottomLoader` 위젯을\n구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\n마지막으로 개별 `Post`를 렌더링할 `PostListItem`을 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\n이 시점에서 앱을 실행하면 모든 게 동작합니다. 하지만 한 가지 더 할 수 있는 게\n있습니다.\n\nbloc 라이브러리를 사용하면 모든 `Transition`에 한 곳에서 접근할 수 있다는\n추가적인 이점이 있습니다.\n\n한 상태에서 다른 상태로의 변경을 `Transition`이라고 합니다.\n\n:::note\n\n`Transition`은 현재 상태, 이벤트, 다음 상태로 구성됩니다.\n\n:::\n\n이 앱에는 bloc이 하나뿐이지만, 큰 앱에서는 앱 상태의 다른 부분을 관리하는 많은\nbloc이 있는 게 일반적입니다.\n\n모든 `Transition`에 대해 뭔가를 하고 싶다면 간단히 `BlocObserver`를 만들면\n됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\n`BlocObserver`를 상속하고 `onTransition` 메서드를 override하기만 하면 됩니다.\n\n:::\n\n이제 Bloc `Transition`이 발생할 때마다 콘솔에 transition이 출력되는 걸 볼 수\n있습니다.\n\n:::note\n\n실제로 다른 `BlocObserver`를 만들 수 있고, 모든 상태 변경이 기록되기 때문에 모든\n사용자 상호작용과 상태 변경을 한 곳에서 쉽게 추적할 수 있습니다!\n\n:::\n\n이게 전부입니다! [bloc](https://pub.dev/packages/bloc)과\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) 패키지를 사용해서\nFlutter에서 무한 리스트를 성공적으로 구현했고, 프레젠테이션 레이어와 비즈니스\n로직을 분리했습니다.\n\n`PostsPage`는 `Post`가 어디서 오는지, 어떻게 가져오는지 모릅니다. 반대로\n`PostBloc`은 `State`가 어떻게 렌더링되는지 모르고, 단순히 이벤트를 상태로\n변환합니다.\n\n이 예제의 전체 소스 코드는\n[여기](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-login.mdx",
    "content": "---\ntitle: Flutter Login\ndescription: Bloc을 사용한 Flutter 로그인 플로우 튜토리얼입니다.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\n이 튜토리얼에서는 Bloc 라이브러리를 사용해서 Flutter에서 로그인 플로우를 만들어\n봅니다.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## 핵심 주제\n\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider)로 하위 위젯에 bloc\n  제공하기.\n- [context.read](/ko/flutter-bloc-concepts#contextread)로 이벤트 추가하기.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)로 불필요한 rebuild\n  방지하기.\n- [RepositoryProvider](/ko/flutter-bloc-concepts#repositoryprovider)로 하위\n  위젯에 repository 제공하기.\n- [BlocListener](/ko/flutter-bloc-concepts#bloclistener)로 상태 변화에 반응하기.\n- [context.select](/ko/flutter-bloc-concepts#contextselect)로 bloc 상태의 일부에\n  따라 UI 업데이트하기.\n\n## 프로젝트 설정\n\n새로운 Flutter 프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\n의존성을 설치합니다.\n\n<FlutterPubGetSnippet />\n\n## Authentication Repository\n\n먼저 인증 도메인을 관리하는 `authentication_repository` 패키지를 만듭니다.\n\n프로젝트 루트에 모든 내부 패키지가 들어갈 `packages/authentication_repository`\n디렉토리를 생성합니다.\n\n대략적으로 디렉토리 구조는 다음과 같습니다:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\n다음으로 `authentication_repository` 패키지의 `pubspec.yaml`을 생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\n`package:authentication_repository`는 외부 의존성이 없는 순수 Dart 패키지입니다.\n\n:::\n\n다음으로 `AuthenticationRepository` 클래스 자체를\n`packages/authentication_repository/lib/src/authentication_repository.dart`에\n구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository`는 사용자가 로그인하거나 로그아웃할 때 앱에 알리는 데\n사용할 `AuthenticationStatus` 업데이트 `Stream`을 노출합니다.\n\n또한 단순화를 위해 stub으로 처리된 `logIn`과 `logOut` 메서드가 있지만,\n`FirebaseAuth`나 다른 인증 프로바이더로 쉽게 확장할 수 있습니다.\n\n:::note\n\n내부적으로 `StreamController`를 유지하고 있으므로 컨트롤러가 더 이상 필요하지\n않을 때 닫을 수 있도록 `dispose` 메서드를 노출합니다.\n\n:::\n\n마지막으로 public exports를 포함할\n`packages/authentication_repository/lib/authentication_repository.dart`를\n생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository`는 여기까지입니다. 다음으로 `UserRepository`를\n만듭니다.\n\n## User Repository\n\n`AuthenticationRepository`처럼 `packages` 디렉토리 안에 `user_repository`\n패키지를 만듭니다.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\n다음으로 `user_repository`의 `pubspec.yaml`을 생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\n`user_repository`는 사용자 도메인을 담당하고 현재 사용자와 상호작용하기 위한\nAPI를 노출합니다.\n\n먼저 `packages/user_repository/lib/src/models/user.dart`에 user 모델을\n정의합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\n단순화를 위해 user는 `id` 속성만 가지지만, 실제로는 `firstName`, `lastName`,\n`avatarUrl` 등의 추가 속성이 있을 수 있습니다.\n\n:::note\n\n[`package:equatable`](https://pub.dev/packages/equatable)을 사용해서 `User`\n객체의 값 비교를 활성화합니다.\n\n:::\n\n다음으로 `packages/user_repository/lib/src/models`에 `models.dart`를 생성해서\n단일 import로 여러 모델을 가져올 수 있도록 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\n이제 모델이 정의됐으니 `packages/user_repository/lib/src/user_repository.dart`에\n`UserRepository` 클래스를 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\n이 간단한 예제에서 `UserRepository`는 현재 사용자를 가져오는 `getUser` 메서드\n하나만 노출합니다. stub으로 처리됐지만 실제로는 백엔드에서 현재 사용자를\n쿼리하는 곳입니다.\n\n`user_repository` 패키지가 거의 완료됐습니다. 남은 건 public exports를 정의하는\n`packages/user_repository/lib`의 `user_repository.dart` 파일을 만드는\n것뿐입니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\n이제 `authentication_repository`와 `user_repository` 패키지가 완료됐으니 Flutter\n앱에 집중할 수 있습니다.\n\n## 의존성 설치\n\n프로젝트 루트에 생성된 `pubspec.yaml`을 업데이트합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n다음을 실행해서 의존성을 설치합니다:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\n`AuthenticationBloc`은 (`AuthenticationRepository`가 노출하는) 인증 상태 변화에\n반응하고 프레젠테이션 레이어에서 반응할 수 있는 상태를 emit합니다.\n\n`AuthenticationBloc` 구현은 `lib/authentication` 안에 있습니다. 인증을 앱\n레이어의 기능으로 취급하기 때문입니다.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\n[VSCode Extension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)이나\n[IntelliJ Plugin](https://plugins.jetbrains.com/plugin/12129-bloc)을 사용하면\nbloc을 자동으로 생성할 수 있습니다.\n\n:::\n\n### authentication_event.dart\n\n`AuthenticationEvent` 인스턴스는 `AuthenticationBloc`의 입력이 되고, 처리되어\n새로운 `AuthenticationState` 인스턴스를 emit하는 데 사용됩니다.\n\n이 앱에서 `AuthenticationBloc`은 두 가지 이벤트에 반응합니다:\n\n- `AuthenticationSubscriptionRequested`: bloc에게 `AuthenticationStatus`\n  스트림을 구독하라고 알리는 초기 이벤트\n- `AuthenticationLogoutPressed`: 사용자 로그아웃 액션을 bloc에 알림\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\n다음으로 `AuthenticationState`를 살펴봅니다.\n\n### authentication_state.dart\n\n`AuthenticationState` 인스턴스는 `AuthenticationBloc`의 출력이 되고 프레젠테이션\n레이어에서 사용됩니다.\n\n`AuthenticationState` 클래스는 세 개의 named constructor가 있습니다:\n\n- `AuthenticationState.unknown()`: bloc이 현재 사용자가 인증됐는지 아닌지 아직\n  모르는 기본 상태.\n\n- `AuthenticationState.authenticated()`: 사용자가 현재 인증된 상태.\n\n- `AuthenticationState.unauthenticated()`: 사용자가 현재 인증되지 않은 상태.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\n`AuthenticationEvent`와 `AuthenticationState` 구현을 봤으니 이제\n`AuthenticationBloc`을 살펴봅니다.\n\n### authentication_bloc.dart\n\n`AuthenticationBloc`은 사용자를 로그인 페이지에서 시작할지 홈 페이지에서\n시작할지 같은 것들을 결정하는 데 사용되는 앱의 인증 상태를 관리합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\n`AuthenticationBloc`은 `AuthenticationRepository`와 `UserRepository` 모두에\n의존하고 초기 상태를 `AuthenticationState.unknown()`으로 정의합니다.\n\n생성자 본문에서 `AuthenticationEvent` 하위 클래스가 해당 이벤트 핸들러에\n매핑됩니다.\n\n`_onSubscriptionRequested` 이벤트 핸들러에서 `AuthenticationBloc`은\n`emit.onEach`를 사용해서 `AuthenticationRepository`의 `status` 스트림을 구독하고\n각 `AuthenticationStatus`에 대한 응답으로 상태를 emit합니다.\n\n`emit.onEach`는 내부적으로 스트림 구독을 생성하고 `AuthenticationBloc`이나\n`status` 스트림이 닫히면 취소를 처리합니다.\n\n`status` 스트림이 에러를 emit하면 `addError`가 에러와 stackTrace를 listen하고\n있는 `BlocObserver`에 전달합니다.\n\n:::caution\n\n`onError`가 생략되면 `status` 스트림의 모든 에러는 처리되지 않은 것으로 간주되어\n`onEach`에서 throw됩니다. 결과적으로 `status` 스트림에 대한 구독이 취소됩니다.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/ko/bloc-concepts/#blocobserver-1)는 특히 분석과 크래시\n리포팅에서 Bloc 이벤트, 에러, 상태 변화를 로깅하는 데 좋습니다.\n\n:::\n\n`status` 스트림이 `AuthenticationStatus.unknown`이나 `unauthenticated`를\nemit하면 해당 `AuthenticationState`가 emit됩니다.\n\n`AuthenticationStatus.authenticated`가 emit되면 `AuthenticationBloc`이\n`UserRepository`를 통해 사용자를 쿼리합니다.\n\n## main.dart\n\n기본 `main.dart`를 다음으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## App\n\n`app.dart`는 전체 앱의 루트 `App` 위젯을 포함합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`app.dart`는 `App`과 `AppView` 두 부분으로 나뉩니다. `App`은 `AppView`가 사용할\n`AuthenticationBloc`을 생성/제공하는 역할을 합니다. 이런 분리를 통해 나중에\n`App`과 `AppView` 위젯 모두 쉽게 테스트할 수 있습니다.\n\n:::\n\n:::note\n\n`RepositoryProvider`는 `AuthenticationRepository`의 단일 인스턴스를 전체 앱에\n제공하는 데 사용되고, 나중에 유용하게 쓰입니다.\n\n:::\n\n기본적으로 `BlocProvider`는 lazy라서 Bloc이 처음 접근될 때까지 `create`를\n호출하지 않습니다. `AuthenticationBloc`은 항상\n(`AuthenticationSubscriptionRequested` 이벤트를 통해) `AuthenticationStatus`\n스트림을 즉시 구독해야 하므로 `lazy: false`를 설정해서 이 동작을 명시적으로 opt\nout합니다.\n\n`AppView`는 `NavigatorState`에 접근하는 데 사용되는 `GlobalKey`를 유지하기\n때문에 `StatefulWidget`입니다. 기본적으로 `AppView`는 `SplashPage`(나중에\n살펴봄)를 렌더링하고 `BlocListener`를 사용해서 `AuthenticationState` 변화에 따라\n다른 페이지로 이동합니다.\n\n## Splash\n\nsplash 기능은 앱이 시작될 때 사용자가 인증됐는지 판단하는 동안 렌더링될 간단한\n뷰만 포함합니다.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage`는 static `Route`를 노출해서\n`Navigator.of(context).push(SplashPage.route())`를 통해 쉽게 이동할 수 있습니다.\n\n:::\n\n## Login\n\nlogin 기능은 `LoginPage`, `LoginForm`, `LoginBloc`을 포함하고 사용자가 앱에\n로그인하기 위해 username과 password를 입력할 수 있게 합니다.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### Login Models\n\n[`package:formz`](https://pub.dev/packages/formz)를 사용해서 `username`과\n`password`에 대한 재사용 가능하고 표준적인 모델을 만듭니다.\n\n#### Username\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\n단순화를 위해 username이 비어있지 않은지만 검증하지만, 실제로는 특수 문자 사용,\n길이 등을 적용할 수 있습니다.\n\n#### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\n마찬가지로 password가 비어있지 않은지 간단히 확인합니다.\n\n#### Models Barrel\n\n이전처럼 단일 import로 `Username`과 `Password` 모델을 쉽게 가져올 수 있도록\n`models.dart` barrel이 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\n`LoginBloc`은 `LoginForm`의 상태를 관리하고 username과 password 입력 유효성\n검사와 폼 상태를 처리합니다.\n\n#### login_event.dart\n\n이 앱에는 세 가지 `LoginEvent` 타입이 있습니다:\n\n- `LoginUsernameChanged`: username이 수정됐음을 bloc에 알림.\n- `LoginPasswordChanged`: password가 수정됐음을 bloc에 알림.\n- `LoginSubmitted`: 폼이 submit됐음을 bloc에 알림.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\n`LoginState`는 폼의 상태와 username, password 입력 상태를 포함합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\n`Username`과 `Password` 모델은 `LoginState`의 일부로 사용되고 status도\n[package:formz](https://pub.dev/packages/formz)의 일부입니다.\n\n:::\n\n#### login_bloc.dart\n\n`LoginBloc`은 `LoginForm`의 사용자 상호작용에 반응하고 폼의 유효성 검사와\nsubmit을 처리합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\n`LoginBloc`은 폼이 submit될 때 `logIn`을 호출하므로 `AuthenticationRepository`에\n의존합니다. bloc의 초기 상태는 `pure`로, 입력이나 폼이 아직 터치되거나\n상호작용되지 않은 상태입니다.\n\n`username`이나 `password`가 바뀔 때마다 bloc은 `Username`/`Password` 모델의\ndirty variant를 생성하고 `Formz.validate` API를 통해 폼 상태를 업데이트합니다.\n\n`LoginSubmitted` 이벤트가 추가되면 폼의 현재 상태가 valid인 경우 bloc이\n`logIn`을 호출하고 요청 결과에 따라 상태를 업데이트합니다.\n\n다음으로 `LoginPage`와 `LoginForm`을 살펴봅니다.\n\n### Login Page\n\n`LoginPage`는 `Route`를 노출하고 `LoginBloc`을 생성해서 `LoginForm`에 제공하는\n역할을 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\n`context.read<AuthenticationRepository>()`를 사용해서 `BuildContext`를 통해\n`AuthenticationRepository` 인스턴스를 찾습니다.\n\n:::\n\n### Login Form\n\n`LoginForm`은 사용자 이벤트를 `LoginBloc`에 알리고 `BlocBuilder`와\n`BlocListener`를 사용해서 상태 변화에 반응합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`BlocListener`는 로그인 submit이 실패하면 `SnackBar`를 표시하는 데 사용됩니다.\n또한 `context.select`를 사용해서 각 위젯이 `LoginState`의 특정 부분에 효율적으로\n접근하여 불필요한 rebuild를 방지합니다. `onChanged` 콜백은 username/password\n변경을 `LoginBloc`에 알리는 데 사용됩니다.\n\n`_LoginButton` 위젯은 폼의 상태가 valid인 경우에만 활성화되고, 폼이 submit되는\n동안에는 `CircularProgressIndicator`가 대신 표시됩니다.\n\n## Home\n\n성공적인 `logIn` 요청 시 `AuthenticationBloc`의 상태가 `authenticated`로 바뀌고\n사용자는 user의 `id`와 로그아웃 버튼이 표시되는 `HomePage`로 이동합니다.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### Home Page\n\n`HomePage`는 `context.select((AuthenticationBloc bloc) => bloc.state.user.id)`를\n통해 현재 user id에 접근하고 `Text` 위젯을 통해 표시합니다. 또한 logout 버튼이\n탭되면 `AuthenticationBloc`에 `AuthenticationLogoutPressed` 이벤트가 추가됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)`는 user id가\n바뀌면 업데이트를 트리거합니다.\n\n:::\n\n이 시점에서 꽤 괜찮은 로그인 구현이 있고, Bloc을 사용해서 프레젠테이션 레이어와\n비즈니스 로직 레이어를 분리했습니다.\n\n이 예제의 전체 소스 코드(단위 테스트와 위젯 테스트 포함)는\n[여기](https://github.com/felangel/Bloc/tree/master/examples/flutter_login)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: Flutter Timer\ndescription: Bloc을 사용한 Flutter 타이머 앱 만들기 튜토리얼입니다.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\n이 튜토리얼에서는 bloc 라이브러리를 사용해서 타이머 앱을 만들어 봅니다. 완성된\n앱은 다음과 같습니다:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## 핵심 주제\n\n- [BlocObserver](/ko/bloc-concepts#blocobserver)로 상태 변화 관찰하기.\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider)로 하위 위젯에 bloc\n  제공하기.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder)로 상태 변화에 따라 위젯\n  다시 그리기.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)로 불필요한 rebuild\n  방지하기.\n- Bloc에서 `StreamSubscription` 사용하기.\n- `buildWhen`으로 불필요한 rebuild 방지하기.\n\n## 프로젝트 설정\n\n새로운 Flutter 프로젝트를 생성합니다:\n\n<FlutterCreateSnippet />\n\npubspec.yaml 파일을 아래 내용으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\n이 앱에서는 [flutter_bloc](https://pub.dev/packages/flutter_bloc)과\n[equatable](https://pub.dev/packages/equatable) 패키지를 사용합니다.\n\n:::\n\n`flutter pub get`을 실행해서 의존성을 설치합니다.\n\n## 프로젝트 구조\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## Ticker\n\nTicker는 타이머 앱의 데이터 소스입니다. 구독하고 반응할 수 있는 tick 스트림을\n제공합니다.\n\n`ticker.dart` 파일을 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\n`Ticker` 클래스는 원하는 tick 수(초)를 받아서 매초마다 남은 시간을 emit하는\n스트림을 반환하는 tick 함수를 제공합니다.\n\n다음으로 `Ticker`를 사용하는 `TimerBloc`을 만들어야 합니다.\n\n## Timer Bloc\n\n### TimerState\n\n먼저 `TimerBloc`이 가질 수 있는 `TimerState`를 정의합니다.\n\n`TimerBloc`의 상태는 다음 중 하나입니다:\n\n- `TimerInitial`: 지정된 시간부터 카운트다운을 시작할 준비가 된 상태.\n- `TimerRunInProgress`: 지정된 시간부터 카운트다운 중인 상태.\n- `TimerRunPause`: 남은 시간에서 일시 정지된 상태.\n- `TimerRunComplete`: 남은 시간이 0으로 완료된 상태.\n\n각 상태는 UI와 사용자가 수행할 수 있는 액션에 영향을 줍니다. 예를 들어:\n\n- `TimerInitial` 상태면 사용자가 타이머를 시작할 수 있습니다.\n- `TimerRunInProgress` 상태면 사용자가 타이머를 일시 정지하고 리셋할 수 있으며,\n  남은 시간을 볼 수 있습니다.\n- `TimerRunPause` 상태면 사용자가 타이머를 재개하고 리셋할 수 있습니다.\n- `TimerRunComplete` 상태면 사용자가 타이머를 리셋할 수 있습니다.\n\nbloc 파일들을 한곳에 모아두기 위해 bloc 디렉토리에 `bloc/timer_state.dart`를\n생성합니다.\n\n:::tip\n\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator)나\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n확장을 사용하면 bloc 파일을 자동으로 생성할 수 있습니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\n모든 `TimerState`는 duration 속성을 가진 추상 클래스 `TimerState`를 상속합니다.\n`TimerBloc`이 어떤 상태에 있든 남은 시간을 알아야 하기 때문입니다. 또한\n`TimerState`는 `Equatable`을 상속해서 동일한 상태가 발생했을 때 불필요한\nrebuild를 방지합니다.\n\n다음으로 `TimerBloc`이 처리할 `TimerEvent`를 정의하고 구현합니다.\n\n### TimerEvent\n\n`TimerBloc`은 다음 이벤트를 처리해야 합니다:\n\n- `TimerStarted`: 타이머를 시작해야 함을 알립니다.\n- `TimerPaused`: 타이머를 일시 정지해야 함을 알립니다.\n- `TimerResumed`: 타이머를 재개해야 함을 알립니다.\n- `TimerReset`: 타이머를 원래 상태로 리셋해야 함을 알립니다.\n- `_TimerTicked`: tick이 발생했고 그에 따라 상태를 업데이트해야 함을 알립니다.\n\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator)나\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n확장을 사용하지 않았다면 `bloc/timer_event.dart`를 생성하고 이벤트를 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\n다음으로 `TimerBloc`을 구현합니다!\n\n### TimerBloc\n\n아직 하지 않았다면 `bloc/timer_bloc.dart`를 생성하고 빈 `TimerBloc`을 만듭니다.\n\n<TimerBlocEmptySnippet />\n\n먼저 `TimerBloc`의 초기 상태를 정의해야 합니다. 여기서는 `TimerBloc`이\n1분(60초)의 기본 시간으로 `TimerInitial` 상태에서 시작하도록 합니다.\n\n<TimerBlocInitialStateSnippet />\n\n다음으로 `Ticker` 의존성을 정의합니다.\n\n<TimerBlocTickerSnippet />\n\n`Ticker`에 대한 `StreamSubscription`도 정의합니다. 이건 잠시 후에 다룹니다.\n\n이제 이벤트 핸들러만 구현하면 됩니다. 가독성을 위해 각 이벤트 핸들러를 별도의\n헬퍼 함수로 분리합니다. `TimerStarted` 이벤트부터 시작합니다.\n\n<TimerBlocOnStartedSnippet />\n\n`TimerBloc`이 `TimerStarted` 이벤트를 받으면 시작 시간과 함께\n`TimerRunInProgress` 상태를 push합니다. 이미 열린 `_tickerSubscription`이 있다면\n메모리 해제를 위해 취소해야 합니다. 또한 `TimerBloc`이 닫힐 때\n`_tickerSubscription`을 취소하도록 `close` 메서드를 override해야 합니다.\n마지막으로 `_ticker.tick` 스트림을 listen하고 매 tick마다 남은 시간과 함께\n`_TimerTicked` 이벤트를 추가합니다.\n\n다음으로 `_TimerTicked` 이벤트 핸들러를 구현합니다.\n\n<TimerBlocOnTickedSnippet />\n\n`_TimerTicked` 이벤트를 받을 때마다 tick의 duration이 0보다 크면 새로운\nduration과 함께 `TimerRunInProgress` 상태를 push합니다. 그렇지 않고 tick의\nduration이 0이면 타이머가 끝난 것이므로 `TimerRunComplete` 상태를 push합니다.\n\n이제 `TimerPaused` 이벤트 핸들러를 구현합니다.\n\n<TimerBlocOnPausedSnippet />\n\n`_onPaused`에서 `TimerBloc`의 `state`가 `TimerRunInProgress`이면\n`_tickerSubscription`을 일시 정지하고 현재 타이머 duration과 함께\n`TimerRunPause` 상태를 push합니다.\n\n다음으로 타이머를 재개할 수 있도록 `TimerResumed` 이벤트 핸들러를 구현합니다.\n\n<TimerBlocOnResumedSnippet />\n\n`TimerResumed` 이벤트 핸들러는 `TimerPaused` 이벤트 핸들러와 매우 비슷합니다.\n`TimerBloc`의 `state`가 `TimerRunPause`이고 `TimerResumed` 이벤트를 받으면\n`_tickerSubscription`을 재개하고 현재 duration과 함께 `TimerRunInProgress`\n상태를 push합니다.\n\n마지막으로 `TimerReset` 이벤트 핸들러를 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\n`TimerBloc`이 `TimerReset` 이벤트를 받으면 추가 tick 알림을 받지 않도록 현재\n`_tickerSubscription`을 취소하고 원래 duration과 함께 `TimerInitial` 상태를\npush합니다.\n\n`TimerBloc`은 이게 전부입니다. 이제 타이머 앱의 UI만 구현하면 됩니다.\n\n## 앱 UI\n\n### MyApp\n\n`main.dart`의 내용을 삭제하고 다음으로 교체합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n다음으로 앱의 루트가 될 'App' 위젯을 `app.dart`에 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n다음으로 `Timer` 위젯을 구현합니다.\n\n### Timer\n\n`Timer` 위젯(`lib/timer/view/timer_page.dart`)은 남은 시간을 표시하고 사용자가\n타이머를 시작, 일시 정지, 리셋할 수 있는 버튼을 제공합니다.\n\n<TimerPageSnippet />\n\n여기서는 `BlocProvider`를 사용해서 `TimerBloc` 인스턴스에 접근합니다.\n\n다음으로 적절한 액션(시작, 일시 정지, 리셋)을 가진 `Actions` 위젯을 구현합니다.\n\n### Barrel\n\n`Timer` 섹션의 import를 깔끔하게 정리하기 위해 barrel 파일 `timer/timer.dart`를\n생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### Actions\n\n<ActionsSnippet />\n\n`Actions` 위젯은 `BlocBuilder`를 사용해서 새로운 `TimerState`가 올 때마다 UI를\nrebuild하는 `StatelessWidget`입니다. `Actions`는 `context.read<TimerBloc>()`을\n사용해서 `TimerBloc` 인스턴스에 접근하고 `TimerBloc`의 현재 상태에 따라 다른\n`FloatingActionButton`을 반환합니다. 각 `FloatingActionButton`은 `onPressed`\n콜백에서 `TimerBloc`에 알리기 위해 이벤트를 추가합니다.\n\n`builder` 함수가 호출되는 시점을 세밀하게 제어하고 싶다면 `BlocBuilder`에\n선택적으로 `buildWhen`을 제공할 수 있습니다. `buildWhen`은 이전 bloc 상태와 현재\nbloc 상태를 받아서 `boolean`을 반환합니다. `buildWhen`이 `true`를 반환하면\n`state`와 함께 `builder`가 호출되고 위젯이 rebuild됩니다. `buildWhen`이\n`false`를 반환하면 `state`와 함께 `builder`가 호출되지 않고 rebuild도 일어나지\n않습니다.\n\n이 경우 매 tick마다 `Actions` 위젯이 rebuild되는 것은 비효율적이므로 원하지\n않습니다. 대신 `TimerState`의 `runtimeType`이 바뀔 때만 `Actions`가 rebuild되길\n원합니다 (TimerInitial => TimerRunInProgress, TimerRunInProgress =>\nTimerRunPause 등).\n\n결과적으로 매 rebuild마다 위젯에 랜덤 색상을 칠하면 다음과 같이 보입니다:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\n`Text` 위젯은 매 tick마다 rebuild되지만 `Actions`는 필요할 때만 rebuild됩니다.\n\n:::\n\n### Background\n\n마지막으로 background 위젯을 추가합니다:\n\n<BackgroundSnippet />\n\n### 완성\n\n이게 전부입니다! 이제 필요한 위젯만 효율적으로 rebuild하는 꽤 괜찮은 타이머 앱이\n완성됐습니다.\n\n이 예제의 전체 소스 코드는\n[여기](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Flutter Todos\ndescription: Flutter에서 bloc을 사용하여 todos 앱을 구현하는 심층 가이드.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\n이번 튜토리얼에서는 Bloc 라이브러리를 사용하여 Flutter에서 todos 앱을 만들어\n봅니다.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## 주요 주제\n\n- [Bloc과 Cubit](/ko/bloc-concepts#cubit-vs-bloc)을 사용한 다양한 기능 상태\n  관리.\n- 관심사 분리와 재사용성을 위한 [레이어드 아키텍처](/ko/architecture).\n- 상태 변화를 관찰하기 위한 [BlocObserver](/ko/bloc-concepts#blocobserver).\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider), 자식에게 bloc을\n  제공하는 Flutter 위젯.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder), 새로운 상태에 따라\n  위젯을 빌드하는 Flutter 위젯.\n- [BlocListener](/ko/flutter-bloc-concepts#bloclistener), 상태 변화에 따라\n  사이드 이펙트를 수행하는 Flutter 위젯.\n- [RepositoryProvider](/ko/flutter-bloc-concepts#repositoryprovider), 자식에게\n  리포지토리를 제공하는 Flutter 위젯.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)을 사용한 불필요한\n  리빌드 방지.\n- [MultiBlocListener](/ko/flutter-bloc-concepts#multibloclistener), 여러\n  BlocListener 사용 시 중첩을 줄이는 Flutter 위젯.\n\n## 설정\n\n[very_good_cli](https://pub.dev/packages/very_good_cli)를 사용하여 새 Flutter\n프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\n:::note\n\n다음 명령으로 `very_good_cli`를 설치할 수 있습니다\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\n다음으로 `very_good_cli`를 사용하여 `todos_api`, `local_storage_todos_api`,\n`todos_repository` 패키지를 생성합니다:\n\n<FlutterCreatePackagesSnippet />\n\n그런 다음 `pubspec.yaml`의 내용을 다음으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n마지막으로 모든 의존성을 설치합니다:\n\n<VeryGoodPackagesGetSnippet />\n\n## 프로젝트 구조\n\n애플리케이션의 프로젝트 구조는 다음과 같습니다:\n\n<ProjectStructureSnippet />\n\n프로젝트를 여러 패키지로 분리하여 각 패키지가 명확한 경계와 함께 명시적 의존성을\n갖도록 합니다. 이는\n[단일 책임 원칙](https://en.wikipedia.org/wiki/Single-responsibility_principle)을\n따릅니다. 이렇게 프로젝트를 모듈화하면 다음과 같은 이점이 있습니다:\n\n- 여러 프로젝트에서 패키지를 쉽게 재사용\n- CI/CD 효율성 향상 (변경된 코드에 대해서만 검사 실행)\n- 전용 테스트 스위트, 시맨틱 버저닝, 릴리스 주기로 패키지를 독립적으로 유지보수\n  가능\n\n## 아키텍처\n\n![Todos Architecture Diagram](~/assets/tutorials/todos-architecture.png)\n\n코드를 레이어로 분리하는 것은 매우 중요하며 빠르고 자신감 있게 반복 작업을 할 수\n있게 해줍니다. 각 레이어는 단일 책임을 가지며 독립적으로 사용하고 테스트할 수\n있습니다. 이를 통해 변경 사항을 특정 레이어에 한정하여 전체 애플리케이션에\n미치는 영향을 최소화할 수 있습니다. 또한 애플리케이션을 레이어로 분리하면 여러\n프로젝트에서 라이브러리를 쉽게 재사용할 수 있습니다(특히 데이터 레이어).\n\n애플리케이션은 세 가지 주요 레이어로 구성됩니다:\n\n- 데이터 레이어\n- 도메인 레이어\n- 기능 레이어\n  - 프레젠테이션/UI (위젯)\n  - 비즈니스 로직 (bloc/cubit)\n\n**데이터 레이어**\n\n이 레이어는 가장 낮은 레이어로 데이터베이스, API 등의 외부 소스에서 원시\n데이터를 가져오는 역할을 합니다. 데이터 레이어의 패키지는 일반적으로 UI에\n의존하지 않으며 [pub.dev](https://pub.dev)에 독립적인 패키지로 재사용하거나\n게시할 수 있습니다. 이 예제에서 데이터 레이어는 `todos_api`와\n`local_storage_todos_api` 패키지로 구성됩니다.\n\n**도메인 레이어**\n\n이 레이어는 하나 이상의 데이터 프로바이더를 결합하고 데이터에 \"비즈니스 규칙\"을\n적용합니다. 이 레이어의 각 컴포넌트는 리포지토리라고 하며, 각 리포지토리는\n일반적으로 단일 도메인을 관리합니다. 리포지토리 레이어의 패키지는 일반적으로\n데이터 레이어와만 상호작용해야 합니다. 이 예제에서 리포지토리 레이어는\n`todos_repository` 패키지로 구성됩니다.\n\n**기능 레이어**\n\n이 레이어는 애플리케이션별 기능과 유스케이스를 포함합니다. 각 기능은 일반적으로\nUI와 비즈니스 로직으로 구성됩니다. 기능은 일반적으로 다른 기능과 독립적이어야\n하며, 나머지 코드베이스에 영향을 주지 않고 쉽게 추가/제거할 수 있어야 합니다. 각\n기능 내에서 기능의 상태와 비즈니스 로직은 bloc으로 관리됩니다. Bloc은 0개 이상의\n리포지토리와 상호작용합니다. Bloc은 이벤트에 반응하고 UI 변경을 트리거하는\n상태를 방출합니다. 각 기능 내의 위젯은 일반적으로 해당 bloc에만 의존하고 현재\n상태를 기반으로 UI를 렌더링해야 합니다. UI는 이벤트를 통해 bloc에 사용자 입력을\n알릴 수 있습니다. 이 예제에서 애플리케이션은 `home`, `todos_overview`, `stats`,\n`edit_todos` 기능으로 구성됩니다.\n\n레이어에 대한 개요를 살펴봤으니 이제 데이터 레이어부터 시작하여 애플리케이션을\n만들어 봅시다!\n\n## 데이터 레이어\n\n데이터 레이어는 애플리케이션의 가장 낮은 레이어로 원시 데이터 프로바이더로\n구성됩니다. 이 레이어의 패키지는 주로 데이터가 어디서/어떻게 오는지에 관심을\n둡니다. 이 경우 데이터 레이어는 인터페이스인 `TodosApi`와 `shared_preferences`를\n기반으로 `TodosApi`를 구현한 `LocalStorageTodosApi`로 구성됩니다.\n\n### TodosApi\n\n`todos_api` 패키지는 todos와 상호작용/관리하기 위한 제네릭 인터페이스를\n내보냅니다. 나중에 `shared_preferences`를 사용하여 `TodosApi`를 구현할 것입니다.\n추상화를 통해 애플리케이션의 다른 부분을 변경하지 않고도 다른 구현을 쉽게 지원할\n수 있습니다. 예를 들어, 나중에 `shared_preferences` 대신 `cloud_firestore`를\n사용하는 `FirestoreTodosApi`를 최소한의 코드 변경으로 추가할 수 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### Todo 모델\n\n다음으로 `Todo` 모델을 정의합니다.\n\n첫 번째로 주목할 점은 `Todo` 모델이 앱에 있지 않다는 것입니다 — `todos_api`\n패키지의 일부입니다. 이는 `TodosApi`가 `Todo` 객체를 반환/수락하는 API를\n정의하기 때문입니다. 모델은 저장/검색될 원시 Todo 객체의 Dart 표현입니다.\n\n`Todo` 모델은 json (역)직렬화를 처리하기 위해\n[json_serializable](https://pub.dev/packages/json_serializable)을 사용합니다.\n따라 하는 경우 컴파일러 오류를 해결하려면\n[코드 생성 단계](https://pub.dev/packages/json_serializable#running-the-code-generator)를\n실행해야 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\n`json_map.dart`는 코드 검사 및 린팅을 위한 `typedef`를 제공합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\n`Todo` 모델은 `todos_api/models/todo.dart`에 정의되어 있으며\n`package:todos_api/todos_api.dart`에서 내보냅니다.\n\n#### Export 업데이트\n\n`Todo` 모델과 `TodosApi`는 배럴 파일을 통해 내보냅니다. 모델을 직접 임포트하지\n않고 `lib/src/todos_api.dart`에서 패키지 배럴 파일을 참조하여 임포트합니다:\n`import 'package:todos_api/todos_api.dart';`. 나머지 임포트 오류를 해결하기 위해\n배럴 파일을 업데이트합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Stream vs Future\n\n이 튜토리얼의 이전 버전에서는 `TodosApi`가 `Stream` 기반이 아닌 `Future`\n기반이었습니다.\n\n`Future` 기반 API의 예시는\n[Brian Egan의 Architecture Samples 구현](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core)을\n참고하세요.\n\n`Future` 기반 구현은 두 개의 메서드로 구성될 수 있습니다: `loadTodos`와\n`saveTodos`(복수형에 주목). 이는 매번 메서드에 전체 todos 목록을 제공해야 한다는\n의미입니다.\n\n- 이 접근 방식의 한계 중 하나는 표준 CRUD(Create, Read, Update, Delete) 작업이\n  매번 전체 todos 목록을 보내야 한다는 것입니다. 예를 들어, Todo 추가 화면에서\n  추가된 todo 항목만 보낼 수 없습니다. 대신 전체 목록을 추적하고 업데이트된\n  목록을 저장할 때 전체 새 todos 목록을 제공해야 합니다.\n- 두 번째 한계는 `loadTodos`가 일회성 데이터 전달이라는 것입니다. 앱에는\n  주기적으로 업데이트를 요청하는 로직이 포함되어야 합니다.\n\n현재 구현에서 `TodosApi`는 `getTodos()`를 통해 `Stream<List<Todo>>`를 노출하며,\ntodos 목록이 변경되면 모든 구독자에게 실시간 업데이트를 보고합니다.\n\n또한 todos는 개별적으로 생성, 삭제, 업데이트할 수 있습니다. 예를 들어, todo\n삭제와 저장 모두 `todo`만 인자로 사용합니다. 매번 새로 업데이트된 todos 목록을\n제공할 필요가 없습니다.\n\n### LocalStorageTodosApi\n\n이 패키지는 [`shared_preferences`](https://pub.dev/packages/shared_preferences)\n패키지를 사용하여 `todos_api`를 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## 리포지토리 레이어\n\n[리포지토리](/ko/architecture#repository)는 비즈니스 레이어의 일부입니다.\n리포지토리는 비즈니스 가치가 없는 하나 이상의 데이터 프로바이더에 의존하며,\n그들의 공개 API를 비즈니스 가치를 제공하는 API로 결합합니다. 또한 리포지토리\n레이어를 가지면 애플리케이션의 나머지 부분에서 데이터 획득을 추상화하여 앱의\n다른 부분에 영향을 주지 않고 데이터가 어디서/어떻게 저장되는지 변경할 수\n있습니다.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\n리포지토리를 인스턴스화하려면 이 튜토리얼 앞부분에서 논의한 `TodosApi`를\n지정해야 하므로 `pubspec.yaml`에 의존성으로 추가했습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### 라이브러리 Export\n\n`TodosRepository` 클래스 외에도 `todos_api` 패키지의 `Todo` 모델도 내보냅니다.\n이 단계는 애플리케이션과 데이터 프로바이더 간의 긴밀한 결합을 방지합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\n이 경우 데이터 모델을 완전히 제어할 수 있기 때문에 `todos_repository`에 별도의\n모델을 재정의하지 않고 `todos_api`에서 동일한 `Todo` 모델을 재내보내기로\n했습니다. 많은 경우 데이터 프로바이더는 제어할 수 없는 것일 수 있습니다. 그러한\n경우에는 인터페이스와 API 계약을 완전히 제어하기 위해 리포지토리 레이어에서 자체\n모델 정의를 유지하는 것이 더욱 중요해집니다.\n\n## 기능 레이어\n\n### 진입점\n\n앱의 진입점은 `main.dart`입니다. 이 경우 세 가지 버전이 있습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\n가장 주목할 점은 `local_storage_todos_api`의 구체적인 구현이 각 진입점 내에서\n인스턴스화된다는 것입니다.\n\n### 부트스트래핑\n\n`bootstrap.dart`는 `BlocObserver`를 로드하고 `TodosRepository` 인스턴스를\n생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### App\n\n`App`은 모든 자식에게 리포지토리를 제공하는 `RepositoryProvider` 위젯을\n감쌉니다. `EditTodoPage`와 `HomePage` 서브트리 모두 자손이므로 모든 bloc과\ncubit이 리포지토리에 접근할 수 있습니다.\n\n`AppView`는 `MaterialApp`을 생성하고 테마와 로컬라이제이션을 구성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### Theme\n\n라이트 모드와 다크 모드에 대한 테마 정의를 제공합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### Home\n\nhome 기능은 현재 선택된 탭의 상태를 관리하고 올바른 서브트리를 표시하는 역할을\n합니다.\n\n#### HomeState\n\n두 화면인 `todos`와 `stats`에 연결된 두 가지 상태만 있습니다.\n\n:::note\n\n`EditTodo`는 별도의 라우트이므로 `HomeState`의 일부가 아닙니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### HomeCubit\n\n비즈니스 로직이 단순하기 때문에 이 경우 cubit이 적합합니다. 탭을 변경하는\n`setTab` 메서드 하나가 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### HomeView\n\n`view.dart`는 home 기능에 대한 모든 관련 UI 컴포넌트를 내보내는 배럴 파일입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\n`home_page.dart`는 앱이 시작될 때 사용자가 볼 루트 페이지의 UI를 포함합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n`HomePage`의 위젯 트리를 간략하게 나타내면:\n\n<HomePageTreeSnippet />\n\n`HomePage`는 `HomeView`에 `HomeCubit` 인스턴스를 제공합니다. `HomeView`는 탭이\n변경될 때마다 선택적으로 리빌드하기 위해 `context.select`를 사용합니다. 이를\n통해 mock `HomeCubit`을 제공하고 상태를 스텁하여 `HomeView`를 쉽게 위젯 테스트할\n수 있습니다.\n\n`BottomAppBar`는 `HomeCubit`에서 `setTab`을 호출하는 `HomeTabButton` 위젯을\n포함합니다. cubit 인스턴스는 `context.read`를 통해 조회하고 cubit 인스턴스에서\n적절한 메서드가 호출됩니다.\n\n:::caution\n\n`context.read`는 변경사항을 리슨하지 않으며, `HomeCubit`에 접근하고 `setTab`을\n호출하는 데만 사용됩니다.\n\n:::\n\n### TodosOverview\n\ntodos overview 기능을 사용하면 사용자가 todos를 생성, 편집, 삭제 및 필터링하여\n관리할 수 있습니다.\n\n#### TodosOverviewEvent\n\n`todos_overview/bloc/todos_overview_event.dart`를 만들고 이벤트를 정의합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: 시작 이벤트입니다. 응답으로 bloc은\n  `TodosRepository`의 todos 스트림을 구독합니다.\n- `TodosOverviewTodoDeleted`: Todo를 삭제합니다.\n- `TodosOverviewTodoCompletionToggled`: todo의 완료 상태를 토글합니다.\n- `TodosOverviewToggleAllRequested`: 모든 todos의 완료를 토글합니다.\n- `TodosOverviewClearCompletedRequested`: 완료된 모든 todos를 삭제합니다.\n- `TodosOverviewUndoDeletionRequested`: 실수로 인한 삭제 등 todo 삭제를 실행\n  취소합니다.\n- `TodosOverviewFilterChanged`: `TodosViewFilter`를 인자로 받아 필터를 적용하여\n  뷰를 변경합니다.\n\n#### TodosOverviewState\n\n`todos_overview/bloc/todos_overview_state.dart`를 만들고 상태를 정의합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState`는 todos 목록, 활성 필터, `lastDeletedTodo`, 상태를\n추적합니다.\n\n:::note\n\n기본 getter와 setter 외에도 `filteredTodos`라는 커스텀 getter가 있습니다. UI는\n`BlocBuilder`를 사용하여 `state.filteredTodos` 또는 `state.todos`에 접근합니다.\n\n:::\n\n#### TodosOverviewBloc\n\n`todos_overview/bloc/todos_overview_bloc.dart`를 만듭니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nbloc은 내부적으로 `TodosRepository` 인스턴스를 생성하지 않습니다. 대신 생성자를\n통해 리포지토리 인스턴스가 주입되는 것에 의존합니다.\n\n:::\n\n##### onSubscriptionRequested\n\n`TodosOverviewSubscriptionRequested`가 추가되면 bloc은 먼저 `loading` 상태를\n방출합니다. 응답으로 UI는 로딩 인디케이터를 렌더링할 수 있습니다.\n\n다음으로 `emit.forEach<List<Todo>>( ... )`를 사용하여 `TodosRepository`의 todos\n스트림에 대한 구독을 생성합니다.\n\n:::caution\n\n`emit.forEach()`는 리스트에서 사용하는 `forEach()`와 다릅니다. 이 `forEach`는\nbloc이 `Stream`을 구독하고 스트림의 각 업데이트마다 새 상태를 방출할 수 있게\n합니다.\n\n:::\n\n:::note\n\n이 튜토리얼에서는 `stream.listen`을 직접 호출하지 않습니다.\n`await emit.forEach()`는 bloc이 내부적으로 구독을 관리할 수 있게 하는 새로운\n패턴입니다.\n\n:::\n\n이제 구독이 처리되었으니 todos 추가, 수정, 삭제와 같은 다른 이벤트를 처리합니다.\n\n##### onTodoSaved\n\n`_onTodoSaved`는 단순히 `_todosRepository.saveTodo(event.todo)`를 호출합니다.\n\n:::note\n\n`onTodoSaved`와 다른 많은 이벤트 핸들러 내에서 `emit`이 호출되지 않습니다. 대신\n리포지토리에 알리고 리포지토리가 todos 스트림을 통해 업데이트된 목록을\n방출합니다. 자세한 내용은 [데이터 흐름](#데이터-흐름) 섹션을 참조하세요.\n\n:::\n\n##### 실행 취소\n\n실행 취소 기능을 통해 사용자는 마지막으로 삭제된 항목을 복원할 수 있습니다.\n\n`_onTodoDeleted`는 두 가지를 수행합니다. 먼저 삭제할 `Todo`와 함께 새 상태를\n방출합니다. 그런 다음 리포지토리 호출을 통해 `Todo`를 삭제합니다.\n\n`_onUndoDeletionRequested`는 UI에서 삭제 취소 요청 이벤트가 올 때 실행됩니다.\n\n`_onUndoDeletionRequested`는 다음을 수행합니다:\n\n- 마지막으로 삭제된 todo의 복사본을 임시 저장합니다.\n- `lastDeletedTodo`를 제거하여 상태를 업데이트합니다.\n- 삭제를 되돌립니다.\n\n##### 필터링\n\n`_onFilterChanged`는 새 이벤트 필터와 함께 새 상태를 방출합니다.\n\n#### Models\n\n뷰 필터링을 다루는 모델 파일이 하나 있습니다.\n\n`todos_view_filter.dart`는 세 가지 뷰 필터를 나타내는 enum과 필터를 적용하는\n메서드입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart`는 내보내기를 위한 배럴 파일입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\n다음으로 `TodosOverviewPage`를 살펴봅시다.\n\n#### TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\n`TodosOverviewPage`의 위젯 트리를 간략하게 나타내면:\n\n<TodosOverviewPageTreeSnippet />\n\n`Home` 기능과 마찬가지로 `TodosOverviewPage`는\n`BlocProvider<TodosOverviewBloc>`을 통해 서브트리에 `TodosOverviewBloc`\n인스턴스를 제공합니다. 이는 `TodosOverviewBloc`의 범위를 `TodosOverviewPage`\n아래의 위젯으로만 한정합니다.\n\n`TodosOverviewBloc`의 변경사항을 리슨하는 세 개의 위젯이 있습니다.\n\n1. 첫 번째는 에러를 리슨하는 `BlocListener`입니다. `listener`는 `listenWhen`이\n   `true`를 반환할 때만 호출됩니다. 상태가 `TodosOverviewStatus.failure`이면\n   `SnackBar`가 표시됩니다.\n\n2. 삭제를 리슨하는 두 번째 `BlocListener`를 만들었습니다. todo가 삭제되면 실행\n   취소 버튼이 있는 `SnackBar`가 표시됩니다. 사용자가 실행 취소를 탭하면\n   `TodosOverviewUndoDeletionRequested` 이벤트가 bloc에 추가됩니다.\n\n3. 마지막으로 `BlocBuilder`를 사용하여 todos를 표시하는 ListView를 빌드합니다.\n\n`AppBar`에는 todos를 필터링하고 조작하기 위한 드롭다운인 두 가지 액션이\n있습니다.\n\n:::note\n\n`TodosOverviewTodoCompletionToggled`와 `TodosOverviewTodoDeleted`는\n`context.read`를 통해 bloc에 추가됩니다.\n\n:::\n\n`view.dart`는 `todos_overview_page.dart`를 내보내는 배럴 파일입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Widgets\n\n`widgets.dart`는 `todos_overview` 기능 내에서 사용되는 모든 컴포넌트를 내보내는\n또 다른 배럴 파일입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart`는 각 todo 항목의 `ListTile`입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart`는 todos를 조작하기 위한 두 가지 옵션을\n노출합니다:\n\n- `toggleAll`\n- `clearCompleted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart`는 세 가지 필터 옵션을 노출합니다:\n\n- `all`\n- `activeOnly`\n- `completedOnly`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### Stats\n\nstats 기능은 활성 및 완료된 todos에 대한 통계를 표시합니다.\n\n#### StatsState\n\n`StatsState`는 요약 정보와 현재 `StatsStatus`를 추적합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### StatsEvent\n\n`StatsEvent`에는 `StatsSubscriptionRequested`라는 하나의 이벤트만 있습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### StatsBloc\n\n`StatsBloc`은 `TodosOverviewBloc`과 마찬가지로 `TodosRepository`에 의존합니다.\n`_todosRepository.getTodos`를 통해 todos 스트림을 구독합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### Stats View\n\n`view.dart`는 `stats_page`를 위한 배럴 파일입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart`는 todos 통계를 표시하는 페이지의 UI를 포함합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\n`StatsPage`의 위젯 트리를 간략하게 나타내면:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\n`TodosOverviewBloc`과 `StatsBloc`은 모두 `TodosRepository`와 통신하지만, bloc\n간에 직접적인 통신은 없다는 점에 유의해야 합니다. 자세한 내용은\n[데이터 흐름](#데이터-흐름) 섹션을 참조하세요.\n\n:::\n\n### EditTodo\n\n`EditTodo` 기능을 통해 사용자는 기존 todo 항목을 편집하고 변경사항을 저장할 수\n있습니다.\n\n#### EditTodoState\n\n`EditTodoState`는 todo를 편집할 때 필요한 정보를 추적합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### EditTodoEvent\n\nbloc이 반응할 다양한 이벤트는 다음과 같습니다:\n\n- `EditTodoTitleChanged`\n- `EditTodoDescriptionChanged`\n- `EditTodoSubmitted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc`은 `TodosOverviewBloc` 및 `StatsBloc`과 마찬가지로\n`TodosRepository`에 의존합니다.\n\n:::caution\n\n다른 Bloc과 달리 `EditTodoBloc`은 `_todosRepository.getTodos`를 구독하지\n않습니다. 리포지토리에서 정보를 읽을 필요가 없는 \"쓰기 전용\" bloc입니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### 데이터 흐름\n\n동일한 todos 목록에 의존하는 많은 기능이 있지만, bloc 간 통신은 없습니다. 대신\n모든 기능은 서로 독립적이며 todos 목록의 변경사항을 리슨하고 목록을 업데이트하기\n위해 `TodosRepository`에 의존합니다.\n\n예를 들어, `EditTodos`는 `TodosOverview`나 `Stats` 기능에 대해 아무것도\n모릅니다.\n\nUI가 `EditTodoSubmitted` 이벤트를 제출하면:\n\n- `EditTodoBloc`이 `TodosRepository`를 업데이트하는 비즈니스 로직을 처리합니다.\n- `TodosRepository`가 `TodosOverviewBloc`과 `StatsBloc`에 알립니다.\n- `TodosOverviewBloc`과 `StatsBloc`이 새 상태로 업데이트하는 UI에 알립니다.\n\n#### EditTodoPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\n이전 기능과 마찬가지로 `EditTodosPage`는 `BlocProvider`를 통해 `EditTodosBloc`\n인스턴스를 제공합니다. 다른 기능과 달리 `EditTodosPage`는 별도의 라우트이므로\n`static` `route` 메서드를 노출합니다. 이를 통해\n`Navigator.of(context).push(...)`를 통해 네비게이션 스택에 `EditTodosPage`를\n쉽게 푸시할 수 있습니다.\n\n`EditTodosPage`의 위젯 트리를 간략하게 나타내면:\n\n<EditTodosPageTreeSnippet />\n\n## 요약\n\n튜토리얼을 완료했습니다! 🎉\n\n유닛 테스트와 위젯 테스트를 포함한 전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/flutter_todos)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: Flutter Weather\ndescription: Flutter에서 bloc을 사용하여 날씨 앱을 구현하는 심층 가이드.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\n이번 튜토리얼에서는 Flutter로 날씨 앱을 만들어 여러 cubit을 관리하여 동적 테마,\n당겨서 새로고침 등을 구현하는 방법을 보여줍니다. 날씨 앱은 공개 OpenMeteo\nAPI에서 실시간 날씨 데이터를 가져오고 애플리케이션을 레이어(데이터, 리포지토리,\n비즈니스 로직, 프레젠테이션)로 분리하는 방법을 보여줍니다.\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## 프로젝트 요구사항\n\n앱에서 사용자가 할 수 있어야 하는 것:\n\n- 전용 검색 페이지에서 도시 검색\n- [Open Meteo API](https://open-meteo.com)에서 반환된 날씨 데이터를 보기 좋게\n  표시\n- 표시되는 단위 변경 (미터법 vs 야드파운드법)\n\n추가로:\n\n- 애플리케이션의 테마는 선택한 도시의 날씨를 반영해야 함\n- 애플리케이션 상태는 세션 간에 유지되어야 함: 즉, 앱을 닫았다가 다시 열어도\n  상태를 기억해야 함\n  ([HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  사용)\n\n## 주요 개념\n\n- [BlocObserver](/ko/bloc-concepts#blocobserver)로 상태 변화 관찰.\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider), 자식에게 bloc을\n  제공하는 Flutter 위젯.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder), 새로운 상태에 따라\n  위젯을 빌드하는 Flutter 위젯.\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)을 사용한 불필요한\n  리빌드 방지.\n- [RepositoryProvider](/ko/flutter-bloc-concepts#repositoryprovider), 자식에게\n  리포지토리를 제공하는 Flutter 위젯.\n- [BlocListener](/ko/flutter-bloc-concepts#bloclistener), bloc의 상태 변화에\n  따라 리스너 코드를 호출하는 Flutter 위젯.\n- [MultiBlocProvider](/ko/flutter-bloc-concepts#multiblocprovider), 여러\n  BlocProvider 위젯을 하나로 합치는 Flutter 위젯.\n- [BlocConsumer](/ko/flutter-bloc-concepts#blocconsumer), 새로운 상태에 반응하기\n  위해 builder와 listener를 노출하는 Flutter 위젯.\n- 상태를 관리하고 유지하기 위한\n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc).\n\n## 설정\n\n새 flutter 프로젝트를 생성합니다\n\n<FlutterCreateSnippet />\n\n### 프로젝트 구조\n\n앱은 해당 디렉토리에 분리된 기능으로 구성됩니다. 이를 통해 기능 수가 증가함에\n따라 확장할 수 있으며 개발자가 서로 다른 기능을 병렬로 작업할 수 있습니다.\n\n앱은 네 가지 주요 기능으로 나눌 수 있습니다: **search, settings, theme,\nweather**. 해당 디렉토리를 만들어 봅시다.\n\n<FeatureTreeSnippet />\n\n### 아키텍처\n\n[bloc 아키텍처](/ko/architecture) 가이드라인에 따라 애플리케이션은 여러 레이어로\n구성됩니다.\n\n이 튜토리얼에서 각 레이어가 하는 일:\n\n- **데이터**: API에서 원시 날씨 데이터 검색\n- **리포지토리**: 데이터 레이어를 추상화하고 애플리케이션이 사용할 도메인 모델\n  노출\n- **비즈니스 로직**: 각 기능의 상태 관리 (단위 정보, 도시 정보, 테마 등)\n- **프레젠테이션**: 날씨 정보 표시 및 사용자 입력 수집 (설정 페이지, 검색 페이지\n  등)\n\n## 데이터 레이어\n\n이 애플리케이션에서는 [Open Meteo API](https://open-meteo.com)를 사용합니다.\n\n두 가지 엔드포인트에 집중합니다:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` - 주어진\n  도시 이름의 위치 가져오기\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true` -\n  주어진 위치의 날씨 가져오기\n\n브라우저에서\n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)을\n열어 시카고 도시의 응답을 확인하세요. 응답의 `latitude`와 `longitude`를 사용하여\n날씨 엔드포인트를 호출합니다.\n\n시카고의 `latitude`/`longitude`는 `41.85003`/`-87.65005`입니다. 브라우저에서\n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)로\n이동하면 시카고의 날씨 응답을 볼 수 있으며, 앱에 필요한 모든 데이터가 포함되어\n있습니다.\n\n### OpenMeteo API 클라이언트\n\nOpenMeteo API 클라이언트는 애플리케이션과 독립적입니다. 따라서 내부 패키지로\n생성하고 ([pub.dev](https://pub.dev)에 게시할 수도 있음) 리포지토리 레이어의\n`pubspec.yaml`에 추가하여 메인 날씨 애플리케이션의 데이터 요청을 처리합니다.\n\n프로젝트 레벨에 `packages`라는 새 디렉토리를 만듭니다. 이 디렉토리에 모든 내부\n패키지를 저장합니다.\n\n이 디렉토리 내에서 내장된 `flutter create` 명령을 실행하여 API 클라이언트용\n`open_meteo_api`라는 새 패키지를 만듭니다.\n\n<FlutterCreateApiClientSnippet />\n\n### 날씨 데이터 모델\n\n다음으로 `location` 및 `weather` API 엔드포인트 응답에 대한 모델을 포함할\n`location.dart`와 `weather.dart`를 만듭니다.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### Location 모델\n\n`location.dart` 모델은 다음과 같은 location API가 반환하는 데이터를 저장해야\n합니다:\n\n<LocationJsonSnippet />\n\n위 응답을 저장하는 진행 중인 `location.dart` 파일입니다:\n\n<LocationDartSnippet />\n\n#### Weather 모델\n\n다음으로 `weather.dart`를 작업합니다. weather 모델은 다음과 같은 weather API가\n반환하는 데이터를 저장해야 합니다:\n\n<WeatherJsonSnippet />\n\n위 응답을 저장하는 진행 중인 `weather.dart` 파일입니다:\n\n<WeatherDartSnippet />\n\n### 배럴 파일\n\n여기서 임포트를 정리하기 위해\n[배럴 파일](https://adrianfaciu.dev/posts/barrel-files/)을 빠르게 만들어 봅시다.\n\n`models.dart` 배럴 파일을 만들고 두 모델을 내보냅니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\n패키지 레벨 배럴 파일 `open_meteo_api.dart`도 만듭니다\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\n최상위 `open_meteo_api.dart`에서 모델을 내보냅니다:\n\n<OpenMeteoLibrarySnippet />\n\n### 설정\n\nAPI 데이터로 작업하려면 모델을\n[직렬화 및 역직렬화](https://en.wikipedia.org/wiki/Serialization)할 수 있어야\n합니다. 이를 위해 모델에 `toJson` 및 `fromJson` 메서드를 추가합니다.\n\n또한 API에서 데이터를 가져오기 위해\n[HTTP 네트워크 요청을 만드는](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)\n방법이 필요합니다. 다행히 이를 위한 인기 있는 패키지가 여러 개 있습니다.\n\n`toJson` 및 `fromJson` 구현을 생성하기 위해\n[json_annotation](https://pub.dev/packages/json_annotation),\n[json_serializable](https://pub.dev/packages/json_serializable),\n[build_runner](https://pub.dev/packages/build_runner) 패키지를 사용합니다.\n\n이후 단계에서는 [http](https://pub.dev/packages/http) 패키지를 사용하여 weather\nAPI에 네트워크 요청을 보내 애플리케이션이 현재 날씨 데이터를 표시할 수 있도록\n합니다.\n\n이러한 의존성을 `pubspec.yaml`에 추가합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\n의존성을 추가한 후 `flutter pub get`을 실행하는 것을 잊지 마세요.\n\n:::\n\n### (역)직렬화\n\n코드 생성이 작동하려면 다음을 사용하여 코드에 어노테이션을 달아야 합니다:\n\n- `@JsonSerializable` - 직렬화할 수 있는 클래스에 레이블 지정\n- `@JsonKey` - 필드 이름의 문자열 표현 제공\n- `@JsonValue` - 필드 값의 문자열 표현 제공\n- `JSONConverter` 구현 - 객체 표현을 JSON 표현으로 변환\n\n각 파일에 대해 다음도 필요합니다:\n\n- `json_annotation` 임포트\n- [part](https://dart.dev/tools/pub/create-packages#organizing-a-package)\n  키워드를 사용하여 생성된 코드 포함\n- 역직렬화를 위한 `fromJson` 메서드 포함\n\n#### Location 모델\n\n완성된 `location.dart` 모델 파일입니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### Weather 모델\n\n완성된 `weather.dart` 모델 파일입니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### Build 파일 생성\n\n`open_meteo_api` 폴더에 `build.yaml` 파일을 만듭니다. 이 파일의 목적은\n`json_serializable` 필드 이름의 명명 규칙 간 불일치를 처리하는 것입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### 코드 생성\n\n`build_runner`를 사용하여 코드를 생성합니다.\n\n<BuildRunnerBuildSnippet />\n\n`build_runner`가 `location.g.dart`와 `weather.g.dart` 파일을 생성해야 합니다.\n\n### OpenMeteo API 클라이언트\n\n`src` 디렉토리 내에 `open_meteo_api_client.dart`에 API 클라이언트를 만듭니다.\n이제 프로젝트 구조는 다음과 같아야 합니다:\n\n<OpenMeteoApiClientTreeSnippet />\n\n이전에 `pubspec.yaml` 파일에 추가한 [http](https://pub.dev/packages/http)\n패키지를 사용하여 weather API에 HTTP 요청을 하고 이 정보를 애플리케이션에서\n사용할 수 있습니다.\n\nAPI 클라이언트는 두 가지 메서드를 노출합니다:\n\n- `locationSearch` - `Future<Location>` 반환\n- `getWeather` - `Future<Weather>` 반환\n\n#### Location 검색\n\n`locationSearch` 메서드는 location API를 호출하고 해당되는 경우\n`LocationRequestFailure` 에러를 throw합니다. 완성된 메서드는 다음과 같습니다:\n\n<LocationSearchMethodSnippet />\n\n#### 날씨 가져오기\n\n마찬가지로 `getWeather` 메서드는 weather API를 호출하고 해당되는 경우\n`WeatherRequestFailure` 에러를 throw합니다. 완성된 메서드는 다음과 같습니다:\n\n<GetWeatherMethodSnippet />\n\n완성된 파일은 다음과 같습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### 배럴 파일 업데이트\n\n배럴 파일에 API 클라이언트를 추가하여 이 패키지를 마무리합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### 유닛 테스트\n\n데이터 레이어는 애플리케이션의 기반이므로 유닛 테스트를 작성하는 것이 특히\n중요합니다. 유닛 테스트는 패키지가 예상대로 동작한다는 확신을 줍니다.\n\n#### 설정\n\n이전에 pubspec.yaml에 [test](https://pub.dev/packages/test) 패키지를 추가하여\n유닛 테스트를 쉽게 작성할 수 있습니다.\n\nAPI 클라이언트와 두 모델에 대한 테스트 파일을 만들 것입니다.\n\n#### Location 테스트\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### Weather 테스트\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### API 클라이언트 테스트\n\n다음으로 API 클라이언트를 테스트합니다. API 클라이언트가 엣지 케이스를 포함하여\n두 API 호출을 올바르게 처리하는지 테스트해야 합니다.\n\n:::note\n\n우리의 목표는 API 자체가 아닌 API 클라이언트 로직(모든 엣지 케이스 포함)을\n테스트하는 것이므로 테스트에서 실제 API 호출을 하지 않습니다. 일관되고 제어된\n테스트 환경을 갖기 위해 이전에 pubspec.yaml 파일에 추가한\n[mocktail](https://github.com/felangel/mocktail)을 사용하여 `http` 클라이언트를\n모킹합니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### 테스트 커버리지\n\n마지막으로 각 코드 라인이 최소한 하나의 테스트 케이스로 커버되었는지 확인하기\n위해 테스트 커버리지를 수집합니다.\n\n<FlutterTestCoverageSnippet />\n\n## 리포지토리 레이어\n\n리포지토리 레이어의 목표는 데이터 레이어를 추상화하고 bloc 레이어와의 통신을\n용이하게 하는 것입니다. 이렇게 하면 코드베이스의 나머지 부분은 특정 데이터\n프로바이더 구현이 아닌 리포지토리 레이어에서 노출하는 함수에만 의존합니다. 이를\n통해 애플리케이션 레벨 코드를 방해하지 않고 데이터 프로바이더를 변경할 수\n있습니다. 예를 들어, 이 특정 weather API에서 마이그레이션하기로 결정하면\n리포지토리 또는 애플리케이션 레이어의 공개 API를 변경하지 않고도 새 API\n클라이언트를 만들어 교체할 수 있어야 합니다.\n\n### 설정\n\npackages 디렉토리 내에서 다음 명령을 실행합니다:\n\n<FlutterCreateRepositorySnippet />\n\n마지막 단계의 `open_meteo_api` 패키지를 포함하여 `open_meteo_api` 패키지와\n동일한 패키지를 사용합니다. `pubspec.yaml`을 업데이트하고 `flutter pub get`을\n실행합니다.\n\n:::note\n\n`open_meteo_api`의 위치를 지정하기 위해 `path`를 사용하며, 이를 통해 `pub.dev`의\n외부 패키지처럼 취급할 수 있습니다.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### Weather Repository 모델\n\n도메인별 날씨 모델을 노출하기 위해 새 `weather.dart` 파일을 만듭니다. 이 모델은\n비즈니스 케이스와 관련된 데이터만 포함합니다 -- 즉, API 클라이언트 및 원시\n데이터 형식과 완전히 분리되어야 합니다. 평소처럼 `models.dart` 배럴 파일도\n만듭니다.\n\n<RepositoryModelsBarrelTreeSnippet />\n\n이번에는 weather 모델이 `location, temperature, condition` 프로퍼티만\n저장합니다. 직렬화 및 역직렬화를 위해 코드에 어노테이션을 계속 달 것입니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\n이전에 만든 배럴 파일을 업데이트하여 모델을 포함합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### Build 파일 생성\n\n이전과 마찬가지로 다음 내용으로 `build.yaml` 파일을 만들어야 합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### 코드 생성\n\n이전에 했던 것처럼 다음 명령을 실행하여 (역)직렬화 구현을 생성합니다.\n\n<BuildRunnerBuildSnippet />\n\n#### 배럴 파일\n\n모델을 내보내기 위해\n`packages/weather_repository/lib/weather_repository.dart`라는 패키지 레벨 배럴\n파일도 만듭니다:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\n`WeatherRepository`의 주요 목표는 데이터 프로바이더를 추상화하는 인터페이스를\n제공하는 것입니다. 이 경우 `WeatherRepository`는 `WeatherApiClient`에 의존하고\n단일 공개 메서드 `getWeather(String city)`를 노출합니다.\n\n:::note\n\n`WeatherRepository`의 소비자는 날씨 API에 두 번의 네트워크 요청이 이루어진다는\n사실과 같은 기저 구현 세부 사항을 알 수 없습니다. `WeatherRepository`의 목표는\n\"무엇\"과 \"어떻게\"를 분리하는 것입니다 -- 즉, 주어진 도시의 날씨를 가져오는\n방법을 갖고 싶지만, 데이터가 어디서 어떻게 오는지는 신경 쓰지 않습니다.\n\n:::\n\n#### 설정\n\n패키지의 `src` 디렉토리 내에 `weather_repository.dart` 파일을 만들고 리포지토리\n구현을 작업합니다.\n\n집중할 주요 메서드는 `getWeather(String city)`입니다. 다음과 같이 API\n클라이언트에 대한 두 번의 호출을 사용하여 구현할 수 있습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### 배럴 파일\n\n이전에 만든 배럴 파일을 업데이트합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### 유닛 테스트\n\n데이터 레이어와 마찬가지로 도메인 레벨 로직이 올바른지 확인하기 위해 리포지토리\n레이어를 테스트하는 것이 중요합니다. `WeatherRepository`를 테스트하기 위해\n[mocktail](https://github.com/felangel/mocktail) 라이브러리를 사용합니다.\n격리된, 제어된 환경에서 `WeatherRepository` 로직을 유닛 테스트하기 위해 기저 api\n클라이언트를 모킹합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## 비즈니스 로직 레이어\n\n비즈니스 로직 레이어에서는 `WeatherRepository`의 날씨 도메인 모델을 사용하고\nUI를 통해 사용자에게 표시될 기능 레벨 모델을 노출합니다.\n\n:::note\n\n이것이 우리가 구현하는 세 번째 다른 유형의 날씨 모델입니다. API 클라이언트에서\nweather 모델은 API가 반환한 모든 정보를 포함했습니다. 리포지토리 레이어에서\nweather 모델은 비즈니스 케이스를 기반으로 추상화된 모델만 포함했습니다. 이\n레이어에서 weather 모델은 현재 기능 세트에 특별히 필요한 관련 정보를 포함합니다.\n\n:::\n\n### 설정\n\n비즈니스 로직 레이어가 메인 앱에 있으므로 전체 `flutter_weather` 프로젝트의\n`pubspec.yaml`을 편집하고 사용할 모든 패키지를 포함해야 합니다.\n\n- [equatable](https://pub.dev/packages/equatable)을 사용하면 앱의 상태 클래스\n  인스턴스를 equals `==` 연산자를 사용하여 비교할 수 있습니다. 내부적으로 bloc은\n  상태가 같은지 비교하고, 같지 않으면 리빌드를 트리거합니다. 이는 위젯 트리가\n  필요할 때만 리빌드되도록 하여 성능을 빠르고 반응적으로 유지합니다.\n- [google_fonts](https://pub.dev/packages/google_fonts)로 사용자 인터페이스를\n  꾸밀 수 있습니다.\n- [HydratedBloc](https://pub.dev/packages/hydrated_bloc)을 사용하면 앱을\n  닫았다가 다시 열 때 애플리케이션 상태를 유지할 수 있습니다.\n- 방금 만든 `weather_repository` 패키지를 포함하여 현재 날씨 데이터를 가져올 수\n  있습니다!\n\n테스트를 위해 일반적인 `test` 패키지와 의존성 모킹을 위한 `mocktail`, 비즈니스\n로직 단위 또는 bloc의 쉬운 테스트를 가능하게 하는\n[bloc_test](https://pub.dev/packages/bloc_test)를 포함합니다!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n다음으로 `weather` 기능 디렉토리 내의 애플리케이션 레이어를 작업합니다.\n\n### Weather 모델\n\nweather 모델의 목표는 앱에 표시되는 날씨 데이터와 온도 설정(섭씨 또는 화씨)을\n추적하는 것입니다.\n\n`flutter_weather/lib/weather/models/weather.dart`를 만듭니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### Build 파일 생성\n\n비즈니스 로직 레이어를 위한 `build.yaml` 파일을 만듭니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### 코드 생성\n\n`build_runner`를 실행하여 (역)직렬화 구현을 생성합니다.\n\n<BuildRunnerBuildSnippet />\n\n### 배럴 파일\n\n배럴 파일(`flutter_weather/lib/weather/models/models.dart`)에서 모델을\n내보냅니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\n그런 다음 최상위 weather 배럴 파일(`flutter_weather/lib/weather/weather.dart`)을\n만듭니다;\n\n<WeatherBarrelDartSnippet />\n\n### Weather\n\n`HydratedCubit`을 사용하여 앱이 닫았다가 다시 열어도 애플리케이션 상태를 기억할\n수 있게 합니다.\n\n:::note\n\n`HydratedCubit`은 세션 간 상태 유지 및 복원을 처리하는 `Cubit`의 확장입니다.\n\n:::\n\n#### Weather 상태\n\n[Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n또는 [Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) 확장을\n사용하여 `weather` 디렉토리를 우클릭하고 `Weather`라는 새 cubit을 만듭니다.\n프로젝트 구조는 다음과 같아야 합니다:\n\n<WeatherCubitTreeSnippet />\n\n날씨 앱이 있을 수 있는 네 가지 상태:\n\n- `initial` - 아무것도 로드되기 전\n- `loading` - API 호출 중\n- `success` - API 호출 성공\n- `failure` - API 호출 실패\n\n`WeatherStatus` enum이 위의 내용을 나타냅니다.\n\n완성된 weather 상태는 다음과 같습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\n`WeatherState`를 정의했으니 이제 다음 메서드를 노출하는 `WeatherCubit`을\n작성합니다:\n\n- `fetchWeather(String? city)` - weather repository를 사용하여 주어진 도시의\n  weather 객체를 가져오려고 시도\n- `refreshWeather()` - 현재 weather 상태를 기반으로 weather repository를\n  사용하여 새 weather 객체를 가져옴\n- `toggleUnits()` - 섭씨와 화씨 사이에서 상태를 토글\n- `fromJson(Map<String, dynamic> json)`, `toJson(WeatherState state)` - 지속성에\n  사용\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\n다음을 통해 (역)직렬화 코드를 생성하는 것을 잊지 마세요:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### 유닛 테스트\n\n데이터 및 리포지토리 레이어와 마찬가지로 기능 레벨 로직이 예상대로 동작하는지\n확인하기 위해 비즈니스 로직 레이어를 유닛 테스트하는 것이 중요합니다. `mocktail`\n외에도 [bloc_test](https://pub.dev/packages/bloc_test)를 사용합니다.\n\n`test`, `bloc_test`, `mocktail` 패키지를 `dev_dependencies`에 추가합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\n[bloc_test](https://pub.dev/packages/bloc_test) 패키지를 사용하면 bloc을\n테스트용으로 쉽게 준비하고, 상태 변경을 처리하고, 일관된 방식으로 결과를 확인할\n수 있습니다.\n\n:::\n\n#### Weather Cubit 테스트\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## 프레젠테이션 레이어\n\n### Weather 페이지\n\n`WeatherPage`는 `BlocProvider`를 사용하여 `WeatherCubit` 인스턴스를 위젯 트리에\n제공합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\n페이지가 `SettingsPage`와 `SearchPage` 위젯에 의존하는 것을 알 수 있으며, 다음에\n만들 것입니다.\n\n### SettingsPage\n\n설정 페이지에서 사용자는 온도 단위에 대한 기본 설정을 업데이트할 수 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### SearchPage\n\n검색 페이지에서 사용자는 원하는 도시 이름을 입력할 수 있으며\n`Navigator.of(context).pop`을 통해 이전 라우트에 검색 결과를 제공합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Weather 위젯\n\n앱은 `WeatherCubit`의 네 가지 가능한 상태에 따라 다른 화면을 표시합니다.\n\n#### WeatherEmpty\n\n이 화면은 사용자가 아직 도시를 선택하지 않아 표시할 데이터가 없을 때 표시됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### WeatherError\n\n에러가 있을 때 이 화면이 표시됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### WeatherLoading\n\n애플리케이션이 데이터를 가져오는 동안 이 화면이 표시됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### WeatherPopulated\n\n사용자가 도시를 선택하고 데이터를 가져온 후 이 화면이 표시됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### 배럴 파일\n\n임포트를 정리하기 위해 이러한 상태를 배럴 파일에 추가합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### 진입점\n\n`main.dart` 파일은 `WeatherApp`과 `BlocObserver`(디버깅 목적)를 초기화하고 세션\n간 상태를 유지하기 위해 `HydratedStorage`를 설정해야 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n`app.dart` 위젯은 이전에 만든 `WeatherPage` 뷰를 빌드하고 `BlocProvider`를\n사용하여 `WeatherCubit`을 주입합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### 위젯 테스트\n\n[`bloc_test`](https://pub.dev/packages/bloc_test) 라이브러리는 UI를 쉽게\n테스트할 수 있는 `MockBlocs`와 `MockCubits`도 노출합니다. 다양한 cubit의 상태를\n모킹하고 UI가 올바르게 반응하는지 확인할 수 있습니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\n각 테스트 케이스에서 cubit의 상태를 스텁하기 위해 `mocktail`의 `when` API와 함께\n`MockWeatherCubit`을 사용합니다. 이를 통해 모든 상태를 시뮬레이션하고 모든\n상황에서 UI가 올바르게 동작하는지 확인할 수 있습니다.\n\n:::\n\n## 요약\n\n튜토리얼을 완료했습니다! 🎉\n\n`flutter run` 명령을 사용하여 최종 앱을 실행할 수 있습니다.\n\n유닛 테스트와 위젯 테스트를 포함한 전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/tutorials/github-search.mdx",
    "content": "---\ntitle: GitHub 검색\ndescription:\n  Flutter와 AngularDart에서 bloc을 사용하여 GitHub 검색 앱을 구현하는 심층\n  가이드.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\n이번 튜토리얼에서는 Flutter와 AngularDart에서 GitHub 검색 앱을 만들어 두\n프로젝트 간에 데이터 레이어와 비즈니스 로직 레이어를 어떻게 공유할 수 있는지\n보여줍니다.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## 주요 주제\n\n- [BlocProvider](/ko/flutter-bloc-concepts#blocprovider), 자식 위젯에 bloc을\n  제공하는 Flutter 위젯.\n- [BlocBuilder](/ko/flutter-bloc-concepts#blocbuilder), 새로운 상태에 따라\n  위젯을 빌드하는 Flutter 위젯.\n- Bloc 대신 Cubit 사용하기.\n  [차이점은 무엇인가요?](/ko/bloc-concepts#cubit-vs-bloc)\n- [Equatable](/ko/faqs#언제-equatable를-사용해야-하나요)을 사용하여 불필요한\n  리빌드 방지.\n- [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)와 함께 커스텀\n  `EventTransformer` 사용.\n- `http` 패키지를 사용한 네트워크 요청.\n\n## Common GitHub Search 라이브러리\n\nCommon GitHub Search 라이브러리는 AngularDart와 Flutter 간에 공유될 모델, 데이터\n프로바이더, 리포지토리, 그리고 bloc을 포함합니다.\n\n### 설정\n\n먼저 애플리케이션을 위한 새 디렉토리를 만듭니다.\n\n<SetupSnippet />\n\n:::note\n\n`common_github_search` 디렉토리가 공유 라이브러리를 포함할 것입니다.\n\n:::\n\n필요한 의존성이 담긴 `pubspec.yaml`을 생성해야 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\n마지막으로 의존성을 설치합니다.\n\n<DartPubGetSnippet />\n\n프로젝트 설정이 완료되었습니다! 이제 `common_github_search` 패키지를 만들어\n봅시다.\n\n### Github 클라이언트\n\n`GithubClient`는 [GitHub API](https://developer.github.com/v3/)에서 원시\n데이터를 제공합니다.\n\n:::note\n\n반환되는 데이터의 샘플은\n[여기](https://api.github.com/search/repositories?q=dartlang)에서 확인할 수\n있습니다.\n\n:::\n\n`github_client.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\n`GithubClient`는 단순히 Github의 Repository Search API에 네트워크 요청을 하고\n결과를 `SearchResult` 또는 `SearchResultError`로 변환하여 `Future`로 반환합니다.\n\n:::\n\n:::note\n\n`GithubClient` 구현은 아직 구현하지 않은 `SearchResult.fromJson`에 의존합니다.\n\n:::\n\n다음으로 `SearchResult`와 `SearchResultError` 모델을 정의해야 합니다.\n\n#### Search Result 모델\n\n사용자의 쿼리를 기반으로 한 `SearchResultItems` 리스트를 나타내는\n`search_result.dart`를 생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\n`SearchResult` 구현은 아직 구현하지 않은 `SearchResultItem.fromJson`에\n의존합니다.\n\n:::\n\n:::note\n\n모델에서 사용하지 않을 프로퍼티는 포함하지 않습니다.\n\n:::\n\n#### Search Result Item 모델\n\n다음으로 `search_result_item.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\n마찬가지로 `SearchResultItem` 구현은 아직 구현하지 않은 `GithubUser.fromJson`에\n의존합니다.\n\n:::\n\n#### GitHub User 모델\n\n다음으로 `github_user.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\n여기까지 `SearchResult`와 그 의존성 구현을 완료했습니다. 이제\n`SearchResultError`로 넘어갑니다.\n\n#### Search Result Error 모델\n\n`search_result_error.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\n`GithubClient`가 완성되었으니 다음으로 성능 최적화를 위해\n[메모이제이션](https://en.wikipedia.org/wiki/Memoization)을 담당할\n`GithubCache`로 넘어갑니다.\n\n### GitHub Cache\n\n`GithubCache`는 모든 이전 쿼리를 기억하여 GitHub API에 불필요한 네트워크 요청을\n피하는 역할을 합니다. 이를 통해 애플리케이션의 성능도 향상됩니다.\n\n`github_cache.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\n이제 `GithubRepository`를 만들 준비가 되었습니다!\n\n### GitHub Repository\n\nGithub Repository는 데이터 레이어(`GithubClient`)와 비즈니스 로직 레이어(`Bloc`)\n사이에 추상화를 만드는 역할을 합니다. 여기서 `GithubCache`도 사용하게 됩니다.\n\n`github_repository.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\n`GithubRepository`는 `GithubCache`와 `GithubClient`에 의존하며 기저 구현을\n추상화합니다. 애플리케이션은 데이터가 어디서 어떻게 가져와지는지 알 필요가\n없습니다. 인터페이스를 변경하지 않는 한 클라이언트 코드를 변경하지 않고도\n리포지토리의 동작 방식을 언제든지 바꿀 수 있습니다.\n\n:::\n\n여기까지 데이터 프로바이더 레이어와 리포지토리 레이어를 완성했으니 이제 비즈니스\n로직 레이어로 넘어갑니다.\n\n### GitHub Search 이벤트\n\nBloc은 사용자가 리포지토리 이름을 입력하면 알림을 받게 되며, 이를 `TextChanged`\n`GithubSearchEvent`로 나타냅니다.\n\n`github_search_event.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\n`GithubSearchEvent`의 인스턴스를 비교할 수 있도록\n[`Equatable`](https://pub.dev/packages/equatable)을 상속합니다. 기본적으로 동등\n연산자는 this와 other가 동일한 인스턴스인 경우에만 true를 반환합니다.\n\n:::\n\n### Github Search 상태\n\n프레젠테이션 레이어가 적절하게 표시되려면 여러 정보가 필요합니다:\n\n- `SearchStateEmpty` - 사용자가 아무 입력도 하지 않았음을 프레젠테이션 레이어에\n  알립니다.\n\n- `SearchStateLoading` - 로딩 인디케이터를 표시해야 함을 프레젠테이션 레이어에\n  알립니다.\n\n- `SearchStateSuccess` - 표시할 데이터가 있음을 프레젠테이션 레이어에 알립니다.\n\n  - `items` - 표시될 `List<SearchResultItem>`입니다.\n\n- `SearchStateError` - 리포지토리를 가져오는 중 에러가 발생했음을 프레젠테이션\n  레이어에 알립니다.\n\n  - `error` - 발생한 정확한 에러입니다.\n\n이제 `github_search_state.dart`를 생성하고 다음과 같이 구현합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\n`GithubSearchState`의 인스턴스를 비교할 수 있도록\n[`Equatable`](https://pub.dev/packages/equatable)을 상속합니다. 기본적으로 동등\n연산자는 this와 other가 동일한 인스턴스인 경우에만 true를 반환합니다.\n\n:::\n\n이벤트와 상태가 구현되었으니 이제 `GithubSearchBloc`을 만들 수 있습니다.\n\n### GitHub Search Bloc\n\n`github_search_bloc.dart`를 생성합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\n`GithubSearchBloc`은 `GithubSearchEvent`를 `GithubSearchState`로 변환하며\n`GithubRepository`에 의존합니다.\n\n:::\n\n:::note\n\n`GithubSearchEvents`를\n[디바운스](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)하기\n위해 커스텀 `EventTransformer`를 생성합니다. `Cubit` 대신 `Bloc`을 만든 이유 중\n하나가 바로 스트림 트랜스포머를 활용하기 위해서입니다.\n\n:::\n\n`common_github_search` 패키지가 완성되었습니다. 최종 결과물은\n[여기](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search)에서\n확인할 수 있습니다.\n\n다음으로 Flutter 구현을 진행합니다.\n\n## Flutter GitHub Search\n\nFlutter Github Search는 `common_github_search`의 모델, 데이터 프로바이더,\n리포지토리, bloc을 재사용하여 Github Search를 구현하는 Flutter\n애플리케이션입니다.\n\n### 설정\n\n`github_search` 디렉토리 내에서 `common_github_search`와 같은 레벨에 새 Flutter\n프로젝트를 생성합니다.\n\n<FlutterCreateSnippet />\n\n다음으로 필요한 모든 의존성을 포함하도록 `pubspec.yaml`을 업데이트합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\n새로 만든 `common_github_search` 라이브러리를 의존성으로 포함하고 있습니다.\n\n:::\n\n이제 의존성을 설치합니다.\n\n<FlutterPubGetSnippet />\n\n프로젝트 설정이 완료되었습니다. `common_github_search` 패키지가 데이터 레이어와\n비즈니스 로직 레이어를 포함하고 있으므로 프레젠테이션 레이어만 구현하면 됩니다.\n\n### Search Form\n\n`_SearchBar`와 `_SearchBody` 위젯이 있는 폼을 만들어야 합니다.\n\n- `_SearchBar`는 사용자 입력을 받는 역할을 합니다.\n- `_SearchBody`는 검색 결과, 로딩 인디케이터, 에러를 표시하는 역할을 합니다.\n\n`search_form.dart`를 생성합니다.\n\n`SearchForm`은 `_SearchBar`와 `_SearchBody` 위젯을 렌더링하는\n`StatelessWidget`입니다.\n\n`_SearchBar`도 `StatefulWidget`인데, 사용자가 입력한 내용을 추적하기 위해 자체\n`TextEditingController`를 유지해야 하기 때문입니다.\n\n`_SearchBody`는 검색 결과, 에러, 로딩 인디케이터를 표시하는\n`StatelessWidget`입니다. `GithubSearchBloc`의 소비자가 됩니다.\n\n상태가 `SearchStateSuccess`이면 다음에 구현할 `_SearchResults`를 렌더링합니다.\n\n`_SearchResults`는 `List<SearchResultItem>`을 받아 `_SearchResultItems` 리스트로\n표시하는 `StatelessWidget`입니다.\n\n`_SearchResultItem`은 단일 검색 결과의 정보를 렌더링하는\n`StatelessWidget`입니다. 사용자 상호작용을 처리하고 탭 시 리포지토리 URL로\n이동하는 역할도 합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar`는 `context.read<GithubSearchBloc>()`을 통해 `GitHubSearchBloc`에\n접근하고 bloc에 `TextChanged` 이벤트를 알립니다.\n\n:::\n\n:::note\n\n`_SearchBody`는 상태 변화에 따라 리빌드하기 위해 `BlocBuilder`를 사용합니다.\n`BlocBuilder` 객체의 bloc 파라미터가 생략되었으므로 `BlocBuilder`는\n`BlocProvider`와 현재 `BuildContext`를 사용하여 자동으로 조회합니다. 더 자세한\n내용은 [여기](/ko/flutter-bloc-concepts#blocbuilder)를 참고하세요.\n\n:::\n\n:::note\n\n스크롤 가능한 `_SearchResultItem` 리스트를 구성하기 위해 `ListView.builder`를\n사용합니다.\n\n:::\n\n:::note\n\n외부 URL을 열기 위해 [url_launcher](https://pub.dev/packages/url_launcher)\n패키지를 사용합니다.\n\n:::\n\n### 통합하기\n\n이제 `main.dart`에서 메인 앱을 구현하기만 하면 됩니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\n`GithubRepository`는 `main`에서 생성되어 `App`에 주입됩니다. `SearchForm`은\n`GithubSearchBloc`의 인스턴스를 초기화, 종료하고 `SearchForm` 위젯과 그 자식들이\n사용할 수 있도록 하는 `BlocProvider`로 감싸져 있습니다.\n\n:::\n\n이것이 전부입니다! [bloc](https://pub.dev/packages/bloc)과\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) 패키지를 사용하여\nFlutter에서 GitHub 검색 앱을 성공적으로 구현했고, 프레젠테이션 레이어와 비즈니스\n로직을 성공적으로 분리했습니다.\n\n전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search)에서\n확인할 수 있습니다.\n\n마지막으로 AngularDart GitHub Search 앱을 만들어 봅시다.\n\n## AngularDart GitHub Search\n\nAngularDart GitHub Search는 `common_github_search`의 모델, 데이터 프로바이더,\n리포지토리, bloc을 재사용하여 Github Search를 구현하는 AngularDart\n애플리케이션입니다.\n\n### 설정\n\n`common_github_search`와 같은 레벨의 github_search 디렉토리에 새 AngularDart\n프로젝트를 생성합니다.\n\n<StagehandSnippet />\n\n:::note\n\n`stagehand`는 다음 명령으로 설치할 수 있습니다:\n\n<ActivateStagehandSnippet />\n:::\n\n그런 다음 `pubspec.yaml`의 내용을 다음으로 교체합니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### Search Form\n\nFlutter 앱과 마찬가지로 `SearchBar`와 `SearchBody` 컴포넌트가 있는\n`SearchForm`을 만들어야 합니다.\n\n`SearchForm` 컴포넌트는 `GithubSearchBloc`을 생성하고 닫아야 하므로 `OnInit`과\n`OnDestroy`를 구현합니다.\n\n- `SearchBar`는 사용자 입력을 받는 역할을 합니다.\n- `SearchBody`는 검색 결과, 로딩 인디케이터, 에러를 표시하는 역할을 합니다.\n\n`search_form_component.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\n`GithubRepository`가 `SearchFormComponent`에 주입됩니다.\n\n:::\n\n:::note\n\n`GithubSearchBloc`은 `SearchFormComponent`에서 생성되고 닫힙니다.\n\n:::\n\n템플릿(`search_form_component.html`)은 다음과 같습니다:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\n다음으로 `SearchBar` 컴포넌트를 구현합니다.\n\n### Search Bar\n\n`SearchBar`는 사용자 입력을 받고 `GithubSearchBloc`에 텍스트 변경을 알리는\n컴포넌트입니다.\n\n`search_bar_component.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent`는 bloc에 `TextChanged` 이벤트를 알리는 역할을 하므로\n`GitHubSearchBloc`에 의존합니다.\n\n:::\n\n다음으로 `search_bar_component.html`을 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\n`SearchBar`가 완성되었습니다. 이제 `SearchBody`로 넘어갑니다.\n\n### Search Body\n\n`SearchBody`는 검색 결과, 에러, 로딩 인디케이터를 표시하는 컴포넌트입니다.\n`GithubSearchBloc`의 소비자가 됩니다.\n\n`search_body_component.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent`는 `angular_bloc` bloc 파이프를 통해 `GithubSearchBloc`이\n제공하는 `GithubSearchState`에 의존합니다.\n\n:::\n\n`search_body_component.html`을 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\n상태가 `isSuccess`이면 `SearchResults`를 렌더링합니다. 다음에 구현해 봅시다.\n\n### Search Results\n\n`SearchResults`는 `List<SearchResultItem>`을 받아 `SearchResultItems` 리스트로\n표시하는 컴포넌트입니다.\n\n`search_results_component.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\n다음으로 `search_results_component.html`을 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\n`SearchResultItem` 컴포넌트 리스트를 구성하기 위해 `ngFor`를 사용합니다.\n\n:::\n\n이제 `SearchResultItem`을 구현할 차례입니다.\n\n### Search Result Item\n\n`SearchResultItem`은 단일 검색 결과의 정보를 렌더링하는 컴포넌트입니다. 사용자\n상호작용을 처리하고 탭 시 리포지토리 URL로 이동하는 역할도 합니다.\n\n`search_result_item_component.dart`를 생성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\n그리고 `search_result_item_component.html`에 해당 템플릿을 작성합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### 통합하기\n\n모든 컴포넌트가 준비되었으니 이제 `app_component.dart`에서 통합합니다.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\n`AppComponent`에서 `GithubRepository`를 생성하고 `SearchForm` 컴포넌트에\n주입합니다.\n\n:::\n\n이것이 전부입니다! `bloc`과 `angular_bloc` 패키지를 사용하여 AngularDart에서\nGitHub 검색 앱을 성공적으로 구현했고, 프레젠테이션 레이어와 비즈니스 로직을\n성공적으로 분리했습니다.\n\n전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search)에서\n확인할 수 있습니다.\n\n## 요약\n\n이번 튜토리얼에서는 Flutter와 AngularDart 앱을 만들면서 두 앱 간에 모든 모델,\n데이터 프로바이더, bloc을 공유했습니다.\n\n실제로 두 번 작성해야 했던 것은 프레젠테이션 레이어(UI)뿐이며, 이는 효율성과\n개발 속도 면에서 큰 장점입니다. 또한 웹 앱과 모바일 앱이 서로 다른 사용자 경험과\n스타일을 갖는 것은 꽤 흔한 일인데, 이 접근 방식은 완전히 다르게 보이는 두 앱이\n동일한 데이터와 비즈니스 로직 레이어를 공유하는 것이 얼마나 쉬운지 보여줍니다.\n\n전체 소스 코드는\n[여기](https://github.com/felangel/bloc/tree/master/examples/github_search)에서\n확인할 수 있습니다.\n"
  },
  {
    "path": "docs/src/content/docs/ko/why-bloc.mdx",
    "content": "---\ntitle: 왜 Bloc인가?\ndescription:\n  어떤 요소가 Bloc을 견고한 상태 관리 솔루션으로 만드는 지에 대한 개요입니다.\nsidebar:\n  order: 1\n---\n\nBloc을 사용하면 Business Logic에서 Presentation을 쉽게 분리하여 코드를 _빠르게_,\n_테스트하기 쉽게_, *재사용 가능*하게 만들 수 있습니다.\n\n프로덕션 품질의 애플리케이션을 구축할 때 상태 관리가 중요해집니다.\n\n개발자로서 우리는 다음과 같은 요구사항을 가질 수 있습니다:\n\n- 언제든지 애플리케이션의 상태를 알 수 있어야 합니다.\n- 모든 케이스를 쉽게 테스트하여 앱이 적절하게 응답하는지 확인해야 합니다.\n- 데이터 기반 결정을 내릴 수 있도록 애플리케이션의 모든 단일 사용자 상호 작용을\n  기록해야 합니다.\n- 애플리케이션 내부와 다른 애플리케이션 전반에서 최대한 효율적으로 작업하고,\n  구성 요소를 재사용해야 합니다.\n- 많은 개발자가 동일한 패턴과 규칙에 따라 단일 코드 기반 내에서 원활하게 작업할\n  수 있어야 합니다.\n- 빠르고 반응성이 뛰어난 앱을 개발해야 합니다.\n\nBloc은 이러한 모든 요구 사항과 그 이상을 충족하도록 설계되었습니다.\n\n상태 관리 솔루션은 다양하며 어떤 솔루션을 사용할지 결정하는 것은 어려운 작업일\n수 있습니다. 완벽한 상태 관리 솔루션은 없습니다! 하지만 중요한 것은 팀과\n프로젝트에 가장 적합한 것을 선택하는 것입니다.\n\nBloc은 다음 세 가지 핵심 가치를 염두에 두고 설계되었습니다:\n\n- **간단함:** 이해하기 쉽고 다양한 스킬 수준을 가진 개발자가 사용할 수 있습니다.\n- **강력함:** 더 작은 구성 요소로 구성하여 놀랍고 복잡한 애플리케이션을 만드는\n  데 도움을 줍니다.\n- **테스트 가능:** 애플리케이션의 모든 측면을 쉽게 테스트할 수 있으므로 자신있게\n  테스트를 반복할 수 있습니다.\n\n전반적으로, Bloc은 상태 변경이 발생할 수 있는 시기를 규제하고 애플리케이션\n전체에 걸쳐 단일한 상태를 변경하는 방법을 시행함으로써 상태 변경을 예측 가능하게\n만들려고 시도합니다.\n"
  },
  {
    "path": "docs/src/content/docs/lint/configuration.mdx",
    "content": "---\ntitle: Linter Configuration\ndescription: Configuring the bloc linter.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nBy default, the bloc linter will not report any diagnostics unless you have\nexplicitly configured a project's analysis options.\n\nTo get started, create or modify the existing `analysis_options.yaml` at the\nroot of your project to include a list of rules under the top-level bloc key:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nRun the linter using the following command in your terminal:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nThe above command will analyze all files in the current directory and its\nsubdirectories, but you can also lint specific files and directories by passing\nthem as command-line arguments:\n\n<RunBlocLintInSrcTestSnippet />\n\nThe above command will analyze all code in the `src` and `test` directories.\n\nIf the `avoid_flutter_imports` rule is enabled, any bloc or cubit file that\ncontains a flutter import will be reported as a warning:\n\n<AvoidFlutterImportsWarningSnippet />\n\nYou can see the warning by running the `bloc lint` command:\n\n<RunBlocLintCounterCubitSnippet />\n\nThe output should look like:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nHere are all of the supported lint rules:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/lint/customizing-rules.mdx",
    "content": "---\ntitle: Customizing Lint Rules\ndescription: Customizing bloc lint rules\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nYou can customize the behavior of the bloc linter by changing the severity of\nindividual rules, individually enabling or disabling rules, and excluding files\nfrom static analysis.\n\n## Enabling and Disabling Rules\n\nThe bloc linter supports a growing list of lint rules. Note that lint rules\ndon't have to agree with each other. For example, some developers might prefer\nto use blocs (`prefer_bloc`) while others might prefer to use cubits\n(`prefer_cubit`).\n\n:::note\n\nUnlike static analysis, lint rules might contain false positives. Feel free to\nreport any false positives or other issues by\n[filing an issue](https://github.com/felangel/bloc/issues/new/choose).\n\n:::\n\n### Enabling Recommended Rules\n\nThe bloc library provides a set of recommended lint rules as part of the\n[`bloc_lint`](https://pub.dev/packages/bloc_lint) package.\n\nTo enable the recommended set of lints add the `bloc_lint` package as a dev\ndependency:\n\n<InstallBlocLintSnippet />\n\nThen edit your `analysis_options.yaml` to include the rule set:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nWhen a new version of `bloc_lint` is published, code that previously passed\nstatic analysis might start failing. We recommend updating your code to work\nwith the new rules, or you can also optionally enable or disable individual\nrules.\n\n:::\n\n### Enabling Individual Rules\n\nTo enable individual rules, add `bloc:` to the `analysis_options.yaml` file as a\ntop-level key and `rules:` as a second-level key. On subsequent lines, specify\nthe rules you want as a YAML list (prefixed with dashes).\n\nFor example:\n\n<BlocLintEnablingRulesSnippet />\n\n### Disabling Individual Rules\n\nIf you include an existing rule set such as the `recommended` set, you may want\nto disable one or more included lint rules. Disabling rules is similar to\nenabling them, but requires the use of a YAML map rather than a list.\n\nFor example, the following includes the recommended set of lint rules except for\n`avoid_public_bloc_methods` and additionally enables the `prefer_bloc` rule:\n\n<BlocLintDisablingRulesSnippet />\n\n## Customizing Rule Severity\n\nYou can adjust the severity of any rule like so:\n\n<BlocLintChangingSeveritySnippet />\n\nNow the same lint rule will be reported with a severity of `info` instead of\n`warning`:\n\n<ImportFlutterInfoSnippet />\n\nThe output of the `bloc lint` command should look like:\n\n<ImportFlutterInfoOutputSnippet />\n\nThe supported severity options are:\n\n| Severity  | Description                                        |\n| --------- | -------------------------------------------------- |\n| `error`   | Indicates the pattern is not allowed.              |\n| `warning` | Indicates the pattern is suspicious but allowed.   |\n| `info`    | Provides information to users but is not a problem |\n| `hint`    | Proposes a better way of achieving a result.       |\n\n## Excluding Files\n\nSometimes it's okay for static analysis to fail. For example, you might want to\nignore warnings or errors reported in generated code that wasn't written by you\nand your team. Just like with official Dart lint rules, you can use the\n`exclude:` analyzer option to exclude files from static analysis.\n\nYou can either list individual files or use\n[`glob`](https://pub.dev/packages/glob) patterns.\n\n:::note\n\nAll usage of glob patterns should be relative to the directory containing the\ncorresponding `analysis_options.yaml` file.\n\n:::\n\nFor example, we can exclude all generated Dart code via the following analysis\noptions:\n\n<BlocLintExcludingFilesSnippet />\n\n## Ignoring Rules\n\nJust like with official Dart lint rules, you can ignore bloc lint rules for a\ngiven file or line of code using `// ignore_for_file` and `// ignore`\nrespectively.\n\n:::note\n\nTo ignore multiple rules for a given line or file, supply a comma-separated\nlist.\n\n:::\n\n### Ignoring Lines\n\nWe can ignore specific occurrences of rule violations by adding an `ignore`\ncomment either right above the offending line or by appending it to the\noffending line.\n\nFor example, we can ignore specific occurrences of\n`prefer_file_naming_conventions` in a given file:\n\n<BlocLintIgnoreForLineSnippet />\n\n### Ignoring Files\n\nWe can ignore all occurrences of rule violations within a file by adding an\n`ignore_for_file` comment anywhere in the file.\n\nFor example, we can ignore all occurrences of `prefer_file_naming_conventions`\nin a given file:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/lint/index.mdx",
    "content": "---\ntitle: Linter Overview\ndescription: An introduction to the bloc linter.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nLinting is the process of statically analyzing code for potential bugs in\naddition to programmatic and stylistic errors.\n\nBloc has a built-in linter, which can be used through your IDE or the\n[`bloc command-line tools`](https://pub.dev/packages/bloc_tools) with the\n`bloc lint` command.\n\nWith the help of the bloc linter, you can improve the quality of your codebase\nand enforce consistency without executing a single line of code.\n\nFor example, perhaps you accidentally imported a Flutter dependency into your\ncubit:\n\n<AvoidFlutterImportsWarningSnippet />\n\nIf properly configured, the bloc linter will point to the import and produce the\nfollowing warning:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nIn the following sections, we'll cover how to install, configure, and customize\nthe bloc linter so that you can take advantage of static analysis in your\ncodebase.\n\n## Quick Start\n\nGet started using the bloc linter in just a few quick and easy steps.\n\n:::note\n\nIn order to start using bloc you must have the\n[Dart SDK](https://dart.dev/get-dart) installed on your machine.\n\n:::\n\n1. Install the [bloc command-line tools](https://pub.dev/packages/bloc_tools)\n\n   <InstallBlocToolsSnippet />\n\n1. Install the [bloc_lint](https://pub.dev/packages/bloc_lint) package\n\n   <InstallBlocLintSnippet />\n\n1. Add an `analysis_options.yaml` to the root of your project with the\n   recommended rules\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. Run the linter\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nThat's all there is to it 🎉\n\nContinue reading for a more in-depth look at configuring and customizing the\nbloc linter.\n"
  },
  {
    "path": "docs/src/content/docs/lint/installation.mdx",
    "content": "---\ntitle: Linter Installation\ndescription: Installing the bloc linter.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## Command-Line Tools\n\nTo use the linter from the command line, install\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) via the following\ncommand:\n\n<InstallBlocToolsSnippet />\n\nOnce the bloc command-line tools have been installed, you can run the bloc\nlinter via the `bloc lint` command:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## Recommended Rule Set\n\nTo install the recommended lint rule set, install\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) as a dev dependency\nvia the following command:\n\n<InstallBlocLintSnippet />\n\nThen, add an `analysis_options.yaml` to the root of your project with the\nrecommended rule set:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nIf needed, you can include multiple rule sets by defining them as a list:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## IDE Integrations\n\nThe following IDEs officially support the bloc linter and language server to\nprovide instant diagnostics directly within your IDE.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tSupport for the [Bloc VSCode\n\t\tExtension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tis available in v6.8.0.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tSupport for the [Bloc IntelliJ\n\t\tPlugin](https://plugins.jetbrains.com/plugin/12129-bloc) is available in\n\t\tv4.1.0.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: Avoid BuildContext Extensions\ndescription: The avoid_build_context_extensions rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nAvoid using `BuildContext` extensions to access `Bloc` or `Cubit` instances.\n\n:::note\n\nThis lint rule was introduced in version `0.3.0` of\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Rationale\n\nFor consistency and for the sake of being explicit, prefer directly using the\nunderlying methods instead of `BuildContext` extensions. This is also beneficial\nfor testing because it is not possible to mock an extension method.\n\n| extension        | explicit method                                                     |\n| ---------------- | ------------------------------------------------------------------- |\n| `context.read`   | `BlocProvider.of<Bloc>(context, listen: false)`                     |\n| `context.watch`  | `BlocBuilder<Bloc, State>(...)` or `BlocProvider.of<Bloc>(context)` |\n| `context.select` | `BlocSelector<Bloc, State>(...)`                                    |\n\n## Examples\n\n**Avoid** using `BuildContext` extensions to interact with `Bloc` or `Cubit`\ninstances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `avoid_build_context_extensions` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: Avoid Flutter Imports\ndescription: The avoid_flutter_imports bloc lint rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nAvoid introducing dependencies on Flutter within business logic components\n(`Bloc` or `Cubit` instances).\n\n## Rationale\n\nLayering an application is a key part of building a maintainable codebase and\nhelps developers iterate quickly and with confidence. Each layer should have a\nsingle responsibility and be able to function and tested in isolation. This\nallows you to contain changes to specific layers, minimizing the impact on the\nentire application.\n\nAs a result, business logic components should generally manage feature state and\nbe decoupled from the UI layer. Events should flow into business logic\ncomponents from the UI layer and state should flow out of the business logic\nlayer into the UI layer.\n\nKeeping business logic components decoupled from Flutter provides the ability to\nreuse business logic across multiple platforms/frameworks (e.g. Flutter,\nAngularDart, Jaspr, etc.).\n\n## Examples\n\n**DO NOT** import Flutter within your business logic components.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `avoid_flutter_imports` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: Avoid Public Bloc Methods\ndescription: The avoid_public_bloc_methods rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nAvoid exposing public methods on `Bloc` instances.\n\n## Rationale\n\nBlocs react to incoming events and emit outgoing states. As a result, the\nrecommended way of communicating with a bloc instance is via the `add` method.\nIn most cases, there's no need to create additional abstractions on top of the\n`add` API.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n## Examples\n\n**DO NOT** expose public methods on bloc instances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `avoid_public_bloc_methods` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: Avoid Public Fields\ndescription: The avoid_public_fields rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nAvoid exposing public fields on `Bloc` and `Cubit` instances.\n\n## Rationale\n\nBusiness logic components maintain their own `state` and emit state changes via\nthe `emit` API. As a result, all public facing state should be exposed via the\n`state` object.\n\n## Examples\n\n**DO NOT** expose public fields on bloc and cubit instances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `avoid_public_fields` rule, add it to your `analysis_options.yaml`\nunder `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: Prefer Bloc\ndescription: The prefer_bloc rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPrefer using `Bloc` instances of `Cubit` instances.\n\n## Rationale\n\nThis rule is purely a stylistic rule. In some cases, teams may prefer to\nstandardize on just using `Bloc` instances throughout their entire application\nfor consistency.\n\n:::tip\n\nLearn more about the benefits of `Bloc` in\n[Core Concepts](/bloc-concepts/#bloc-advantages).\n\n:::\n\n## Examples\n\n**Avoid** using `Cubit` instances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `prefer_bloc` rule, add it to your `analysis_options.yaml` under\n`bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: Prefer BuildContext Extensions\ndescription: The prefer_build_context_extensions rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPrefer using `BuildContext` extensions to access a `Bloc` or `Repository`\ninstance.\n\n:::note\n\nThis lint rule was introduced in version `0.3.2` of\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Rationale\n\nFor consistency, prefer using `BuildContext` extensions like `context.read`,\n`context.watch`, and `context.select` instead of `BlocProvider.of`,\n`RepositoryProvider.of`, `BlocBuilder` or `BlocSelector`.\n\n| explicit method                                                     | extension             |\n| ------------------------------------------------------------------- | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                     | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` or `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                    | `context.select`      |\n\n## Examples\n\n**Avoid** using `BlocProvider.of<T>(context)` to access a `Bloc` instance.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `prefer_build_context_extensions` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: Prefer Cubit\ndescription: The prefer_cubit rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nPrefer using `Cubit` instances of `Bloc` instances.\n\n## Rationale\n\nThis rule is purely a stylistic rule. In some cases, teams may prefer to\nstandardize on just using `Cubit` instances throughout their entire application\nfor consistency.\n\n:::tip\n\nLearn more about the benefits of `Cubit` in\n[Core Concepts](/bloc-concepts/#cubit-advantages).\n\n:::\n\n## Examples\n\n**Avoid** using `Bloc` instances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `prefer_cubit` rule, add it to your `analysis_options.yaml` under\n`bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: Prefer File Naming Conventions\ndescription: The prefer_file_naming_conventions rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nPrefer following file naming conventions.\n\n:::note\n\nThis lint rule was introduced in version `0.3.0` of\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Rationale\n\nFor consistency, ease of maintenance, and separation of concerns prefer to\ndefine bloc and cubit instances in their respective Dart files instead of\ninlining them.\n\n:::tip\n\nConsider using the `bloc new <component>` command from\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) to quickly and\nconsistently generate new bloc/cubit instances.\n\n:::\n\n## Examples\n\n**Prefer** declaring bloc/cubit instances in their own respective files.\n\n**GOOD**:\n\n<GoodSnippet />\n\n**BAD**:\n\n<BadSnippet />\n\n## Enable\n\nTo enable the `prefer_file_naming_conventions` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: Prefer Void Public Cubit Methods\ndescription: The prefer_void_public_cubit_methods rule.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nPrefer void public methods on `Cubit` instances.\n\n:::note\n\nThis lint rule was introduced in version `0.2.0-dev.2` of\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Rationale\n\nPublic methods on `Cubit` instances should be used to notify the `Cubit` and\ninitiate state changes via the `emit` method. If the caller needs access to any\nstate information, they should access it from the `state` instead.\n\n:::note\n\nThe following rules are related and are usually enabled in combination with\n`prefer_void_public_cubit_methods`.\n\n- [`avoid_public_bloc_methods`](/lint-rules/avoid_public_bloc_methods)\n- [`avoid_public_fields`](/lint-rules/avoid_public_fields)\n\n:::\n\n## Examples\n\n**Avoid** non-void public methods on `Cubit` instances.\n\n**BAD**:\n\n<BadSnippet />\n\n**GOOD**:\n\n<GoodSnippet />\n\n## Enable\n\nTo enable the `prefer_void_public_cubit_methods` rule, add it to your\n`analysis_options.yaml` under `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/migration.mdx",
    "content": "---\ntitle: Migration Guide\ndescription: Migrate to the latest stable version of Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nPlease refer to the [release log](https://github.com/felangel/bloc/releases) for\nmore information regarding what changed in each release.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ Decouple `blocTest` from `BlocBase`\n\n:::note[What Changed?]\n\nIn bloc_test v10.0.0, the `blocTest` API is no longer tightly coupled to\n`BlocBase`.\n\n:::\n\n##### Rationale\n\n`blocTest` should use the core bloc interfaces when possible for increased\nflexibility and reusability. Previously this wasn't possible because `BlocBase`\nimplemented `StateStreamableSource` which was not enough for `blocTest` due to\nthe internal dependency on the `emit` API.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Support WebAssembly\n\n:::note[What Changed?]\n\nIn hydrated_bloc v10.0.0, support for compiling to WebAssembly (wasm) was added.\n\n:::\n\n##### Rationale\n\nIt was previously not possible to compile apps to wasm when using\n`hydrated_bloc`. In v10.0.0, the package was refactored to allow compiling to\nwasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Remove Deprecated APIs\n\n:::note[What Changed?]\n\nIn bloc v9.0.0, all previously deprecated APIs were removed.\n\n:::\n\n##### Summary\n\n- `BlocOverrides` removed in favor of `Bloc.observer` and `Bloc.transformer`\n\n#### ❗✨ Introduce new `EmittableStateStreamableSource` Interface\n\n:::note[What Changed?]\n\nIn bloc v9.0.0, a new core interface `EmittableStateStreamableSource` was\nintroduced.\n\n:::\n\n##### Rationale\n\n`package:bloc_test` was previously tightly coupled to `BlocBase`. The\n`EmittableStateStreamableSource` interface was introduced in order to allow\n`blocTest` to be decoupled from the `BlocBase` concrete implementation.\n\n### `package:hydrated_bloc`\n\n#### ✨ Reintroduce `HydratedBloc.storage` API\n\n:::note[What Changed?]\n\nIn hydrated_bloc v9.0.0, `HydratedBlocOverrides` was removed in favor of the\n`HydratedBloc.storage` API.\\*\\*\n\n:::\n\n##### Rationale\n\nRefer to the\n[rationale for reintroducing the Bloc.observer and Bloc.transformer overrides](/migration#-reintroduce-blocobserver-and-bloctransformer-apis).\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ Reintroduce `Bloc.observer` and `Bloc.transformer` APIs\n\n:::note[What Changed?]\n\nIn bloc v8.1.0, `BlocOverrides` was deprecated in favor of the `Bloc.observer`\nand `Bloc.transformer` APIs.\n\n:::\n\n##### Rationale\n\nThe `BlocOverrides` API was introduced in v8.0.0 in an attempt to support\nscoping bloc-specific configurations such as `BlocObserver`, `EventTransformer`,\nand `HydratedStorage`. In pure Dart applications, the changes worked well;\nhowever, in Flutter applications the new API caused more problems than it\nsolved.\n\nThe `BlocOverrides` API was inspired by similar APIs in Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**Problems**\n\nWhile it wasn't the primary reason for these changes, the `BlocOverrides` API\nintroduced additional complexity for developers. In addition to increasing the\namount of nesting and lines of code needed to achieve the same effect, the\n`BlocOverrides` API required developers to have a solid understanding of\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) in Dart.\n`Zones` are not a beginner-friendly concept and failure to understand how Zones\nwork could lead to the introduction of bugs (such as uninitialized observers,\ntransformers, storage instances).\n\nFor example, many developers would have something like:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nThe above code, while appearing harmless, can actually lead to many difficult to\ntrack bugs. Whatever zone `WidgetsFlutterBinding.ensureInitialized` is initially\ncalled from will be the zone in which gesture events are handled (e.g. `onTap`,\n`onPressed` callbacks) due to `GestureBinding.initInstances`. This is just one\nof many issues caused by using `zoneValues`.\n\nIn addition, Flutter does many things behind the scenes which involve\nforking/manipulating Zones (especially when running tests) which can lead to\nunexpected behaviors (and in many cases behaviors that are outside the\ndeveloper's control -- see issues below).\n\nDue to the use of the\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), the\ntransition to the `BlocOverrides` API led to the discovery of several\nbugs/limitations in Flutter (specifically around Widget and Integration Tests):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nwhich affected many developers using the bloc library:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ Introduce new `BlocOverrides` API\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, `Bloc.observer` and `Bloc.transformer` were removed in favor of\nthe `BlocOverrides` API.\n\n:::\n\n##### Rationale\n\nThe previous API used to override the default `BlocObserver` and\n`EventTransformer` relied on a global singleton for both the `BlocObserver` and\n`EventTransformer`.\n\nAs a result, it was not possible to:\n\n- Have multiple `BlocObserver` or `EventTransformer` implementations scoped to\n  different parts of the application\n- Have `BlocObserver` or `EventTransformer` overrides be scoped to a package\n  - If a package were to depend on `package:bloc` and registered its own\n    `BlocObserver`, any consumer of the package would either have to overwrite\n    the package's `BlocObserver` or report to the package's `BlocObserver`.\n\nIt was also more difficult to test because of the shared global state across\ntests.\n\nBloc v8.0.0 introduces a `BlocOverrides` class which allows developers to\noverride `BlocObserver` and/or `EventTransformer` for a specific `Zone` rather\nthan relying on a global mutable singleton.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n`Bloc` instances will use the `BlocObserver` and/or `EventTransformer` for the\ncurrent `Zone` via `BlocOverrides.current`. If there are no `BlocOverrides` for\nthe zone, they will use the existing internal defaults (no change in\nbehavior/functionality).\n\nThis allows allow each `Zone` to function independently with its own\n`BlocOverrides`.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA and eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Blocs in this zone report to BlocObserverA\n    // and use eventTransformerA as the default transformer.\n    // ...\n\n    // Later...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB and eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Blocs in this zone report to BlocObserverB\n        // and use eventTransformerB as the default transformer.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Improve Error Handling and Reporting\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, `BlocUnhandledErrorException` is removed. In addition, any\nuncaught exceptions are always reported to `onError` and rethrown (regardless of\ndebug or release mode). The `addError` API reports errors to `onError`, but does\nnot treat reported errors as uncaught exceptions.\n\n:::\n\n##### Rationale\n\nThe goal of these changes is:\n\n- make internal unhandled exceptions extremely obvious while still preserving\n  bloc functionality\n- support `addError` without disrupting control flow\n\nPreviously, error handling and reporting varied depending on whether the\napplication was running in debug or release mode. In addition, errors reported\nvia `addError` were treated as uncaught exceptions in debug mode which led to a\npoor developer experience when using the `addError` API (specifically when\nwriting unit tests).\n\nIn v8.0.0, `addError` can be safely used to report errors and `blocTest` can be\nused to verify that errors are reported. All errors are still reported to\n`onError`, however, only uncaught exceptions are rethrown (regardless of debug\nor release mode).\n\n#### ❗🧹 Make `BlocObserver` abstract\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, `BlocObserver` was converted into an `abstract` class which\nmeans an instance of `BlocObserver` cannot be instantiated.\n\n:::\n\n##### Rationale\n\n`BlocObserver` was intended to be an interface. Since the default API\nimplementation are no-ops, `BlocObserver` is now an `abstract` class to clearly\ncommunicate that the class is meant to be extended and not directly\ninstantiated.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // It was possible to create an instance of the base class.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // Cannot instantiate the base class.\n  final observer = BlocObserver(); // ERROR\n\n  // Extend `BlocObserver` instead.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ `add` throws `StateError` if Bloc is closed\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, calling `add` on a closed bloc will result in a `StateError`.\n\n:::\n\n##### Rationale\n\nPreviously, it was possible to call `add` on a closed bloc and the internal\nerror would get swallowed, making it difficult to debug why the added event was\nnot being processed. In order to make this scenario more visible, in v8.0.0,\ncalling `add` on a closed bloc will throw a `StateError` which will be reported\nas an uncaught exception and propagated to `onError`.\n\n#### ❗✨ `emit` throws `StateError` if Bloc is closed\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, calling `emit` within a closed bloc will result in a\n`StateError`.\n\n:::\n\n##### Rationale\n\nPreviously, it was possible to call `emit` within a closed bloc and no state\nchange would occur but there would also be no indication of what went wrong,\nmaking it difficult to debug. In order to make this scenario more visible, in\nv8.0.0, calling `emit` within a closed bloc will throw a `StateError` which will\nbe reported as an uncaught exception and propagated to `onError`.\n\n#### ❗🧹 Remove Deprecated APIs\n\n:::note[What Changed?]\n\nIn bloc v8.0.0, all previously deprecated APIs were removed.\n\n:::\n\n##### Summary\n\n- `mapEventToState` removed in favor of `on<Event>`\n- `transformEvents` removed in favor of `EventTransformer` API\n- `TransitionFunction` typedef removed in favor of `EventTransformer` API\n- `listen` removed in favor of `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` and `MockCubit` no longer require `registerFallbackValue`\n\n:::note[What Changed?]\n\nIn bloc_test v9.0.0, developers no longer need to explicitly call\n`registerFallbackValue` when using `MockBloc` or `MockCubit`.\n\n:::\n\n##### Summary\n\n`registerFallbackValue` is only needed when using the `any()` matcher from\n`package:mocktail` for a custom type. Previously, `registerFallbackValue` was\nneeded for every `Event` and `State` when using `MockBloc` or `MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Tests...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Tests...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Introduce new `HydratedBlocOverrides` API\n\n:::note[What Changed?]\n\nIn hydrated_bloc v8.0.0, `HydratedBloc.storage` was removed in favor of the\n`HydratedBlocOverrides` API.\n\n:::\n\n##### Rationale\n\nPreviously, a global singleton was used to override the `Storage`\nimplementation.\n\nAs a result, it was not possible to have multiple `Storage` implementations\nscoped to different parts of the application. It was also more difficult to test\nbecause of the shared global state across tests.\n\n`HydratedBloc` v8.0.0 introduces a `HydratedBlocOverrides` class which allows\ndevelopers to override `Storage` for a specific `Zone` rather than relying on a\nglobal mutable singleton.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\n`HydratedBloc` instances will use the `Storage` for the current `Zone` via\n`HydratedBlocOverrides.current`.\n\nThis allows allow each `Zone` to function independently with its own\n`BlocOverrides`.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ Introduce new `on<Event>` API\n\n:::note[What Changed?]\n\nIn bloc v7.2.0, `mapEventToState` was deprecated in favor of `on<Event>`.\n`mapEventToState` will be removed in bloc v8.0.0.\n\n:::\n\n##### Rationale\n\nThe `on<Event>` API was introduced as part of\n[[Proposal] Replace mapEventToState with on\\<Event\\> in Bloc](https://github.com/felangel/bloc/issues/2526).\nDue to [an issue in Dart](https://github.com/dart-lang/sdk/issues/44616) it's\nnot always obvious what the value of `state` will be when dealing with nested\nasync generators (`async*`). Even though there are ways to work around the\nissue, one of the core principles of the bloc library is to be predictable. The\n`on<Event>` API was created to make the library as safe as possible to use and\nto eliminate any uncertainty when it comes to state changes.\n\n:::tip\n\nFor more information,\n[read the full proposal](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**Summary**\n\n`on<E>` allows you to register an event handler for all events of type `E`. By\ndefault, events will be processed concurrently when using `on<E>` as opposed to\n`mapEventToState` which processes events `sequentially`.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nEach registered `EventHandler` functions independently so it's important to\nregister event handlers based on the type of transformer you'd like applied.\n\n:::\n\nIf you want to retain the exact same behavior as in v7.1.0 you can register a\nsingle event handler for all events and apply a `sequential` transformer:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: logic goes here...\n  }\n}\n```\n\nYou can also override the default `EventTransformer` for all blocs in your\napplication:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ Introduce new `EventTransformer` API\n\n:::note[What Changed?]\n\nIn bloc v7.2.0, `transformEvents` was deprecated in favor of the\n`EventTransformer` API. `transformEvents` will be removed in bloc v8.0.0.\n\n:::\n\n##### Rationale\n\nThe `on<Event>` API opened the door to being able to provide a custom event\ntransformer per event handler. A new `EventTransformer` typedef was introduced\nwhich enables developers to transform the incoming event stream for each event\nhandler rather than having to specify a single event transformer for all events.\n\n**Summary**\n\nAn `EventTransformer` is responsible for taking the incoming stream of events\nalong with an `EventMapper` (your event handler) and returning a new stream of\nevents.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\nThe default `EventTransformer` processes all events concurrently and looks\nsomething like:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nCheck out [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency)\nfor an opinionated set of custom event transformers\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Define a custom `EventTransformer`\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Apply the custom `EventTransformer` to the `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ Deprecate `transformTransitions` API\n\n:::note[What Changed?]\n\nIn bloc v7.2.0, `transformTransitions` was deprecated in favor of overriding the\n`stream` API. `transformTransitions` will be removed in bloc v8.0.0.\n\n:::\n\n##### Rationale\n\nThe `stream` getter on `Bloc` makes it easy to override the outbound stream of\nstates therefore it's no longer valuable to maintain a separate\n`transformTransitions` API.\n\n**Summary**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc and Cubit extend BlocBase\n\n##### Rationale\n\nAs a developer, the relationship between blocs and cubits was a bit awkward.\nWhen cubit was first introduced it began as the base class for blocs which made\nsense because it had a subset of the functionality and blocs would just extend\nCubit and define additional APIs. This came with a few drawbacks:\n\n- All APIs would either have to be renamed to accept a cubit for accuracy or\n  they would need to be kept as bloc for consistency even though hierarchically\n  it is inaccurate ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- Cubit would need to extend Stream and implement EventSink in order to have a\n  common base which widgets like BlocBuilder, BlocListener, etc. can be\n  implemented against ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nLater, we experimented with inverting the relationship and making bloc the base\nclass which partially resolved the first bullet above but introduced other\nissues:\n\n- The cubit API is bloated due to the underlying bloc APIs like mapEventToState,\n  add, etc. ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - Developers can technically invoke these APIs and break things\n- We still have the same issue of cubit exposing the entire stream API as before\n  ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nTo address these issues we introduced a base class for both `Bloc` and `Cubit`\ncalled `BlocBase` so that upstream components can still interoperate with both\nbloc and cubit instances but without exposing the entire `Stream` and\n`EventSink` API directly.\n\n**Summary**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed returns a function to support dynamic values\n\n##### Rationale\n\nIn order to support having a mutable seed value which can be updated dynamically\nin `setUp`, `seed` returns a function.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect returns a function to support dynamic values and includes matcher support\n\n##### Rationale\n\nIn order to support having a mutable expectation which can be updated\ndynamically in `setUp`, `expect` returns a function. `expect` also supports\n`Matchers`.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors returns a function to support dynamic values and includes matcher support\n\n##### Rationale\n\nIn order to support having a mutable errors which can be updated dynamically in\n`setUp`, `errors` returns a function. `errors` also supports `Matchers`.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// It can also be a `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc and MockCubit\n\n##### Rationale\n\nTo support stubbing of various core APIs, `MockBloc` and `MockCubit` are\nexported as part of the `bloc_test` package. Previously, `MockBloc` had to be\nused for both `Bloc` and `Cubit` instances which was not intuitive.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Mocktail Integration\n\n##### Rationale\n\nDue to various limitations of the null-safe\n[package:mockito](https://pub.dev/packages/mockito) described\n[here](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) is used by `MockBloc` and\n`MockCubit`. This allows developers to continue using a familiar mocking API\nwithout the need to manually write stubs or rely on code generation.\n\n**Summary**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Please refer to [#347](https://github.com/dart-lang/mockito/issues/347) as\n> well as the\n> [mocktail documentation](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> for more information.\n\n### `package:flutter_bloc`\n\n#### ❗ rename `cubit` parameter to `bloc`\n\n##### Rationale\n\nAs a result of the refactor in `package:bloc` to introduce `BlocBase` which\n`Bloc` and `Cubit` extend, the parameters of `BlocBuilder`, `BlocConsumer`, and\n`BlocListener` were renamed from `cubit` to `bloc` because the widgets operate\non the `BlocBase` type. This also further aligns with the library name and\nhopefully improves readability.\n\n**Summary**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory is required when calling HydratedStorage.build\n\n##### Rationale\n\nIn order to make `package:hydrated_bloc` a pure Dart package, the dependency on\n[package:path_provider](https://pub.dev/packages/path_provider) was removed and\nthe `storageDirectory` parameter when calling `HydratedStorage.build` is\nrequired and no longer defaults to `getTemporaryDirectory`.\n\n**Summary**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc and context.repository are deprecated in favor of context.read and context.watch\n\n##### Rationale\n\n`context.read`, `context.watch`, and `context.select` were added to align with\nthe existing [provider](https://pub.dev/packages/provider) API which many\ndevelopers are familiar and to address issues that have been raised by the\ncommunity. To improve the safety of the code and maintain consistency,\n`context.bloc` was deprecated because it can be replaced with either\n`context.read` or `context.watch` dependending on if it's used directly within\n`build`.\n\n**context.watch**\n\n`context.watch` addresses the request to have a\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) because we can\nwatch several blocs within a single `Builder` in order to render UI based on\nmultiple states:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // return a Widget which depends on the state of BlocA, BlocB, and BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` allows developers to render/update UI based on a part of a bloc\nstate and addresses the request to have a\n[simpler buildWhen](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nThe above snippet allows us to access and rebuild the widget only when the\ncurrent user's name changes.\n\n**context.read**\n\nEven though it looks like `context.read` is identical to `context.bloc` there\nare some subtle but significant differences. Both allow you to access a bloc\nwith a `BuildContext` and do not result in rebuilds; however, `context.read`\ncannot be called directly within a `build` method. There are two main reasons to\nuse `context.bloc` within `build`:\n\n1. **To access the bloc's state**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nThe above usage is error prone because the `Text` widget will not be rebuilt if\nthe state of the bloc changes. In this scenario, either a `BlocBuilder` or\n`context.watch` should be used.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nor\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nUsing `context.watch` at the root of the `build` method will result in the\nentire widget being rebuilt when the bloc state changes. If the entire widget\ndoes not need to be rebuilt, either use `BlocBuilder` to wrap the parts that\nshould rebuild, use a `Builder` with `context.watch` to scope the rebuilds, or\ndecompose the widget into smaller widgets.\n\n:::\n\n2. **To access the bloc so that an event can be added**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nThe above usage is inefficient because it results in a bloc lookup on each\nrebuild when the bloc is only needed when the user taps the `ElevatedButton`. In\nthis scenario, prefer to use `context.read` to access the bloc directly where it\nis needed (in this case, in the `onPressed` callback).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Summary**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> If accessing a bloc to add an event, perform the bloc access using\n`context.read` in the callback where it is needed.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Use `context.watch` when accessing the state of the bloc in order to ensure\nthe widget is rebuilt when the state changes.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError takes Cubit\n\n##### Rationale\n\nDue to the integration of `Cubit`, `onError` is now shared between both `Bloc`\nand `Cubit` instances. Since `Cubit` is the base, `BlocObserver` will accept a\n`Cubit` type rather than a `Bloc` type in the `onError` override.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc does not emit last state on subscription\n\n##### Rationale\n\nThis change was made to align `Bloc` and `Cubit` with the built-in `Stream`\nbehavior in `Dart`. In addition, conforming this the old behavior in the context\nof `Cubit` led to many unintended side-effects and overall complicated the\ninternal implementations of other packages such as `flutter_bloc` and\n`bloc_test` unnecessarily (requiring `skip(1)`, etc...).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nPreviously, the above snippet would output the initial state of the bloc\nfollowed by subsequent state changes.\n\n**v6.x.x**\n\nIn v6.0.0, the above snippet does not output the initial state and only outputs\nsubsequent state changes. The previous behavior can be achieved with the\nfollowing:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Note**: This change will only affect code that relies on direct bloc\nsubscriptions. When using `BlocBuilder`, `BlocListener`, or `BlocConsumer` there\nwill be no noticeable change in behavior.\n\n### `package:bloc_test`\n\n#### ❗MockBloc only requires State type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `MockBloc`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen only requires State type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `whenListen`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest does not require Event type\n\n##### Rationale\n\nIt is not necessary and eliminates extra code while also making `blocTest`\ncompatible with `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip defaults to 0\n\n##### Rationale\n\nSince `bloc` and `cubit` instances will no longer emit the latest state for new\nsubscriptions, it was no longer necessary to default `skip` to `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nThe initial state of a bloc or cubit can be tested with the following:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest make build synchronous\n\n##### Rationale\n\nPreviously, `build` was made `async` so that various preparation could be done\nto put the bloc under test in a specific state. It is no longer necessary and\nalso resolves several issues due to the added latency between the build and the\nsubscription internally. Instead of doing async prep to get a bloc in a desired\nstate we can now set the bloc state by chaining `emit` with the desired state.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` is only visible for testing and should never be used outside of tests.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocBuilder` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocListener` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗BlocConsumer bloc parameter renamed to cubit\n\n##### Rationale\n\nIn order to make `BlocConsumer` interoperate with `bloc` and `cubit` instances\nthe `bloc` parameter was renamed to `cubit` (since `Cubit` is the base class).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState has been removed\n\n##### Rationale\n\nAs a developer, having to override `initialState` when creating a bloc presents\ntwo main issues:\n\n- The `initialState` of the bloc can be dynamic and can also be referenced at a\n  later point in time (even outside of the bloc itself). In some ways, this can\n  be viewed as leaking internal bloc information to the UI layer.\n- It's verbose.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> For more information check out\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate renamed to BlocObserver\n\n##### Rationale\n\nThe name `BlocDelegate` was not an accurate description of the role that the\nclass played. `BlocDelegate` suggests that the class plays an active role\nwhereas in reality the intended role of the `BlocDelegate` was for it to be a\npassive component which simply observes all blocs in an application.\n\n:::note\n\nThere should ideally be no user-facing functionality or features handled within\n`BlocObserver`.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor has been removed\n\n##### Rationale\n\n`BlocSupervisor` was yet another component that developers had to know about and\ninteract with for the sole purpose of specifying a custom `BlocDelegate`. With\nthe change to `BlocObserver` we felt it improved the developer experience to set\nthe observer directly on the bloc itself.\n\n?> This changed also enabled us to decouple other bloc add-ons like\n`HydratedStorage` from the `BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗BlocBuilder condition renamed to buildWhen\n\n##### Rationale\n\nWhen using `BlocBuilder`, we previously could specify a `condition` to determine\nwhether the `builder` should rebuild.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nThe name `condition` is not very self-explanatory or obvious and more\nimportantly, when interacting with a `BlocConsumer` the API became inconsistent\nbecause developers can provide two conditions (one for `builder` and one for\n`listener`). As a result, the `BlocConsumer` API exposed a `buildWhen` and\n`listenWhen`\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nIn order to align the API and provide a more consistent developer experience,\n`condition` was renamed to `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // return true/false to determine whether to call builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗BlocListener condition renamed to listenWhen\n\n##### Rationale\n\nFor the same reasons as described above, the `BlocListener` condition was also\nrenamed.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether to call listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage and HydratedBlocStorage renamed\n\n##### Rationale\n\nIn order to improve code reuse between\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) and\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), the concrete default\nstorage implementation was renamed from `HydratedBlocStorage` to\n`HydratedStorage`. In addition, the `HydratedStorage` interface was renamed from\n`HydratedStorage` to `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage decoupled from BlocDelegate\n\n##### Rationale\n\nAs mentioned earlier, `BlocDelegate` was renamed to `BlocObserver` and was set\ndirectly as part of the `bloc` via:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nThe following change was made to:\n\n- Stay consistent with the new bloc observer API\n- Keep the storage scoped to just `HydratedBloc`\n- Decouple the `BlocObserver` from `Storage`\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Simplified Initialization\n\n##### Rationale\n\nPreviously, developers had to manually call\n`super.initialState ?? DefaultInitialState()` in order to setup their\n`HydratedBloc` instances. This is clunky and verbose and also incompatible with\nthe breaking changes to `initialState` in `bloc`. As a result, in v5.0.0\n`HydratedBloc` initialization is identical to normal `Bloc` initialization.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/modeling-state.mdx",
    "content": "---\ntitle: Modeling State\ndescription:\n  An overview of several ways to model states when using package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nThere are many different approaches when it comes to structuring application\nstate. Each has its own advantages and drawbacks. In this section, we'll take a\nlook at several approaches, their pros and cons, and when to use each one.\n\nThe following approaches are simply recommendations and are completely optional.\nFeel free to use whatever approach you prefer. You may find some of the\nexamples/documentation do not follow the approaches mainly for\nsimplicity/conciseness.\n\n:::tip\n\nThe following code snippets are focused on the state structure. In practice, you\nmay also want to:\n\n- Extend `Equatable` from\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- Annotate the class with `@Data()` from\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- Annotate the class with **@immutable** from\n  [`package:meta`](https://pub.dev/packages/meta)\n- Implement a `copyWith` method\n- Use the `const` keyword for constructors\n\n:::\n\n## Concrete Class and Status Enum\n\nThis approach consists of a **single concrete class** for all states along with\nan `enum` representing different statuses. Properties are made nullable and are\nhandled based on the current status. This approach works best for states which\nare not strictly exclusive and/or contain lots of shared properties.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Pros\n\n- **Simple**: Easy to manage a single class and a status enum and all properties\n  are readily accessible.\n- **Concise**: Generally requires fewer lines of code as compared to other\n  approaches.\n\n#### Cons\n\n- **Not Type Safe**: Requires checking the `status` before accessing properties.\n  It's possible to `emit` a malformed state which can lead to bugs. Properties\n  for specific states are nullable, which can be cumbersome to manage and\n  requires either force unwrapping or performing null checks. Some of these cons\n  can be mitigated by writing unit tests and writing specialized, named\n  constructors.\n- **Bloated**: Results in a single state that can become bloated with many\n  properties over time.\n\n#### Verdict\n\nThis approach works best for simple states or when the requirements call for\nstates that aren't exclusive (e.g. showing a snackbar when an error occurs while\nstill showing old data from the last success state). This approach provides\nflexibility and conciseness at the cost of type safety.\n\n## Sealed Class and Subclasses\n\nThis approach consists of a **sealed class** that holds any shared properties\nand multiple subclasses for the separate states. This approach is great for\nseparate, exclusive states.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Pros\n\n- **Type Safe**: The code is compile-safe and it's not possible to accidentally\n  access an invalid property. Each subclass holds its own properties, making it\n  clear which properties belong to which state.\n- **Explicit:** Separates shared properties from state-specific properties.\n- **Exhaustive**: Using a `switch` statement for exhaustiveness checks to ensure\n  that each state is explicitly handled.\n  - If you don't want\n    [exhaustive switching](https://dart.dev/language/branches#exhaustiveness-checking)\n    or want to be able to add subtypes later without breaking the API, use the\n    [final](https://dart.dev/language/class-modifiers#final) modifier.\n  - See the\n    [sealed class documentation](https://dart.dev/language/class-modifiers#sealed)\n    for more details.\n\n#### Cons\n\n- **Verbose**: Requires more code (one base class and a subclass per state).\n  Also may require duplicate code for shared properties across subclasses.\n- **Complex**: Adding new properties requires updating each subclass and the\n  base class, which can be cumbersome and lead to increases in complexity of the\n  state. In addition, may require unnecessary/excessive type checking to access\n  properties.\n\n#### Verdict\n\nThis approach works best for well-defined, exclusive states with unique\nproperties. This approach provides type safety & exhaustiveness checks and\nemphasizes safety over conciseness and simplicity.\n"
  },
  {
    "path": "docs/src/content/docs/naming-conventions.mdx",
    "content": "---\ntitle: Naming Conventions\ndescription: Overview of the recommended naming conventions when using bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nThe following naming conventions are simply recommendations and are completely\noptional. Feel free to use whatever naming conventions you prefer. You may find\nsome of the examples/documentation do not follow the naming conventions mainly\nfor simplicity/conciseness. These conventions are strongly recommended for large\nprojects with multiple developers.\n\n## Event Conventions\n\nEvents should be named in the **past tense** because events are things that have\nalready occurred from the bloc's perspective.\n\n### Anatomy\n\n`BlocSubject` + `Noun (optional)` + `Verb (event)`\n\nInitial load events should follow the convention: `BlocSubject` + `Started`\n\n:::note\n\nThe base event class should be name: `BlocSubject` + `Event`.\n\n:::\n\n### Examples\n\n✅ **Good**\n\n<EventExamplesGood1 />\n\n❌ **Bad**\n\n<EventExamplesBad1 />\n\n## State Conventions\n\nStates should be nouns because a state is just a snapshot at a particular point\nin time. There are two common ways to represent state: using subclasses or using\na single class.\n\n### Anatomy\n\n#### Subclasses\n\n`BlocSubject` + `Verb (action)` + `State`\n\nWhen representing the state as multiple subclasses `State` should be one of the\nfollowing:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nInitial states should follow the convention: `BlocSubject` + `Initial`.\n\n:::\n\n#### Single Class\n\n`BlocSubject` + `State`\n\nWhen representing the state as a single base class an enum named `BlocSubject` +\n`Status` should be used to represent the status of the state:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nThe base state class should always be named: `BlocSubject` + `State`.\n\n:::\n\n### Examples\n\n✅ **Good**\n\n##### Subclasses\n\n<StateExamplesGood1Snippet />\n\n##### Single Class\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Bad**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/architecture.mdx",
    "content": "---\ntitle: Arquitetura\ndescription: Visão geral dos padrões de arquitetura recomendados ao usar bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Arquitetura Bloc](~/assets/concepts/bloc_architecture_full.png)\n\nUsar a biblioteca bloc nos permite separar nossa aplicação em três camadas:\n\n- Apresentação\n- Lógica de Negócios\n- Dados\n  - Repositório\n  - Provedor de Dados\n\nComeçaremos na camada de nível mais baixo (mais distante da interface do\nusuário) e avançaremos até a camada de apresentação.\n\n## Camada de Dados\n\nA responsabilidade da camada de dados é recuperar/manipular dados de uma ou mais\nfontes.\n\nA camada de dados pode ser dividida em duas partes:\n\n- Repositório\n- Provedor de Dados\n\nEsta camada é o nível mais baixo da aplicação e interage com bancos de dados,\nrequisições de rede e outras fontes de dados assíncronas.\n\n### Provedor de Dados\n\nA responsabilidade do provedor de dados é fornecer dados brutos. O provedor de\ndados deve ser genérico e versátil.\n\nO provedor de dados geralmente expõe APIs simples para executar operações\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). Podemos\nter os métodos `createData`, `readData`, `updateData`, e `deleteData` como parte\nda nossa camada de dados.\n\n<DataProviderSnippet />\n\n### Repositório\n\nA camada de repositório é um wrapper em torno de um ou mais provedores de dados\ncom os quais a camada Bloc se comunica.\n\n<RepositorySnippet />\n\nComo pode ver, nossa camada de repositório pode interagir com vários provedores\nde dados e executar transformações nos dados antes de entregar o resultado para\na camada de lógica de negócios.\n\n## Camada de Lógica de Negócios\n\nA responsabilidade da camada de lógica de negócios é responder às entradas da\ncamada de apresentação com novos estados. Esta camada pode depender de um ou\nmais repositórios para recuperar os dados necessários para construir o estado do\naplicativo.\n\nPense na camada de lógica de negócios como a ponte entre a interface do usuário\n(camada de apresentação) e a camada de dados. A camada de lógica de negócios é\nnotificada por eventos/ações da camada de apresentação e então se comunica com o\nrepositório para criar um novo estado para a camada de apresentação consumir.\n\n<BusinessLogicComponentSnippet />\n\n### Comunicação Bloc-to-Bloc\n\nComo os blocs expõem streams, pode ser tentador criar um bloc que ouça outro\nbloc. Você **não** deve fazer isso. Existem alternativas melhores do que\nrecorrer ao código abaixo:\n\n<BlocTightCouplingSnippet />\n\nEmbora o código acima esteja livre de erros (e até mesmo se limpe depois de\nexecutado), ele tem um grande problema: ele cria uma dependência entre dois\nblocs.\n\nEm geral, dependências entre duas entidades na mesma camada arquitetural devem\nser evitadas a todo custo, pois criam um alto acoplamento que é difícil de\nmanter. Como os blocs ficam na camada arquitetural da lógica de negócios, nenhum\nbloc deve saber sobre qualquer outro bloc.\n\n![Camadas de Arquitetura da Aplicação](~/assets/architecture/architecture.png)\n\nUm bloc só deve receber informações por meio de eventos e de repositórios\ninjetados (ou seja, repositórios fornecidos ao bloc em seu construtor).\n\nSe você estiver em uma situação em que um bloc precisa responder a outro bloc,\nvocê tem duas outras opções. Você pode enviar o problema para uma camada acima\n(a camada de apresentação) ou para uma camada abaixo (a camada de domínio).\n\n#### Conectando Blocs com Apresentação\n\nVocê pode usar um `BlocListener` para escutar um bloc e adicionar um evento a\noutro bloc sempre que o primeiro bloc for alterado.\n\n<BlocLooseCouplingPresentationSnippet />\n\nO código acima impede que o `SecondBloc` precise saber algo sobre o `FirstBloc`,\nencorajando o baixo acoplamento. O aplicativo\n[flutter_weather](/pt-br/tutorials/flutter-weather) usa\n[essa técnica](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\npara alterar o tema do aplicativo com base nas informações meteorológicas\nrecebidas.\n\nEm algumas situações, você pode não querer acoplar dois blocs na camada de\napresentação. Em vez disso, pode fazer sentido que dois blocs compartilhem a\nmesma fonte de dados e atualizem sempre que os dados mudarem.\n\n#### Conectando Blocs com Domínio\n\nDois blocs podem escutar um stream de um repositório e atualizar seus estados,\nindependente um do outro, sempre que os dados do repositório mudarem. Usar\nrepositórios reativos para manter o estado sincronizado é comum em aplicativos\nempresariais de larga escala.\n\nPrimeiro, crie ou use um repositório que forneça um `Stream` de dados. Por\nexemplo, o seguinte repositório expõe um stream interminável das mesmas ideias\nde aplicativos:\n\n<AppIdeasRepositorySnippet />\n\nO mesmo repositório pode ser injetado em cada bloc que precisa reagir a novas\nideias de aplicativo. Abaixo está um `AppIdeaRankingBloc` que produz um estado\npara cada ideia de aplicativo recebida do repositório acima:\n\n<AppIdeaRankingBlocSnippet />\n\nPara saber mais sobre como usar streams com Bloc, consulte\n[Como usar o Bloc com streams e concorrência](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Camada de Apresentação\n\nA responsabilidade da camada de apresentação é resolver como se renderizar com\nbase em um ou mais estados do bloc. Além disso, ela deve manipular as entradas\ndo usuário e os eventos do ciclo de vida do aplicativo.\n\nA maioria dos fluxos de aplicativos começa com um evento `AppStart` que aciona a\naplicação para buscar alguns dados para apresentar ao usuário.\n\nNesse cenário, a camada de apresentação adicionaria um evento `AppStart`.\n\nAlém disso, a camada de apresentação terá que descobrir o que renderizar na tela\ncom base no estado da camada do bloc.\n\n<PresentationComponentSnippet />\n\nAté agora, embora tenhamos alguns trechos de código, tudo isso tem sido muito de\nalto nível. Na seção tutorial, vamos juntar tudo isso enquanto construímos\nvários aplicativos de exemplo diferentes.\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/bloc-concepts.mdx",
    "content": "---\ntitle: Conceitos do Bloc\ndescription: Uma visão geral dos conceitos básicos do package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nPor favor, certifique-se de ler atentamente as seções a seguir antes de\ntrabalhar com [`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nExistem vários conceitos básicos que são essenciais para entender como usar o\npacote bloc.\n\nNas próximas seções, discutiremos cada um deles em detalhes e veremos como eles\nse aplicariam a um aplicativo de contador.\n\n## Streams\n\n:::note\n\nConfira a\n[Documentação oficial do Dart](https://dart.dev/tutorials/language/streams) para\nmais informações sobre `Streams`.\n\n:::\n\nUm stream é uma sequência de dados asincronos.\n\nPara usar a biblioteca bloc, é fundamental ter um compreensão básica de\n`Streams` e como eles funcionam.\n\nSe você não está familiarizado com `Streams`, basta pensar em um tubo com água\nfluindo por dele. O tubo é o `Stream` e a água são os dados assíncronos.\n\nNós podemos criar um `Stream` em Dart escrevendo uma função `async*` (gerador de\nasync).\n\n<CountStreamSnippet />\n\nMarcando a função como `async*` podemos usar a palavra-chave `yield` para\nretornar um `Stream` de dados. No exemplo acima, retornamos um `Stream` de\ninteiros até o parâmetro inteiro `max`.\n\nToda vez que colocamos um `yield` em uma função `async*` estamos inserindo esse\npedaço de dados no `Stream`.\n\nPodemos consumir o `Stream` acima em várias formas. Se quisermos escrever uma\nfunção para retornar a soma de um `Stream` de inteiros, poderia escrever algo\ncomo:\n\n<SumStreamSnippet />\n\nMarcando a função acima como `async` podemos usar a palavra-chave `await` para\nretornar um `Future` de inteiros. Neste exemplo, esperamos cada valor do stream\ne retornamos a soma de todos os inteiros no stream.\n\nPodemos juntar todo isso assim:\n\n<StreamsMainSnippet />\n\nAgora que temos uma compreensão básica de como `Streams` funcionam em Dart,\nestamos prontos para aprender sobre o componente central do pacote bloc: o\n`Cubit`.\n\n## Cubit\n\nUm `Cubit` é uma classe que estende `BlocBase` e pode ser estendida para\ngerenciar qualquer tipo de estado.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\nUm `Cubit` pode expor funções que podem ser chamadas para disparar mudanças de\nestado.\n\nEstados são a saida de um `Cubit` e representam uma parte do estado da sua\naplicação. Os componentes de IU podem ser notificados pelos estados e se\nredesenharem com base no estado atual.\n\n:::note\n\nPara mais informações sobre as origens do `Cubit`, consulte o seguinte questão\n[aqui](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Criando um Cubit\n\nPodemos criar um `CounterCubit` como:\n\n<CounterCubitSnippet />\n\nAo criar um `Cubit`, precisamos definir o tipo de estado que o `Cubit` vai\ngerenciar. No caso do `CounterCubit` acima, o estado é representado por um `int`\nmas em casos mais complexos, pode ser necessário usar uma `class` ao invés de um\ntipo primitivo.\n\nA segunda coisa que precisamos fazer ao criar um `Cubit` é especificar o estado\ninicial. Podemos fazer isso chamando `super` com o valor do estado inicial. No\nexemplo acima, definimos o estado inicial para `0` internamente, mas também\npodemos permitir que o `Cubit` seja mais flexível aceitando um valor externo:\n\n<CounterCubitInitialStateSnippet />\n\nIsto nos permitiria criar instâncias de `CounterCubit` com diferentes estados\niniciais, como:\n\n<CounterCubitInstantiationSnippet />\n\n### Mudanças de estado do Cubit\n\nCada `Cubit` tem a capacidade de emitir um novo estado via `emit`.\n\n<CounterCubitIncrementSnippet />\n\nNo trecho acima, o `CouterCubit` está expondo um método publico chamado\n`increment` que pode ser chamado externamente para notificar o `CounterCubit`\npara incrementar seu estado. Quando `increment` é chamado, podemos acessar o\nestado atual do `Cubit` através do getter `state` e `emit` um novo estado\nadicionando 1 ao estado atual.\n\n:::caution\n\nO método `emit` é protegido, o que significa que deve ser usado apenas dentro de\num `Cubit`.\n\n:::\n\n### Usando um Cubit\n\nAgora podemos pegar o `CounterCubit` implementado e coloca-lo em uso!\n\n#### Uso Básico\n\n<CounterCubitBasicUsageSnippet />\n\nNo trecho acima, começamos criando uma instância de um `CounterCubit`. Em\nseguida, imprimimos o estado atual do cubit, que é o estado inicial (já que\nnenhum estado novo foi emitido ainda). Em seguida, chamamos a função `increment`\npara disparar uma mudança de estado. Por fim, imprimimos o estado do `Cubit`\nnovamente que passou de `0` para `1` e chamamos `close` no `Cubit` para fechar o\nfluxo de dados interno.\n\n#### Uso de Stream\n\n`Cubit` expõe um `Stream` que nos permite receber atualizações de estado em\ntempo real:\n\n<CounterCubitStreamUsageSnippet />\n\nNo trecho acima, estamos assinando o `CounterCubit` e chamando print a cada\nmudança de estado. Em seguida, invocamos a função `increment` que emitirá um\nnovo estado. Por fim, estamos chamando `cancel` na `subscription` quando não\nqueremos mais receber atualizações e fechando o `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` é adicionado a este exemplo para evitar\ncancelar a assinatura imediatamente.\n\n:::\n\n:::caution\n\nSomente as mudanças de estado subsequentes serão recebidas quando chamamos\n`listen` em um `Cubit`.\n\n:::\n\n### Observando um Cubit\n\nQuando um `Cubit` emite um novo estado, ocorre uma `Change`. Podemos observar\ntodas as mudanças em um `Cubit` substituindo `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nPodemos então interagir com o `Cubit` e observar todas as alterações geradas no\nconsole.\n\n<CounterCubitOnChangeUsageSnippet />\n\nO exemplo acima produziria:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\nUma `Change` ocorre logo antes do estado do `Cubit` ser atualizado. Uma `Change`\nconsiste em no `currentState` e no `nextState`.\n\n:::\n\n#### BlocObserver\n\nUm bônus adicional de usar a biblioteca bloc é que podemos ter acesso a todas as\n`Changes` em um lugar. Embora nessa aplicação tenhamos apenas um `Cubit`, é\nmuito comum em aplicações maiores ter muitos `Cubits` gerenciando diferentes\npartes do estado da aplicação.\n\nSe quisermos fazer algo em resposta a todas as `Changes`, podemos simplesmente\ncriar nosso próprio `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nTudo que precisamos fazer é extender `BlocObserver` e substituir o método\n`onChange`.\n\n:::\n\nPara usar o `SimpleBlocObserver`, precisamos apenas ajustar a função `main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nO trecho acima produziria então:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nA sobrescrita interna do `onChange` é chamada primeiro, que chama\n`super.onChange` notificando o `onChange` no `BlocObserver`.\n\n:::\n\n:::tip\n\nNo `BlocObserver` temos acesso à instância do `Cubit` além da própria `Change`.\n\n:::\n\n### Tratamento de Erros do Cubit\n\nTodo `Cubit` tem um método `addError` que pode ser usado para indicar que\nocorreu um erro.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` pode ser sobrescrito dentro do `Cubit` para manipular todos os erros\nde um `Cubit` específico.\n\n:::\n\n`onError` também pode ser sobrescrito no `BlocObserver` para manipular todos os\nerros relatados globalmente.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nSe rodarmos o mesmo programa novamente, devemos ver o seguinte output:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\nUm `Bloc` é uma classe mais avançada que depende de `eventos` para acionar\nmudanças de `estado` em vez de funções. `Bloc` também estende `BlocBase`, o que\nsignifica que ele tem uma API pública semelhante ao `Cubit`. No entanto, em vez\nde chamar uma `função` em um `Bloc` e emitir diretamente um novo `estado`, os\n`Blocs` recebem `eventos` e convertem os `eventos` de entrada em `estados` de\nsaída.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Criando um Bloc\n\nCriar um `Bloc` é semelhante a criar um `Cubit`, exceto que além de definir o\nestado que iremos gerenciar, também devemos definir o evento que o `Bloc` poderá\nprocessar.\n\nEventos são a entrada para um Bloc. Eles normalmente são adicionados em resposta\na interações do usuário, como botões pressionados ou eventos do ciclo de vida,\ncomo carregamentos de página.\n\n<CounterBlocSnippet />\n\nAssim como ao criar o `CounterCubit`, devemos especificar um estado inicial\npassando-o para a superclasse via `super`.\n\n### Mundanças de Estado do Bloc\n\n`Bloc` exige que registremos manipuladores de eventos via a API `on<Event>`, em\nvez de funções no `Cubit`. Um manipulador de eventos é responsável por converter\nquaisquer eventos de entrada em zero ou mais estados de saída.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\nUm `EventHandler` tem acesso ao evento adicionado, bem como a um `Emitter`, que\npode ser usado para emitir zero ou mais estados em resposta ao evento recebido.\n\n:::\n\nPodemos então atualizar o `EventHandler` para lidar com o evento\n`CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nNo trecho acima, registramos um `EventHandler` para gerenciar todos os eventos\n`CounterIncrementPressed`. Para cada evento `CounterIncrementPressed` recebido,\npodemos acessar o estado atual do bloc via o getter `state` e `emit(state + 1)`.\n\n:::note\n\nComo a classe `Bloc` estende `BlocBase`, temos acesso ao estado atual do bloc em\nqualquer momento via o getter `state`, assim como no `Cubit`.\n\n:::\n\n:::caution\n\nBlocs nunca devem `emitir` novos estados diretamente. Em vez disso, toda mudança\nde estado deve ser emitida em resposta a um evento de entrada dentro de um\n`EventHandler`.\n\n:::\n\n:::caution\n\nAmbos blocs e cubits irão ignorar estados duplicados. Se emitirmos\n`State nextState` onde `state == nextState`, então nenhuma mudança de estado irá\nocorrer.\n\n:::\n\n### Usando um Bloc\n\nNeste ponto, podemos criar uma instância do nosso `CounterBloc` e colocá-lo em\nuso!\n\n#### Uso Básico\n\n<CounterBlocUsageSnippet />\n\nNo trecho acima, começamos criando uma instância do nosso `CounterBloc`. Em\nseguida, imprimimos o estado atual do `Bloc` que é o estado inicial (já que\nnenhum estado novo foi emitido ainda). Em seguida, adicionamos o evento\n`CounterIncrementPressed` para disparar uma mudança de estado. Por fim,\nimprimimos o estado do `Bloc` novamente que foi de `0` para `1` e chamamos\n`close` no `Bloc` para fechar o fluxo de dados interno.\n\n:::note\n\n`await Future.delayed(Duration.zero)` é adicionado para garantir que aguardamos\na próxima iteração do event-loop (permitindo que o `EventHandler` processe o\nevento).\n\n:::\n\n#### Uso de Stream\n\nAssim como com `Cubit`, um `Bloc` é um tipo especial de `Stream`, o que\nsignifica que também podemos assinar um `Bloc` para atualizações em tempo real\nde seu estado:\n\n<CounterBlocStreamUsageSnippet />\n\nNo trecho acima, estamos assinando o `CounterBloc` e chamando print a cada\nmudança de estado. Em seguida, adicionamos o evento `CounterIncrementPressed`,\nque aciona o `EventHandler` `on<CounterIncrementPressed>` e emite um novo\nestado. Por fim, estamos chamando o `cancel` da assinatura quando não queremos\nmais receber atualizações e fechando o `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` é adicionado neste exemplo para evitar o\ncancelamento imediato da assinatura.\n\n:::\n\n### Observando um Bloc\n\nComo o `Bloc` estende `BlocBase`, podemos observar todas as mudanças de estado\nde um `Bloc` usando `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nPodemos então atualizar `main.dart` para:\n\n<CounterBlocOnChangeUsageSnippet />\n\nAgora, se executarmos o trecho acima, a saída será:\n\n<CounterBlocOnChangeOutputSnippet />\n\nUm fator-chave de diferenciação entre `Bloc` e `Cubit` é que, como o `Bloc` é\norientado a eventos, nós também podemos capturar informações sobre o que\ndisparou a mudança de estado.\n\nPodemos fazer isso substituindo `onTransition`.\n\nA mudança de um estado para outro é chamada de `Transition`. Uma `Transition`\nconsiste no estado atual, no evento e no próximo estado.\n\n<CounterBlocOnTransitionSnippet />\n\nSe executarmos novamente o mesmo trecho `main.dart` de antes, veremos a seguinte\nsaída:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` é invocado antes de `onChange` e contém o evento que acionou a\nmudança de `currentState` para `nextState`.\n\n:::\n\n#### BlocObserver\n\nAssim como antes, podemos substituir o `onTransition` em um `BlocObserver`\npersonalizado para observar todas as transições que ocorrem em um único lugar.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nPodemos inicializar o `SimpleBlocObserver` como antes:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nAgora, se executarmos o trecho acima, a saída será semelhante a:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` é invocado primeiro (local antes de global), seguido por\n`onChange`.\n\n:::\n\nOutro recurso exclusivo das instâncias do `Bloc` é que elas nos permitem\nsobrescrever `onEvent`, que é chamado sempre que um novo evento é adicionado ao\n`Bloc`. Assim como com `onChange` e `onTransition`, `onEvent` pode ser\nsobrescrito localmente e também globalmente.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nPodemos executar o mesmo `main.dart` de antes e devemos ver a seguinte saída:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` é chamado assim que o evento é adicionado. O `onEvent` local é\ninvocado antes do `onEvent` global no `BlocObserver`.\n\n:::\n\n### Tratamento de Erros no Bloc\n\nAssim como no `Cubit`, cada `Bloc` tem um método `addError` e `onError`. Podemos\nindicar que ocorreu um erro chamando `addError` de qualquer lugar dentro do\nnosso `Bloc`. Podemos então reagir a todos os erros sobrescrevendo `onError`\nassim como no `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nSe executarmos novamente o mesmo `main.dart` de antes, podemos ver como fica\nquando um erro é reportado:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nO `onError` local é invocado primeiro, seguido pelo `onError` global no\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` e `onChange` funcionam exatamente da mesma maneira para instâncias\n`Bloc` e `Cubit`.\n\n:::\n\n:::caution\n\nQuaisquer exceções não tratadas que ocorram em um `EventHandler` também são\nreportadas no `onError`.\n\n:::\n\n## Cubit vs. Bloc\n\nAgora que cobrimos os conceitos básicos das classes `Cubit` e `Bloc`, você pode\nestar se perguntando quando deve usar `Cubit` e quando deve usar `Bloc`.\n\n### Vantagens do Cubit\n\n#### Simplicidade\n\nUma das maiores vantagens de usar o `Cubit` é a simplicidade. Ao criar um\n`Cubit`, precisamos apenas definir o estado, bem como as funções que queremos\nexpor para alterar o estado. Em comparação, ao criar um `Bloc`, temos de definir\nos estados, os eventos e a implementação do `EventHandler`. Isso torna o Cubit\nmais fácil de entender e há menos código envolvido.\n\nAgora vamos dar uma olhada nas duas implementações do contador:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nA implementação do `Cubit` é mais concisa e, em vez de definir eventos\nseparadamente, as funções agem como eventos. Além disso, ao usar um `Cubit`,\npodemos simplesmente chamar `emit` de qualquer lugar para disparar uma mudança\nde estado.\n\n### Vantagens do Bloc\n\n#### Rastreabilidade\n\nUma das maiores vantagens de usar o `Bloc` é conhecer a sequência de alterações\nde estado, bem como o que exatamente desencadeou essas alterações. Para o estado\nque é essencial para a funcionalidade de um aplicativo, pode ser muito vantajoso\nusar uma abordagem mais orientada a eventos para capturar todos os eventos, além\ndas mudanças de estado.\n\nUm caso de uso comum pode ser gerenciar `AuthenticationState`. Para simplificar,\ndigamos que podemos representar `AuthenticationState` por meio de um `enum`:\n\n<AuthenticationStateSnippet />\n\nPode haver muitos motivos pelos quais o estado do aplicativo pode mudar de\n`authenticated` para não `unauthenticated`. Por exemplo, o usuário pode ter\ntocado no botão de logout e solicitado que fosse desconectado do aplicativo. Por\noutro lado, talvez o token de acesso do usuário tenha sido revogado e ele foi\ndesconectado à força. Ao usar o `Bloc`, podemos rastrear claramente como o\nestado do aplicativo chegou a um determinado valor.\n\n<AuthenticationTransitionSnippet />\n\nA `Transition` acima nos dá todas as informações que precisamos para entender\npor que o estado mudou. Se tivéssemos usado um `Cubit` para gerenciar o\n`AuthenticationState`, nossos logs ficariam assim:\n\n<AuthenticationChangeSnippet />\n\nIsso nos diz que o usuário foi desconectado, mas não explica o motivo, o que\npode ser crítico para a depuração e compreensão de como o estado do aplicativo\nestá mudando ao longo do tempo.\n\n#### Transformações Avançadas de Eventos\n\nOutra área em que o `Bloc` se destaca sobre o `Cubit` é quando precisamos tirar\nvantagem de operadores reativos, como `buffer`, `debounceTime`, `throttle`, etc.\n\nO `Bloc` tem um coletor de eventos que nos permite controlar e transformar o\nfluxo de entrada de eventos.\n\nPor exemplo, se estivéssemos criando uma pesquisa em tempo real, provavelmente\ndesejaríamos reduzir as solicitações para o backend para evitar limitações de\ntaxa e também para reduzir custos/carga no backend.\n\nCom o `Bloc`, podemos fornecer um `EventTransformer` personalizado para alterar\na maneira como os eventos recebidos são processados pelo `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nCom o código acima, podemos facilmente reduzir o retorno de eventos recebidos\ncom muito pouco código adicional.\n\n:::tip\n\nConfira o\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) para um\nconjunto opinativo de transformadores de eventos.\n\n:::\n\nSe não tiver certeza sobre qual usar, comece com o `Cubit` e depois refatore ou\nexpanda para um `Bloc`, conforme necessário.\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Conceitos do Flutter Bloc\ndescription: Uma visão geral dos conceitos principais do package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nLeia atentamente as seções a seguir antes de trabalhar com\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nTodos os widgets exportados pelo pacote `flutter_bloc` integram-se com\ninstâncias do `Cubit` e do `Bloc`.\n\n:::\n\n## Widgets do Bloc\n\n### BlocBuilder\n\n**BlocBuilder** é um widget do Flutter que requer uma função `Bloc` e uma função\n`builder`. `BlocBuilder` lida com a construção do widget em resposta a novos\nestados. `BlocBuilder` é muito semelhante a `StreamBuilder`, mas possui uma API\nmais simples para reduzir a quantidade de código boilerplate necessária. A\nfunção `builder` será potencialmente chamada muitas vezes e deve ser uma\n[função pura](https://en.wikipedia.org/wiki/Pure_function) que retorna um widget\nem resposta ao estado.\n\nVeja `BlocListener` se quiser \"fazer\" algo em resposta a mudanças de estado,\ncomo navegação, exibição de uma caixa de diálogo, etc...\n\nSe o parâmetro `bloc` for omitido, o `BlocBuilder` executará automaticamente uma\npesquisa usando `BlocProvider` e o `BuildContext` atual.\n\n<BlocBuilderSnippet />\n\nEspecifique o bloc somente se desejar fornecer um bloc que terá como escopo um\núnico widget e não seja acessível por meio de um `BlocProvider` pai e do\n`BuildContext` atual.\n\n<BlocBuilderExplicitBlocSnippet />\n\nPara um controle mais preciso sobre quando a função `builder` é chamada, um\n`buildWhen` opcional pode ser fornecido. O `buildWhen` pega o estado do bloc\nanterior e o estado do bloc atual e retorna um booleano. Se `buildWhen` retornar\ntrue, o `builder` será chamado com `state` e o widget será reconstruído. Se\n`buildWhen` retornar false, `builder` não será chamado com `state` e nenhuma\nreconstrução ocorrerá.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** é um widget do Flutter análogo ao `BlocBuilder`, mas permite\nque os desenvolvedores filtrem atualizações selecionando um novo valor com base\nno estado atual do bloc. Builds desnecessários são evitados se o valor\nselecionado não mudar. O valor selecionado deve ser imutável para que\n`BlocSelector` determine com precisão se `builder` deve ser chamado novamente.\n\nSe o parâmetro `bloc` for omitido, `BlocSelector` executará automaticamente uma\npesquisa usando `BlocProvider` e o `BuildContext` atual.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** é um widget do Flutter que fornece um bloc para seus filhos por\nmeio de `BlocProvider.of<T>(context)`. Ele é usado como um widget de injeção de\ndependência (DI) para que uma única instância de um bloc possa ser fornecida a\nvários widgets dentro de uma subárvore.\n\nNa maioria dos casos, `BlocProvider` deve ser usado para criar novos blocs que\nserão disponibilizados para o restante da subárvore. Nesse caso, como\n`BlocProvider` é responsável pela criação do bloc, ele tratará automaticamente\ndo fechamento do bloc.\n\n<BlocProviderSnippet />\n\nPor padrão, `BlocProvider` criará o bloc preguiçosamente, o que significa que\n`create` será executado quando o bloc for consultado via\n`BlocProvider.of<BlocA>(context)`.\n\nPara substituir esse comportamento e forçar a execução imediata de `create`,\n`lazy` pode ser definido como `false`.\n\n<BlocProviderEagerSnippet />\n\nEm alguns casos, `BlocProvider` pode ser usado para fornecer um bloc existente a\numa nova parte da árvore de widgets. Isso será mais comumente usado quando um\nbloc existente precisa ser disponibilizado para uma nova rota. Nesse caso,\n`BlocProvider` não fechará o bloc automaticamente, pois não o criou.\n\n<BlocProviderValueSnippet />\n\nentão, de `ChildA` ou `ScreenA`, podemos recuperar `BlocA` com:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** é um widget do Flutter que mescla vários widgets\n`BlocProvider` em um. `MultiBlocProvider` melhora a legibilidade e elimina a\nnecessidade de aninhar vários `BlocProviders`. Usando `MultiBlocProvider`,\npodemos ir de:\n\n<NestedBlocProviderSnippet />\n\npara:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nQuando um `BlocProvider` é definido dentro do contexto de um\n`MultiBlocProvider`, qualquer `child` será ignorado.\n\n:::\n\n### BlocListener\n\n**BlocListener** é um widget Flutter que recebe um `BlocWidgetListener` e um\n`bloc` opcional e invoca o `listener` em resposta a mudanças de estado no bloc.\nEle deve ser usado para funcionalidades que precisam ocorrer uma vez a cada\nmudança de estado, como navegação, exibição de um `SnackBar`, exibição de um\n`Dialog`, etc...\n\n`listener` é chamado apenas uma vez para cada mudança de estado (**NÃO**\nincluindo o estado inicial), ao contrário de `builder` em `BlocBuilder`, e é uma\nfunção `void`.\n\nSe o parâmetro `bloc` for omitido, `BlocListener` executará automaticamente uma\npesquisa usando `BlocProvider` e o `BuildContext` atual.\n\n<BlocListenerSnippet />\n\nEspecifique o bloc somente se desejar fornecer um bloc que não seja acessível\nvia `BlocProvider` e o `BuildContext` atual.\n\n<BlocListenerExplicitBlocSnippet />\n\nPara um controle mais preciso sobre quando a função `listener` é chamada, um\n`listenWhen` opcional pode ser fornecido. `listenWhen` recebe o estado do bloc\nanterior e o estado do bloc atual e retorna um booleano. Se `listenWhen`\nretornar verdadeiro, `listener` será chamado com `state`. Se `listenWhen`\nretornar falso, `listener` não será chamado com `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** é um widget do Flutter que mescla vários widgets\n`BlocListener` em um. `MultiBlocListener` melhora a legibilidade e elimina a\nnecessidade de aninhar vários `BlocListeners`. Usando `MultiBlocListener`,\npodemos ir de:\n\n<NestedBlocListenerSnippet />\n\npara:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nQuando um `BlocListener` é definido dentro do contexto de um\n`MultiBlocListener`, qualquer `child` será ignorado.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** expõe um `builder` e um `listener` para reagir a novos estados.\n`BlocConsumer` é análogo a um `BlocListener` e `BlocBuilder` aninhados, mas\nreduz a quantidade de boilerplate necessária. `BlocConsumer` deve ser usado\napenas quando for necessário reconstruir a UI e executar outras reações a\nmudanças de estado no `bloc`. `BlocConsumer` recebe um `BlocWidgetBuilder` e um\n`BlocWidgetListener` obrigatórios e um `bloc`, `BlocBuilderCondition` e\n`BlocListenerCondition` opcionais.\n\nSe o parâmetro `bloc` for omitido, `BlocConsumer` executará automaticamente uma\npesquisa usando `BlocProvider` e o `BuildContext` atual.\n\n<BlocConsumerSnippet />\n\nOs métodos opcionais `listenWhen` e `buildWhen` podem ser implementados para um\ncontrole mais granular sobre quando `listener` e `builder` são chamados.\n`listenWhen` e `buildWhen` serão invocados a cada alteração de `state` do\n`bloc`. Cada um deles assume o `state` anterior e o `state` atual e deve\nretornar um `bool` que determina se a função `builder` e/ou `listener` será\ninvocada. O `state` anterior será inicializado com o `state` do `bloc` quando o\n`BlocConsumer` for inicializado. `listenWhen` e `buildWhen` são opcionais e, se\nnão forem implementados, o valor padrão será `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** é um widget do Flutter que fornece um repositório para\nseus filhos por meio de `RepositoryProvider.of<T>(context)`. Ele é usado como um\nwidget de injeção de dependência (DI) para que uma única instância de um\nrepositório possa ser fornecida a vários widgets dentro de uma subárvore.\n`BlocProvider` deve ser usado para fornecer blocs, enquanto `RepositoryProvider`\ndeve ser usado apenas para repositórios.\n\n<RepositoryProviderSnippet />\n\nentão de `ChildA` podemos recuperar a instância `Repository` com:\n\n<RepositoryProviderLookupSnippet />\n\nRepositórios que gerenciam recursos que devem ser descartados podem fazê-lo por\nmeio do callback `dispose`:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** é um widget Flutter que mescla vários widgets\n`RepositoryProvider` em um. `MultiRepositoryProvider` melhora a legibilidade e\nelimina a necessidade de aninhar vários `RepositoryProvider`. Usando\n`MultiRepositoryProvider``, podemos ir de:\n\n<NestedRepositoryProviderSnippet />\n\npara:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nQuando um `RepositoryProvider` é definido dentro do contexto de um\n`MultiRepositoryProvider`, qualquer `child` será ignorado.\n\n:::\n\n## Uso do BlocProvider\n\nVamos dar uma olhada em como usar `BlocProvider` para fornecer um `CounterBloc`\npara uma `CounterPage` e reagir a mudanças de estado com `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nNeste ponto, separamos com sucesso nossa camada de apresentação da nossa camada\nde lógica de negócios. Observe que o widget `CounterPage` não sabe nada sobre o\nque acontece quando um usuário toca nos botões. O widget simplesmente informa ao\n`CounterBloc` que o usuário pressionou o botão de incremento ou decremento.\n\n## Uso do RepositoryProvider\n\nVamos dar uma olhada em como usar `RepositoryProvider` dentro do contexto do\nexemplo [`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nEm nosso `main.dart`, chamamos `runApp` com nosso widget `WeatherApp`.\n\n<WeatherMainSnippet />\n\nInjetaremos nossa instância `WeatherRepository` em nossa árvore de widgets via\n`RepositoryProvider`.\n\nAo instanciar um bloc, podemos acessar a instância de um repositório via\n`context.read` e injetar o repositório no bloc via construtor.\n\n<WeatherAppSnippet />\n\n:::tip\n\nSe você tiver mais de um repositório, poderá usar `MultiRepositoryProvider` para\nfornecer múltiplas instâncias de repositório para a subárvore.\n\n:::\n\n:::note\n\nUse o retorno de chamada `dispose` para lidar com a liberação de quaisquer\nrecursos quando o `RepositoryProvider` for desmontado.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Extension Methods\n\n[Métodos de extensão](https://dart.dev/guides/language/extension-methods),\nintroduzidos no Dart 2.7, são uma forma de adicionar funcionalidades a\nbibliotecas existentes. Nesta seção, veremos os métodos de extensão incluídos em\n`package:flutter_bloc` e como eles podem ser usados.\n\n`flutter_bloc` tem uma dependência de\n[package:provider](https://pub.dev/packages/provider) que simplifica o uso de\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nInternamente, `package:flutter_bloc` usa `package:provider` para implementar: os\nwidgets `BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` e\n`MultiRepositoryProvider`. `package:flutter_bloc` exporta as extensões\n`ReadContext`, `WatchContext` e `SelectContext` do `package:provider`.\n\n:::note\n\nSaiba mais sobre [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` procura a instância ancestral mais próxima do tipo `T` e é\nfuncionalmente equivalente a `BlocProvider.of<T>(context)`. `context.read` é\nmais comumente usado para recuperar uma instância de bloc para adicionar um\nevento dentro de callbacks `onPressed`.\n\n:::note\n\n`context.read<T>()` não escuta `T` -- se o `Object` fornecido do tipo `T` mudar,\n`context.read` não acionará uma reconstrução do widget.\n\n:::\n\n#### Uso\n\n✅ **USE** `context.read` para adicionar eventos em retornos de chamada.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **EVITE** usar `context.read` para recuperar o estado dentro de um método\n`build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nO uso acima é propenso a erros porque o widget `Text` não será reconstruído se o\nestado do bloc mudar.\n\n:::caution\n\nUse `BlocBuilder` ou `context.watch` para reconstruir em resposta a mudanças de\nestado.\n\n:::\n\n### context.watch\n\nAssim como `context.read<T>()`, `context.watch<T>()` fornece a instância\nancestral mais próxima do tipo `T`, porém também monitora alterações na\ninstância. É funcionalmente equivalente a\n`BlocProvider.of<T>(context, listen: true)`.\n\nSe o `Object` fornecido do tipo `T` for alterado, `context.watch` acionará uma\nreconstrução.\n\n:::caution\n\n`context.watch` só é acessível dentro do método `build` de uma classe\n`StatelessWidget` ou `State`.\n\n:::\n\n#### Uso\n\n✅ **USE** `BlocBuilder` em vez de `context.watch` para delimitar explicitamente\nreconstruções.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Sempre que o estado muda, somente o Text é reconstruído.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nComo alternativa, use um `Builder` para definir o escopo das reconstruções.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Sempre que o estado muda, somente o Text é reconstruído.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **USE** `Builder` e `context.watch` como `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // retorna um Widget que depende do estado de BlocA, BlocB e BlocC\n  }\n);\n```\n\n❌ **EVITE** usar `context.watch` quando o widget pai no método `build` não\ndepende do estado.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Sempre que o estado muda, o MaterialApp é reconstruído\n  // mesmo que seja usado apenas no widget Text.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsar `context.watch` na raiz do método `build` resultará na reconstrução de todo\no widget quando o estado do bloc mudar.\n\n:::\n\n### context.select\n\nAssim como `context.watch<T>()`, `context.select<T, R>(R function(T value))`\nfornece a instância ancestral mais próxima do tipo `T` e monitora as alterações\nem `T`. Ao contrário de `context.watch`, `context.select` permite que você\nmonitore as alterações em uma parte menor de um estado.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nO procedimento acima só reconstruirá o widget quando a propriedade `name` do\nestado do `ProfileBloc` mudar.\n\n#### Uso\n\n✅ **USE** `BlocSelector` em vez de `context.select` para delimitar\nexplicitamente reconstruções.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Sempre que o state.name muda, somente o Text é reconstruído.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nComo alternativa, use um `Builder` para definir o escopo das reconstruções.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Sempre que state.name muda, somente o Text é reconstruído.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **EVITE** usar `context.select` quando o widget pai em um método de\nconstrução não depende do estado.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Sempre que o state.value muda, o MaterialApp é reconstruído\n  // mesmo que seja usado apenas no widget Text.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nUsar `context.select` na raiz do método `build` resultará na reconstrução de\ntodo o widget quando a seleção for alterada.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/getting-started.mdx",
    "content": "---\ntitle: Começando\ndescription: Tudo o que você precisa para começar a construir com o Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Pacotes\n\nO ecossistema do bloc consiste nos vários pacotes listados abaixo:\n\n| Pacote                                                                                     | Descrição                    | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | Componentes AngularDart      | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | APIs Centrais do Dart        | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Transformadores de Eventos   | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter                | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | APIs de Teste                | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools           | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Widgets do Flutter           | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Suporte a Cache/Persistência | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Suporte a Desfazer/Refazer   | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Instalação\n\n<InstallationTabs />\n\n:::note\n\nPara começar a usar o bloc, é necessário ter o\n[Dart SDK](https://dart.dev/get-dart) instalado em seu computador.\n\n:::\n\n## Importes\n\nAgora que instalamos o bloc com sucesso, podemos criar nosso `main.dart` e\nimportar o respectivo pacote `bloc`.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Biblioteca de Gerenciamento de Estado Bloc\ndescription:\n  Documentação oficial da biblioteca de gerenciamento de estado bloc. Suporte\n  para Dart, Flutter e AngularDart. Inclui exemplos e tutoriais.\nbanner:\n  content: |\n    ✨ Visite a\n    <a href=\"https://shop.bloclibrary.dev\">loja Bloc</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v8.1.3</span></sup>\n  tagline: Uma biblioteca de gerenciamento de estado previsível para Dart.\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Começar\n      link: /pt-br/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Ver no GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid\n\tsponsoredBy=\"Patrocinado com 💖 por\"\n\tbecomeASponsor=\"Seja um patrocinador\"\n/>\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Começar\" icon=\"rocket\">\n  ```sh\n  # Adicione o bloc ao seu projeto.\n  dart pub add bloc\n  ```\n\nNosso [guia de iniciação](/pt-br/getting-started) contém instruções passo a\npasso sobre como começar a utilizar o Bloc em apenas alguns minutos.\n\n</SplitCard>\n\n<Card title=\"Fazer uma visita guiada\" icon=\"star\">\n\tComplete [os tutoriais oficiais](/pt-br/tutorials/flutter-counter) para\n\taprender as práticas recomendadas e criar uma variedade de aplicativos\n\tdiferentes desenvolvidos pelo Bloc.\n</Card>\n\n<Card title=\"Construir com Bloc\" icon=\"laptop\">\n\tExplore [amostras de\n\taplicações](https://github.com/felangel/bloc/tree/master/examples) de alta\n\tqualidade e totalmente testadas, como contador, temporizador, lista infinita,\n\tprevisão do tempo, lista de tarefas e muito mais!\n</Card>\n\n<ListCard title=\"Aprender\" icon=\"open-book\">\n\n    - [Por que Bloc?](/pt-br/why-bloc)\n    - [Conceitos Básicos](/pt-br/bloc-concepts)\n    - [Arquitetura](/pt-br/architecture)\n    - [Testes](/pt-br/testing)\n    - [Convenções de Nomenclatura](/pt-br/naming-conventions)\n    - [FAQs](/pt-br/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Integrações\" icon=\"puzzle\">\n    - [Integração com VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Integração com IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Integração com Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Integração com Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Templates Customizados](https://brickhub.dev/search?q=bloc)\n    - [Ferramentas de Desenvolvedor](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"Junte-se ao nosso Discord\" />\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/modeling-state.mdx",
    "content": "---\ntitle: Modelagem de Estado\ndescription:\n  Uma visão geral das várias formas de modelar estados quando se utiliza\n  package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nHá muitas abordagens diferentes quando se trata de estruturar o estado do\naplicativo. Cada uma tem suas próprias vantagens e desvantagens. Nesta seção,\ndaremos uma olhada em várias abordagens, seus prós e contras, e quando usar cada\numa delas.\n\nAs abordagens a seguir são simplesmente recomendações e são completamente\nopcionais. Sinta-se à vontade para usar qualquer abordagem que preferir. Você\npode descobrir que alguns dos exemplos/documentação não seguem estas abordagens\nprincipalmente por simplicidade/concisão.\n\n:::tip\n\nOs trechos de código a seguir são focados na estrutura de estado. Na prática,\nvocê também pode querer:\n\n- Estender `Equatable` do\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- Anotar a classe com `@Data()` do\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- Anotar a classe com **@immutable** do\n  [`package:meta`](https://pub.dev/packages/meta)\n- Implementar um método `copyWith`\n- Usar a palavra-chave `const` nos construtores\n\n:::\n\n## Classe Concreta e Enum de Status\n\nEsta abordagem consiste em uma **única classe concreta** para todos os estados\njunto com um `enum` representando diferentes status. Propriedades são tornadas\nanuláveis e são manipuladas com base no status atual. Esta abordagem funciona\nmelhor para estados que não são estritamente exclusivos e/ou contêm muitas\npropriedades compartilhadas.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Prós\n\n- **Simples**: Fácil de gerenciar uma única classe e um status enum e todas as\n  propriedades são prontamente acessíveis.\n- **Conciso**: Geralmente requer menos linhas de código em comparação a outras\n  abordagens.\n\n#### Contras\n\n- **Não é Type Safe**: Requer a verificação do `status` antes de acessar as\n  propriedades. É possível fazer `emit` de um estado malformado que pode levar a\n  bugs. Propriedades para estados específicos são anuláveis, o que pode ser\n  trabalhoso de gerenciar e requer desempacotamento forçado ou execução de\n  verificações de nulos. Alguns desses contras podem ser atenuados escrevendo\n  testes unitários e escrevendo construtores especializados e nomeados.\n- **Inchado**: Resulta em um único estado que pode ficar inchado com muitas\n  propriedades ao longo do tempo.\n\n#### Veredito\n\nEssa abordagem funciona melhor para estados simples ou quando os requisitos\nexigem estados que não são exclusivos (por exemplo, mostrar uma snackbar quando\nocorre um erro enquanto ainda mostra dados antigos do último estado de sucesso).\nEssa abordagem fornece flexibilidade e concisão ao custo da segurança de tipo.\n\n## Classe Selada e Subclasses\n\nEssa abordagem consiste em uma **classe selada** que contém as propriedades\ncompartilhadas e múltiplas subclasses para os estados separados. Essa abordagem\né ótima para estados separados e exclusivos.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Prós\n\n- **Tipagem segura**: O código é seguro para compilação e não é possível acessar\n  acidentalmente uma propriedade inválida. Cada subclasse mantém suas próprias\n  propriedades, deixando claro quais propriedades pertencem a qual estado.\n- **Explícito:** Separa propriedades compartilhadas de propriedades específicas\n  do estado.\n- **Exaustivo**: Usa uma declaração `switch` para verificações de exaustividade\n  para garantir que cada estado seja explicitamente manipulado.\n  - Se você não quer usar o\n    [switch exaustivo](https://dart.dev/language/branches#exhaustiveness-checking)\n    ou pretende adicionar subtipos mais tarde sem quebrar a API, use o\n    modificador [final](https://dart.dev/language/class-modifiers#final).\n  - Veja a\n    [documentação da classe selada](https://dart.dev/language/class-modifiers#sealed)\n    para mais detalhes.\n\n#### Contras\n\n- **Verboso**: Requer mais código (uma classe base e uma subclasse por estado).\n  Também pode exigir código duplicado para propriedades compartilhadas entre\n  subclasses.\n- **Complexo**: Adicionar novas propriedades requer a atualização de cada\n  subclasse e da classe base, o que pode ser trabalhoso e levar ao aumento da\n  complexidade do estado. Além disso, pode exigir verificação de tipo\n  desnecessária/excessiva para acessar propriedades.\n\n#### Veredito\n\nEsta abordagem funciona melhor para estados bem definidos, exclusivos e com\npropriedades únicas. Esta abordagem fornece verificações de segurança de tipo e\nexaustividade e enfatiza a segurança em vez da concisão e simplicidade.\n"
  },
  {
    "path": "docs/src/content/docs/pt-br/why-bloc.mdx",
    "content": "---\ntitle: Por que Bloc?\ndescription:\n  Uma visão geral do que torna o Bloc uma solução de gerenciamento de estado\n  sólido.\nsidebar:\n  order: 1\n---\n\nO Bloc facilita separar a apresentação da lógica de negócios, tornando seu\ncódigo _rápido_, _fácil de testar_ e _reutilizável_.\n\nAo construir aplicativos de qualidade em produção, o gerenciamento do estado\ntorna-se crítico.\n\nComo desenvolvedores, queremos:\n\n- saber em que estado o nosso aplicativo está em qualquer momento.\n- testar facilmente todos os casos de uso para garantir que nosso aplicativo\n  esteja respondendo adequadamente.\n- registrar cada interação do usuário em nosso aplicativo para que possamos\n  tomar decisões baseadas em dados.\n- trabalhar da forma mais eficiente possível e reutilizar componentes tanto em\n  nosso aplicativo quanto em outros aplicativos.\n- ter muitos desenvolvedores trabalhando facilmente em uma única base de código,\n  seguindo os mesmos padrões e convenções.\n- desenvolver aplicativos rápidos e reativos.\n\nO Bloc foi projetado para atender a todas essas necessidades e muito mais.\n\nExistem muitas soluções de gerenciamento de estado e decidir qual delas usar\npode ser uma tarefa difícil. Não existe uma solução de gerenciamento de estado\nperfeita! O importante é que você escolha a que funcione melhor para a sua\nequipe e o seu projeto.\n\nO Bloc foi projetado com três valores fundamentais em mente:\n\n- **Simples:** Fácil de entender e pode ser usado por desenvolvedores com\n  diferentes níveis de habilidade.\n- **Poderoso:** Ajuda a criar aplicativos incríveis e complexos, compondo-os com\n  componentes menores.\n- **Testável:** Testa facilmente todos os aspectos de um aplicativo para que\n  possamos iterar com confiança.\n\nNo geral, o Bloc tenta tornar as mudanças de estado previsíveis, regulando\nquando uma alteração de estado pode ocorrer e impondo uma única maneira de\nalterar o estado em todo o aplicativo.\n"
  },
  {
    "path": "docs/src/content/docs/ru/architecture.mdx",
    "content": "---\ntitle: Архитектура\ndescription: Обзор рекомендуемых архитектурных шаблонов при использовании bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Архитектура Bloc](~/assets/concepts/bloc_architecture_full.png)\n\nИспользование библиотеки bloc позволяет нам разделить наше приложение на три\nслоя:\n\n- Представление\n- Бизнес-логика\n- Данные\n  - Репозиторий\n  - Поставщик данных\n\nМы начнем с самого нижнего слоя (наиболее удаленного от пользовательского\nинтерфейса) и будем двигаться вверх к слою представления.\n\n## Слой данных\n\nОтветственность слоя данных заключается в получении/манипулировании данными из\nодного или нескольких источников.\n\nСлой данных можно разделить на две части:\n\n- Репозиторий\n- Поставщик данных\n\nЭтот слой является самым низким уровнем приложения и взаимодействует с базами\nданных, сетевыми запросами и другими асинхронными источниками данных.\n\n### Поставщик данных\n\nОтветственность поставщика данных заключается в предоставлении необработанных\nданных. Поставщик данных должен быть универсальным и многофункциональным.\n\nПоставщик данных обычно предоставляет простые API для выполнения\n[CRUD](https://ru.wikipedia.org/wiki/CRUD) операций. У нас могут быть методы\n`createData`, `readData`, `updateData` и `deleteData` как часть нашего слоя\nданных.\n\n<DataProviderSnippet />\n\n### Репозиторий\n\nСлой репозитория — это обертка вокруг одного или нескольких поставщиков данных,\nс которыми общается слой Bloc.\n\n<RepositorySnippet />\n\nКак вы можете видеть, наш слой репозитория может взаимодействовать с несколькими\nпоставщиками данных и выполнять преобразования данных перед передачей результата\nна слой бизнес-логики.\n\n## Слой бизнес-логики\n\nОтветственность слоя бизнес-логики заключается в ответе на ввод из слоя\nпредставления новыми состояниями. Этот слой может зависеть от одного или\nнескольких репозиториев для получения данных, необходимых для построения\nсостояния приложения.\n\nДумайте о слое бизнес-логики как о мосте между пользовательским интерфейсом\n(слой представления) и слоем данных. Слой бизнес-логики уведомляется о\nсобытиях/действиях из слоя представления, а затем взаимодействует с\nрепозиторием, чтобы построить новое состояние для использования слоем\nпредставления.\n\n<BusinessLogicComponentSnippet />\n\n### Взаимодействие между блоками\n\nПоскольку блоки предоставляют потоки, может возникнуть соблазн создать блок,\nкоторый прослушивает другой блок. Вы **не должны** делать этого. Существуют\nлучшие альтернативы, чем прибегание к коду ниже:\n\n<BlocTightCouplingSnippet />\n\nХотя приведенный выше код не содержит ошибок (и даже очищается за собой), у него\nесть более серьезная проблема: он создает зависимость между двумя блоками.\n\nКак правило, зависимостей между двумя сущностями на одном архитектурном слое\nследует избегать любой ценой, так как это создает жесткую связь, которую трудно\nподдерживать. Поскольку блоки находятся на архитектурном слое бизнес-логики, ни\nодин блок не должен знать о каком-либо другом блоке.\n\n![Слои архитектуры приложения](~/assets/architecture/architecture.png)\n\nБлок должен получать информацию только через события и из внедренных\nрепозиториев (то есть репозиториев, переданных блоку в его конструкторе).\n\nЕсли вы находитесь в ситуации, когда блок должен реагировать на другой блок, у\nвас есть два других варианта. Вы можете переместить проблему на слой выше (в\nслой представления) или на слой ниже (в слой домена).\n\n#### Соединение блоков через представление\n\nВы можете использовать `BlocListener` для прослушивания одного блока и\nдобавления события в другой блок всякий раз, когда первый блок изменяется.\n\n<BlocLooseCouplingPresentationSnippet />\n\nПриведенный выше код предотвращает необходимость `SecondBloc` знать о\n`FirstBloc`, поощряя слабую связь. Приложение\n[flutter_weather](/ru/tutorials/flutter-weather)\n[использует эту технику](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nдля изменения темы приложения на основе полученной информации о погоде.\n\nВ некоторых ситуациях вы можете не захотеть связывать два блока в слое\nпредставления. Вместо этого часто имеет смысл, чтобы два блока использовали один\nи тот же источник данных и обновлялись при изменении данных.\n\n#### Соединение блоков через домен\n\nДва блока могут прослушивать поток из репозитория и обновлять свои состояния\nнезависимо друг от друга всякий раз, когда изменяются данные репозитория.\nИспользование реактивных репозиториев для синхронизации состояния является\nобычным делом в крупномасштабных корпоративных приложениях.\n\nСначала создайте или используйте репозиторий, который предоставляет `Stream`\nданных. Например, следующий репозиторий предоставляет бесконечный поток одних и\nтех же нескольких идей приложений:\n\n<AppIdeasRepositorySnippet />\n\nОдин и тот же репозиторий может быть внедрен в каждый блок, который должен\nреагировать на новые идеи приложений. Ниже приведен `AppIdeaRankingBloc`,\nкоторый выдает состояние для каждой входящей идеи приложения из репозитория\nвыше:\n\n<AppIdeaRankingBlocSnippet />\n\nПодробнее об использовании потоков с Bloc см. в статье\n[Как использовать Bloc с потоками и параллелизмом](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Слой представления\n\nОтветственность слоя представления заключается в определении того, как\nотрисовать себя на основе одного или нескольких состояний блоков. Кроме того, он\nдолжен обрабатывать ввод пользователя и события жизненного цикла приложения.\n\nБольшинство потоков приложений начинаются с события `AppStart`, которое\nзапускает приложение для получения некоторых данных для представления\nпользователю.\n\nВ этом сценарии слой представления добавит событие `AppStart`.\n\nКроме того, слой представления должен будет выяснить, что отрисовать на экране\nна основе состояния из слоя bloc.\n\n<PresentationComponentSnippet />\n\nДо сих пор, хотя у нас были некоторые фрагменты кода, все это было довольно\nвысокоуровневым. В разделе руководств мы объединим все это вместе, когда будем\nсоздавать несколько различных примеров приложений.\n"
  },
  {
    "path": "docs/src/content/docs/ru/bloc-concepts.mdx",
    "content": "---\ntitle: Концепции Bloc\ndescription: Обзор основных концепций для package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nПожалуйста, внимательно прочитайте следующие разделы перед началом работы с\n[`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nСуществует несколько ключевых концепций, которые критически важны для понимания\nтого, как использовать пакет bloc.\n\nВ предстоящих разделах мы подробно обсудим каждую из них, а также рассмотрим,\nкак они применяются на примере приложения-счетчика.\n\n## Потоки (Streams)\n\n:::note\n\nОзнакомьтесь с официальной\n[Документацией Dart](https://dart.dev/tutorials/language/streams) для получения\nдополнительной информации о `Streams`.\n\n:::\n\nПоток (stream) — это последовательность асинхронных данных.\n\nДля использования библиотеки bloc критически важно иметь базовое понимание\n`Streams` и того, как они работают.\n\nЕсли вы не знакомы с `Streams`, просто представьте трубу с водой, текущей через\nнеё. Труба — это `Stream`, а вода — это асинхронные данные.\n\nМы можем создать `Stream` в Dart, написав функцию `async*` (асинхронный\nгенератор).\n\n<CountStreamSnippet />\n\nПомечая функцию как `async*`, мы получаем возможность использовать ключевое\nслово `yield` и возвращать `Stream` данных. В приведенном выше примере мы\nвозвращаем `Stream` целых чисел до значения параметра `max`.\n\nКаждый раз, когда мы используем `yield` в функции `async*`, мы проталкиваем этот\nфрагмент данных через `Stream`.\n\nМы можем использовать вышеуказанный `Stream` несколькими способами. Если бы мы\nхотели написать функцию для возврата суммы `Stream` целых чисел, она могла бы\nвыглядеть так:\n\n<SumStreamSnippet />\n\nПомечая вышеуказанную функцию как `async`, мы получаем возможность использовать\nключевое слово `await` и возвращать `Future` целых чисел. В этом примере мы\nожидаем каждое значение в потоке и возвращаем сумму всех целых чисел в потоке.\n\nМы можем собрать все это вместе следующим образом:\n\n<StreamsMainSnippet />\n\nТеперь, когда у нас есть базовое понимание того, как работают `Streams` в Dart,\nмы готовы узнать об основном компоненте пакета bloc: `Cubit`.\n\n## Cubit\n\n`Cubit` — это класс, который расширяет `BlocBase` и может быть расширен для\nуправления любым типом состояния.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\n`Cubit` может предоставлять функции, которые можно вызывать для запуска\nизменений состояния.\n\nСостояния — это выходные данные `Cubit` и представляют часть состояния вашего\nприложения. Компоненты UI могут быть уведомлены о состояниях и перерисовывать\nчасти себя на основе текущего состояния.\n\n:::note\n\nДля получения дополнительной информации о происхождении `Cubit` ознакомьтесь с\n[этим issue](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Создание Cubit\n\nМы можем создать `CounterCubit` следующим образом:\n\n<CounterCubitSnippet />\n\nПри создании `Cubit` нам необходимо определить тип состояния, которым будет\nуправлять `Cubit`. В случае `CounterCubit` выше состояние может быть\nпредставлено через `int`, но в более сложных случаях может быть необходимо\nиспользовать `class` вместо примитивного типа.\n\nВторое, что нам нужно сделать при создании `Cubit`, — это указать начальное\nсостояние. Мы можем сделать это, вызвав `super` со значением начального\nсостояния. В приведенном выше фрагменте мы устанавливаем начальное состояние в\n`0` внутренне, но мы также можем позволить `Cubit` быть более гибким, принимая\nвнешнее значение:\n\n<CounterCubitInitialStateSnippet />\n\nЭто позволило бы нам создавать экземпляры `CounterCubit` с различными начальными\nсостояниями, например:\n\n<CounterCubitInstantiationSnippet />\n\n### Изменения состояния Cubit\n\nКаждый `Cubit` имеет возможность выдавать новое состояние через `emit`.\n\n<CounterCubitIncrementSnippet />\n\nВ приведенном выше фрагменте `CounterCubit` предоставляет публичный метод\n`increment`, который может быть вызван извне для уведомления `CounterCubit` об\nувеличении его состояния. Когда вызывается `increment`, мы можем получить доступ\nк текущему состоянию `Cubit` через геттер `state` и вызвать `emit` нового\nсостояния, добавив 1 к текущему состоянию.\n\n:::caution\n\nМетод `emit` защищен, что означает, что он должен использоваться только внутри\n`Cubit`.\n\n:::\n\n### Использование Cubit\n\nТеперь мы можем взять реализованный `CounterCubit` и использовать его!\n\n#### Базовое использование\n\n<CounterCubitBasicUsageSnippet />\n\nВ приведенном выше фрагменте мы начинаем с создания экземпляра `CounterCubit`.\nЗатем мы выводим текущее состояние cubit, которое является начальным состоянием\n(поскольку новые состояния еще не были выпущены). Далее мы вызываем функцию\n`increment` для запуска изменения состояния. Наконец, мы снова выводим состояние\n`Cubit`, которое изменилось с `0` на `1`, и вызываем `close` на `Cubit` для\nзакрытия внутреннего потока состояний.\n\n#### Использование Stream\n\n`Cubit` предоставляет `Stream`, который позволяет нам получать обновления\nсостояния в реальном времени:\n\n<CounterCubitStreamUsageSnippet />\n\nВ приведенном выше фрагменте мы подписываемся на `CounterCubit` и вызываем print\nпри каждом изменении состояния. Затем мы вызываем функцию `increment`, которая\nвыдаст новое состояние. Наконец, мы вызываем `cancel` на `subscription`, когда\nбольше не хотим получать обновления, и закрываем `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` добавлен для этого примера, чтобы избежать\nнемедленной отмены подписки.\n\n:::\n\n:::caution\n\nТолько последующие изменения состояния будут получены при вызове `listen` на\n`Cubit`.\n\n:::\n\n### Наблюдение за Cubit\n\nКогда `Cubit` выдает новое состояние, происходит `Change`. Мы можем наблюдать\nвсе изменения для данного `Cubit`, переопределив `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nЗатем мы можем взаимодействовать с `Cubit` и наблюдать все изменения, выводимые\nв консоль.\n\n<CounterCubitOnChangeUsageSnippet />\n\nПриведенный выше пример выведет:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change` происходит непосредственно перед обновлением состояния `Cubit`.\n`Change` состоит из `currentState` и `nextState`.\n\n:::\n\n#### BlocObserver\n\nОдним из дополнительных преимуществ использования библиотеки bloc является то,\nчто мы можем иметь доступ ко всем `Changes` в одном месте. Хотя в этом\nприложении у нас есть только один `Cubit`, в больших приложениях довольно часто\nвстречается много `Cubits`, управляющих различными частями состояния приложения.\n\nЕсли мы хотим иметь возможность что-то делать в ответ на все `Changes`, мы можем\nпросто создать собственный `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nВсе, что нам нужно сделать, — это расширить `BlocObserver` и переопределить\nметод `onChange`.\n\n:::\n\nЧтобы использовать `SimpleBlocObserver`, нам просто нужно изменить функцию\n`main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nПриведенный выше фрагмент затем выведет:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nВнутреннее переопределение `onChange` вызывается первым, которое вызывает\n`super.onChange`, уведомляя `onChange` в `BlocObserver`.\n\n:::\n\n:::tip\n\nВ `BlocObserver` мы имеем доступ к экземпляру `Cubit` в дополнение к самому\n`Change`.\n\n:::\n\n### Обработка ошибок в Cubit\n\nКаждый `Cubit` имеет метод `addError`, который можно использовать для указания\nна то, что произошла ошибка.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` может быть переопределен внутри `Cubit` для обработки всех ошибок для\nконкретного `Cubit`.\n\n:::\n\n`onError` также может быть переопределен в `BlocObserver` для глобальной\nобработки всех сообщаемых ошибок.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nЕсли мы снова запустим ту же программу, мы должны увидеть следующий вывод:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n`Bloc` — это более продвинутый класс, который полагается на `события` для\nзапуска изменений `состояния`, а не на функции. `Bloc` также расширяет\n`BlocBase`, что означает, что он имеет аналогичный публичный API, как `Cubit`.\nОднако вместо вызова `функции` на `Bloc` и прямого выпуска нового `состояния`,\n`Bloc`-и получают `события` и преобразуют входящие `события` в исходящие\n`состояния`.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Создание Bloc\n\nСоздание `Bloc` аналогично созданию `Cubit`, за исключением того, что в\nдополнение к определению состояния, которым мы будем управлять, мы также должны\nопределить событие, которое `Bloc` сможет обрабатывать.\n\nСобытия — это входные данные для Bloc. Они обычно добавляются в ответ на\nпользовательские взаимодействия, такие как нажатия кнопок, или события\nжизненного цикла, такие как загрузка страницы.\n\n<CounterBlocSnippet />\n\nТак же, как при создании `CounterCubit`, мы должны указать начальное состояние,\nпередав его в суперкласс через `super`.\n\n### Изменения состояния Bloc\n\n`Bloc` требует, чтобы мы регистрировали обработчики событий через API\n`on<Event>`, в отличие от функций в `Cubit`. Обработчик событий отвечает за\nпреобразование любых входящих событий в ноль или более исходящих состояний.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\n`EventHandler` имеет доступ к добавленному событию, а также к `Emitter`, который\nможет использоваться для выдачи нуля или более состояний в ответ на входящее\nсобытие.\n\n:::\n\nЗатем мы можем обновить `EventHandler` для обработки события\n`CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nВ приведенном выше фрагменте мы зарегистрировали `EventHandler` для управления\nвсеми событиями `CounterIncrementPressed`. Для каждого входящего события\n`CounterIncrementPressed` мы можем получить доступ к текущему состоянию bloc\nчерез геттер `state` и вызвать `emit(state + 1)`.\n\n:::note\n\nПоскольку класс `Bloc` расширяет `BlocBase`, мы имеем доступ к текущему\nсостоянию bloc в любой момент времени через геттер `state`, так же как в\n`Cubit`.\n\n:::\n\n:::caution\n\nBloc-и никогда не должны напрямую вызывать `emit` для новых состояний. Вместо\nэтого каждое изменение состояния должно быть выведено в ответ на входящее\nсобытие внутри `EventHandler`.\n\n:::\n\n:::caution\n\nИ bloc-и, и cubit-ы будут игнорировать дублирующиеся состояния. Если мы выдадим\n`State nextState`, где `state == nextState`, то изменение состояния не\nпроизойдет.\n\n:::\n\n### Использование Bloc\n\nНа этом этапе мы можем создать экземпляр нашего `CounterBloc` и использовать\nего!\n\n#### Базовое использование\n\n<CounterBlocUsageSnippet />\n\nВ приведенном выше фрагменте мы начинаем с создания экземпляра `CounterBloc`.\nЗатем мы выводим текущее состояние `Bloc`, которое является начальным состоянием\n(поскольку новые состояния еще не были выпущены). Далее мы добавляем событие\n`CounterIncrementPressed` для запуска изменения состояния. Наконец, мы снова\nвыводим состояние `Bloc`, которое изменилось с `0` на `1`, и вызываем `close` на\n`Bloc` для закрытия внутреннего потока состояний.\n\n:::note\n\n`await Future.delayed(Duration.zero)` добавлен, чтобы убедиться, что мы ждем\nследующей итерации цикла событий (позволяя `EventHandler` обработать событие).\n\n:::\n\n#### Использование Stream\n\nТак же, как с `Cubit`, `Bloc` — это специальный тип `Stream`, что означает, что\nмы также можем подписаться на `Bloc` для получения обновлений его состояния в\nреальном времени:\n\n<CounterBlocStreamUsageSnippet />\n\nВ приведенном выше фрагменте мы подписываемся на `CounterBloc` и вызываем print\nпри каждом изменении состояния. Затем мы добавляем событие\n`CounterIncrementPressed`, которое запускает `EventHandler`\n`on<CounterIncrementPressed>` и выдает новое состояние. Наконец, мы вызываем\n`cancel` на подписке, когда больше не хотим получать обновления, и закрываем\n`Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` добавлен для этого примера, чтобы избежать\nнемедленной отмены подписки.\n\n:::\n\n### Наблюдение за Bloc\n\nПоскольку `Bloc` расширяет `BlocBase`, мы можем наблюдать все изменения\nсостояния для `Bloc` с помощью `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nЗатем мы можем обновить `main.dart` до:\n\n<CounterBlocOnChangeUsageSnippet />\n\nТеперь, если мы запустим приведенный выше фрагмент, вывод будет:\n\n<CounterBlocOnChangeOutputSnippet />\n\nОдним из ключевых отличительных факторов между `Bloc` и `Cubit` является то, что\nпоскольку `Bloc` управляется событиями, мы также можем захватить информацию о\nтом, что вызвало изменение состояния.\n\nМы можем сделать это, переопределив `onTransition`.\n\nИзменение от одного состояния к другому называется `Transition`. `Transition`\nсостоит из текущего состояния, события и следующего состояния.\n\n<CounterBlocOnTransitionSnippet />\n\nЕсли мы затем повторно запустим тот же фрагмент `main.dart` как раньше, мы\nдолжны увидеть следующий вывод:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` вызывается перед `onChange` и содержит событие, которое вызвало\nизменение от `currentState` к `nextState`.\n\n:::\n\n#### BlocObserver\n\nТак же, как и раньше, мы можем переопределить `onTransition` в пользовательском\n`BlocObserver` для наблюдения за всеми переходами, которые происходят из одного\nместа.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nМы можем инициализировать `SimpleBlocObserver` так же, как и раньше:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nТеперь, если мы запустим приведенный выше фрагмент, вывод должен выглядеть так:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` вызывается первым (локальный перед глобальным), за которым\nследует `onChange`.\n\n:::\n\nЕще одной уникальной особенностью экземпляров `Bloc` является то, что они\nпозволяют нам переопределить `onEvent`, который вызывается всякий раз, когда\nновое событие добавляется в `Bloc`. Так же, как с `onChange` и `onTransition`,\n`onEvent` может быть переопределен локально, а также глобально.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nМы можем запустить тот же `main.dart`, как и раньше, и должны увидеть следующий\nвывод:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` вызывается, как только событие добавлено. Локальный `onEvent`\nвызывается перед глобальным `onEvent` в `BlocObserver`.\n\n:::\n\n### Обработка ошибок в Bloc\n\nТак же, как с `Cubit`, каждый `Bloc` имеет методы `addError` и `onError`. Мы\nможем указать, что произошла ошибка, вызвав `addError` из любого места внутри\nнашего `Bloc`. Затем мы можем реагировать на все ошибки, переопределив\n`onError`, так же как с `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nЕсли мы повторно запустим тот же `main.dart`, как раньше, мы можем увидеть, как\nэто выглядит, когда об ошибке сообщается:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nЛокальный `onError` вызывается первым, за которым следует глобальный `onError` в\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` и `onChange` работают точно так же для экземпляров как `Bloc`, так и\n`Cubit`.\n\n:::\n\n:::caution\n\nЛюбые необработанные исключения, которые возникают внутри `EventHandler`, также\nсообщаются в `onError`.\n\n:::\n\n## Cubit против Bloc\n\nТеперь, когда мы рассмотрели основы классов `Cubit` и `Bloc`, вам может быть\nинтересно, когда следует использовать `Cubit`, а когда — `Bloc`.\n\n### Преимущества Cubit\n\n#### Простота\n\nОдним из самых больших преимуществ использования `Cubit` является простота. При\nсоздании `Cubit` нам нужно определить только состояние, а также функции, которые\nмы хотим предоставить для изменения состояния. Для сравнения, при создании\n`Bloc` мы должны определить состояния, события и реализацию `EventHandler`. Это\nделает `Cubit` более понятным и требует меньше кода.\n\nТеперь давайте рассмотрим две реализации счетчика:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nРеализация `Cubit` более лаконична, и вместо отдельного определения событий\nфункции действуют как события. Кроме того, при использовании `Cubit` мы можем\nпросто вызвать `emit` из любого места, чтобы вызвать изменение состояния.\n\n### Преимущества Bloc\n\n#### Отслеживаемость\n\nОдним из самых больших преимуществ использования `Bloc` является знание\nпоследовательности изменений состояния, а также того, что именно вызвало эти\nизменения. Для состояния, которое критически важно для функциональности\nприложения, может быть очень полезно использовать более управляемый событиями\nподход, чтобы захватить все события в дополнение к изменениям состояния.\n\nРаспространенным случаем использования может быть управление\n`AuthenticationState`. Для простоты предположим, что мы можем представить\n`AuthenticationState` через `enum`:\n\n<AuthenticationStateSnippet />\n\nМожет быть много причин, по которым состояние приложения могло измениться с\n`authenticated` на `unauthenticated`. Например, пользователь мог нажать кнопку\nвыхода и запросить выход из приложения. С другой стороны, возможно, токен\nдоступа пользователя был отозван, и он был принудительно разлогинен. При\nиспользовании `Bloc` мы можем четко отследить, как состояние приложения попало в\nопределенное состояние.\n\n<AuthenticationTransitionSnippet />\n\nПриведенный выше `Transition` дает нам всю информацию, необходимую для понимания\nтого, почему изменилось состояние. Если бы мы использовали `Cubit` для\nуправления `AuthenticationState`, наши логи выглядели бы так:\n\n<AuthenticationChangeSnippet />\n\nЭто говорит нам, что пользователь был разлогинен, но не объясняет почему, что\nможет быть критически важно для отладки и понимания того, как состояние\nприложения меняется со временем.\n\n#### Расширенные преобразования событий\n\nЕще одна область, в которой `Bloc` превосходит `Cubit`, — это когда нам нужно\nвоспользоваться реактивными операторами, такими как `buffer`, `debounceTime`,\n`throttle` и т.д.\n\n:::tip\n\nСм. [`package:stream_transform`](https://pub.dev/packages/stream_transform) и\n[`package:rxdart`](https://pub.dev/packages/rxdart) для преобразователей\nпотоков.\n\n:::\n\n`Bloc` имеет приемник событий, который позволяет нам контролировать и\nпреобразовывать входящий поток событий.\n\nНапример, если бы мы создавали поиск в реальном времени, мы, вероятно, хотели бы\nотложить запросы к бэкенду, чтобы избежать ограничения скорости, а также\nсократить затраты/нагрузку на бэкенд.\n\nС `Bloc` мы можем предоставить пользовательский `EventTransformer` для изменения\nспособа обработки входящих событий `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nС приведенным выше кодом мы можем легко задержать входящие события с очень\nнебольшим количеством дополнительного кода.\n\n:::tip\n\nОзнакомьтесь с\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) для\nнабора преобразователей событий с определенным мнением.\n\n:::\n\nЕсли вы не уверены, что использовать, начните с `Cubit`, и вы сможете позже\nотрефакторить или масштабироваться до `Bloc` по мере необходимости.\n"
  },
  {
    "path": "docs/src/content/docs/ru/faqs.mdx",
    "content": "---\ntitle: Часто задаваемые вопросы\ndescription: Ответы на часто задаваемые вопросы о библиотеке bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## Состояние не обновляется\n\n❔ **Вопрос**: Я испускаю состояние в моем блоке, но пользовательский интерфейс\nне обновляется. Что я делаю неправильно?\n\n💡 **Ответ**: Если вы используете Equatable, убедитесь, что вы передаете все\nсвойства в геттер props.\n\n✅ **ХОРОШО**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **ПЛОХО**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nКроме того, убедитесь, что вы испускаете новый экземпляр состояния в вашем\nблоке.\n\n✅ **ХОРОШО**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **ПЛОХО**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nСвойства `Equatable` всегда должны копироваться, а не изменяться. Если класс\n`Equatable` содержит `List` или `Map` в качестве свойств, обязательно\nиспользуйте `List.of` или `Map.of` соответственно, чтобы гарантировать, что\nравенство оценивается на основе значений свойств, а не ссылки.\n\n:::\n\n## Когда использовать Equatable\n\n❔**Вопрос**: Когда мне следует использовать Equatable?\n\n💡**Ответ**:\n\n<EquatableEmitSnippet />\n\nВ приведенном выше сценарии, если `StateA` расширяет `Equatable`, произойдет\nтолько одно изменение состояния (второе испускание будет проигнорировано). В\nобщем, вы должны использовать `Equatable`, если вы хотите оптимизировать свой\nкод, чтобы уменьшить количество перестроек. Вы не должны использовать\n`Equatable`, если хотите, чтобы одно и то же состояние подряд вызывало несколько\nпереходов.\n\nКроме того, использование `Equatable` значительно упрощает тестирование блоков,\nпоскольку мы можем ожидать конкретные экземпляры состояний блока, а не\nиспользовать `Matchers` или `Predicates`.\n\n<EquatableBlocTestSnippet />\n\nБез `Equatable` приведенный выше тест не пройдет, и его нужно будет переписать\nследующим образом:\n\n<NoEquatableBlocTestSnippet />\n\n## Обработка ошибок\n\n❔ **Вопрос**: Как я могу обработать ошибку, сохраняя при этом предыдущие\nданные?\n\n💡 **Ответ**:\n\nЭто во многом зависит от того, как было смоделировано состояние блока. В\nслучаях, когда данные должны сохраняться даже при наличии ошибки, рассмотрите\nиспользование одного класса состояния.\n\n<SingleStateSnippet />\n\nЭто позволит виджетам иметь доступ к свойствам `data` и `error` одновременно, и\nблок может использовать `state.copyWith` для сохранения старых данных даже когда\nпроизошла ошибка.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Вопрос**: В чем разница между Bloc и Redux?\n\n💡 **Ответ**:\n\nBLoC — это шаблон проектирования, определяемый следующими правилами:\n\n1. Вход и выход BLoC — это простые потоки и приемники.\n2. Зависимости должны быть внедряемыми и независимыми от платформы.\n3. Разветвление платформы не допускается.\n4. Реализация может быть любой, если вы следуете приведенным выше правилам.\n\nРекомендации по пользовательскому интерфейсу:\n\n1. Каждый \"достаточно сложный\" компонент имеет соответствующий BLoC.\n2. Компоненты должны отправлять входы \"как есть\".\n3. Компоненты должны показывать выходы как можно ближе к \"как есть\".\n4. Все разветвления должны основываться на простых логических выходах BLoC.\n\nБиблиотека Bloc реализует шаблон проектирования BLoC и нацелена на\nабстрагирование RxDart для упрощения опыта разработчика.\n\nТри принципа Redux:\n\n1. Единый источник истины\n2. Состояние доступно только для чтения\n3. Изменения производятся чистыми функциями\n\nБиблиотека bloc нарушает первый принцип; с bloc состояние распределено по\nнескольким блокам. Кроме того, в bloc нет концепции middleware, и bloc\nпредназначен для упрощения асинхронных изменений состояния, позволяя вам\nиспускать несколько состояний для одного события.\n\n## Bloc vs. Provider\n\n❔ **Вопрос**: В чем разница между Bloc и Provider?\n\n💡 **Ответ**: `provider` предназначен для внедрения зависимостей (он оборачивает\n`InheritedWidget`). Вам все равно нужно выяснить, как управлять вашим состоянием\n(через `ChangeNotifier`, `Bloc`, `Mobx` и т.д...). Библиотека Bloc использует\n`provider` внутренне, чтобы упростить предоставление и доступ к блокам по всему\nдереву виджетов.\n\n## BlocProvider.of() не может найти Bloc\n\n❔ **Вопрос**: При использовании `BlocProvider.of(context)` он не может найти\nблок. Как это исправить?\n\n💡 **Ответ**: Вы не можете получить доступ к блоку из того же контекста, в\nкотором он был предоставлен, поэтому вы должны убедиться, что\n`BlocProvider.of()` вызывается внутри дочернего `BuildContext`.\n\n✅ **ХОРОШО**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **ПЛОХО**\n\n<BlocProviderBad1Snippet />\n\n## Структура проекта\n\n❔ **Вопрос**: Как мне структурировать мой проект?\n\n💡 **Ответ**: Хотя на этот вопрос действительно нет правильного/неправильного\nответа, некоторые рекомендуемые ссылки:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nСамое важное — иметь **последовательную** и **продуманную** структуру проекта.\n\n## Добавление событий внутри блока\n\n❔ **Вопрос**: Можно ли добавлять события внутри блока?\n\n💡 **Ответ**: В большинстве случаев события должны добавляться извне, но в\nнекоторых избранных случаях может иметь смысл добавлять события внутренне.\n\nНаиболее распространенная ситуация, в которой используются внутренние события, —\nэто когда изменения состояния должны происходить в ответ на обновления в\nреальном времени из репозитория. В этих ситуациях репозиторий является стимулом\nдля изменения состояния вместо внешнего события, такого как нажатие кнопки.\n\nВ следующем примере состояние `MyBloc` зависит от текущего пользователя, который\nпредоставляется через `Stream<User>` из `UserRepository`. `MyBloc` прослушивает\nизменения текущего пользователя и добавляет внутреннее событие `_UserChanged`\nвсякий раз, когда пользователь испускается из потока пользователей.\n\n<BlocInternalAddEventSnippet />\n\nДобавляя внутреннее событие, мы также можем указать пользовательский\n`transformer` для события, чтобы определить, как будут обрабатываться несколько\nсобытий `_UserChanged` — по умолчанию они будут обрабатываться одновременно.\n\nНастоятельно рекомендуется, чтобы внутренние события были приватными. Это явный\nспособ сигнализировать, что конкретное событие используется только внутри самого\nблока и предотвращает знание внешних компонентов о событии.\n\n<BlocInternalEventSnippet />\n\nВ качестве альтернативы мы можем определить внешнее событие `Started` и\nиспользовать API `emit.forEach` для обработки реагирования на обновления\nпользователей в реальном времени:\n\n<BlocExternalForEachSnippet />\n\nПреимущества приведенного выше подхода:\n\n- Нам не нужно внутреннее событие `_UserChanged`\n- Нам не нужно управлять `StreamSubscription` вручную\n- У нас есть полный контроль над тем, когда блок подписывается на поток\n  обновлений пользователей\n\nНедостатки приведенного выше подхода:\n\n- Мы не можем легко приостановить `pause` или возобновить `resume` подписку\n- Нам нужно предоставить публичное событие `Started`, которое должно быть\n  добавлено извне\n- Мы не можем использовать пользовательский `transformer` для настройки того,\n  как мы реагируем на обновления пользователей\n\n## Предоставление публичных методов\n\n❔ **Вопрос**: Можно ли предоставлять публичные методы в моих экземплярах bloc и\ncubit?\n\n💡 **Ответ**\n\nПри создании cubit рекомендуется предоставлять только публичные методы для целей\nзапуска изменений состояния. В результате, как правило, все публичные методы в\nэкземпляре cubit должны возвращать `void` или `Future<void>`.\n\nПри создании bloc рекомендуется избегать предоставления каких-либо\nпользовательских публичных методов и вместо этого уведомлять блок о событиях,\nвызывая `add`.\n"
  },
  {
    "path": "docs/src/content/docs/ru/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Концепции Flutter Bloc\ndescription: Обзор основных концепций для package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nПожалуйста, внимательно прочитайте следующие разделы перед работой с\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nВсе виджеты, экспортируемые пакетом `flutter_bloc`, интегрируются как с\nэкземплярами `Cubit`, так и с экземплярами `Bloc`.\n\n:::\n\n## Bloc Виджеты\n\n### BlocBuilder\n\n**BlocBuilder** — это Flutter виджет, которому требуется `Bloc` и функция\n`builder`. `BlocBuilder` обрабатывает построение виджета в ответ на новые\nсостояния. `BlocBuilder` очень похож на `StreamBuilder`, но имеет более простой\nAPI для уменьшения количества шаблонного кода. Функция `builder` может\nпотенциально вызываться много раз и должна быть\n[чистой функцией](https://en.wikipedia.org/wiki/Pure_function), которая\nвозвращает виджет в ответ на состояние.\n\nСмотрите `BlocListener`, если вы хотите \"делать\" что-либо в ответ на изменения\nсостояния, такие как навигация, показ диалога и т.д.\n\nЕсли параметр `bloc` опущен, `BlocBuilder` автоматически выполнит поиск,\nиспользуя `BlocProvider` и текущий `BuildContext`.\n\n<BlocBuilderSnippet />\n\nУказывайте bloc только в том случае, если вы хотите предоставить bloc, который\nбудет ограничен одним виджетом и не доступен через родительский `BlocProvider` и\nтекущий `BuildContext`.\n\n<BlocBuilderExplicitBlocSnippet />\n\nДля точного контроля над тем, когда вызывается функция `builder`, можно\nпредоставить опциональный параметр `buildWhen`. `buildWhen` принимает предыдущее\nсостояние bloc и текущее состояние bloc и возвращает логическое значение. Если\n`buildWhen` возвращает true, `builder` будет вызван с `state` и виджет будет\nперестроен. Если `buildWhen` возвращает false, `builder` не будет вызван с\n`state` и перестроение не произойдет.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** — это Flutter виджет, который аналогичен `BlocBuilder`, но\nпозволяет разработчикам фильтровать обновления, выбирая новое значение на основе\nтекущего состояния bloc. Ненужные построения предотвращаются, если выбранное\nзначение не изменяется. Выбранное значение должно быть неизменяемым, чтобы\n`BlocSelector` мог точно определить, должен ли `builder` быть вызван снова.\n\nЕсли параметр `bloc` опущен, `BlocSelector` автоматически выполнит поиск,\nиспользуя `BlocProvider` и текущий `BuildContext`.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** — это Flutter виджет, который предоставляет bloc своим дочерним\nэлементам через `BlocProvider.of<T>(context)`. Он используется как виджет\nвнедрения зависимостей (DI), чтобы один экземпляр bloc мог быть предоставлен\nнескольким виджетам в поддереве.\n\nВ большинстве случаев `BlocProvider` должен использоваться для создания новых\nbloc, которые будут доступны остальной части поддерева. В этом случае, поскольку\n`BlocProvider` отвечает за создание bloc, он автоматически обработает закрытие\nbloc.\n\n<BlocProviderSnippet />\n\nПо умолчанию `BlocProvider` создаст bloc лениво, что означает, что `create`\nбудет выполнен, когда bloc будет найден через `BlocProvider.of<BlocA>(context)`.\n\nЧтобы переопределить это поведение и принудительно запустить `create`\nнемедленно, `lazy` можно установить в `false`.\n\n<BlocProviderEagerSnippet />\n\nВ некоторых случаях `BlocProvider` может использоваться для предоставления\nсуществующего bloc новой части дерева виджетов. Это чаще всего используется,\nкогда существующий bloc необходимо сделать доступным для нового маршрута. В этом\nслучае `BlocProvider` не будет автоматически закрывать bloc, поскольку он не\nсоздавал его.\n\n<BlocProviderValueSnippet />\n\nзатем из `ChildA` или `ScreenA` мы можем получить `BlocA` с помощью:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** — это Flutter виджет, который объединяет несколько\nвиджетов `BlocProvider` в один. `MultiBlocProvider` улучшает читаемость и\nустраняет необходимость вкладывать несколько `BlocProviders`. Используя\n`MultiBlocProvider`, мы можем перейти от:\n\n<NestedBlocProviderSnippet />\n\nк:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nКогда `BlocProvider` определен в контексте `MultiBlocProvider`, любой `child`\nбудет игнорироваться.\n\n:::\n\n### BlocListener\n\n**BlocListener** — это Flutter виджет, который принимает `BlocWidgetListener` и\nопциональный `Bloc` и вызывает `listener` в ответ на изменения состояния в bloc.\nОн должен использоваться для функциональности, которая должна выполняться один\nраз на каждое изменение состояния, такой как навигация, показ `SnackBar`, показ\n`Dialog` и т.д.\n\n`listener` вызывается только один раз для каждого изменения состояния (**НЕ**\nвключая начальное состояние), в отличие от `builder` в `BlocBuilder`, и является\nфункцией `void`.\n\nЕсли параметр `bloc` опущен, `BlocListener` автоматически выполнит поиск,\nиспользуя `BlocProvider` и текущий `BuildContext`.\n\n<BlocListenerSnippet />\n\nУказывайте bloc только в том случае, если вы хотите предоставить bloc, который\nиначе не доступен через `BlocProvider` и текущий `BuildContext`.\n\n<BlocListenerExplicitBlocSnippet />\n\nДля точного контроля над тем, когда вызывается функция `listener`, можно\nпредоставить опциональный параметр `listenWhen`. `listenWhen` принимает\nпредыдущее состояние bloc и текущее состояние bloc и возвращает логическое\nзначение. Если `listenWhen` возвращает true, `listener` будет вызван с `state`.\nЕсли `listenWhen` возвращает false, `listener` не будет вызван с `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** — это Flutter виджет, который объединяет несколько\nвиджетов `BlocListener` в один. `MultiBlocListener` улучшает читаемость и\nустраняет необходимость вкладывать несколько `BlocListeners`. Используя\n`MultiBlocListener`, мы можем перейти от:\n\n<NestedBlocListenerSnippet />\n\nк:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nКогда `BlocListener` определен в контексте `MultiBlocListener`, любой `child`\nбудет игнорироваться.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** предоставляет `builder` и `listener` для реагирования на новые\nсостояния. `BlocConsumer` аналогичен вложенным `BlocListener` и `BlocBuilder`,\nно уменьшает количество необходимого шаблонного кода. `BlocConsumer` должен\nиспользоваться только когда необходимо как перестроить UI, так и выполнить\nдругие реакции на изменения состояния в `bloc`. `BlocConsumer` принимает\nобязательные `BlocWidgetBuilder` и `BlocWidgetListener` и опциональные `bloc`,\n`BlocBuilderCondition` и `BlocListenerCondition`.\n\nЕсли параметр `bloc` опущен, `BlocConsumer` автоматически выполнит поиск,\nиспользуя `BlocProvider` и текущий `BuildContext`.\n\n<BlocConsumerSnippet />\n\nОпциональные `listenWhen` и `buildWhen` могут быть реализованы для более\nдетального контроля над тем, когда вызываются `listener` и `builder`.\n`listenWhen` и `buildWhen` будут вызваны при каждом изменении `state` в `bloc`.\nКаждый принимает предыдущее `state` и текущее `state` и должен вернуть `bool`,\nкоторый определяет, будет ли вызвана функция `builder` и/или `listener`.\nПредыдущее `state` будет инициализировано состоянием `state` блока `bloc` при\nинициализации `BlocConsumer`. `listenWhen` и `buildWhen` являются опциональными,\nи если они не реализованы, по умолчанию будет `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** — это Flutter виджет, который предоставляет репозиторий\nсвоим дочерним элементам через `RepositoryProvider.of<T>(context)`. Он\nиспользуется как виджет внедрения зависимостей (DI), чтобы один экземпляр\nрепозитория мог быть предоставлен нескольким виджетам в поддереве.\n`BlocProvider` должен использоваться для предоставления bloc, в то время как\n`RepositoryProvider` должен использоваться только для репозиториев.\n\n<RepositoryProviderSnippet />\n\nзатем из `ChildA` мы можем получить экземпляр `Repository` с помощью:\n\n<RepositoryProviderLookupSnippet />\n\nРепозитории, которые управляют ресурсами, которые должны быть освобождены, могут\nсделать это через колбэк `dispose`:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** — это Flutter виджет, который объединяет несколько\nвиджетов `RepositoryProvider` в один. `MultiRepositoryProvider` улучшает\nчитаемость и устраняет необходимость вкладывать несколько `RepositoryProvider`.\nИспользуя `MultiRepositoryProvider`, мы можем перейти от:\n\n<NestedRepositoryProviderSnippet />\n\nк:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nКогда `RepositoryProvider` определен в контексте `MultiRepositoryProvider`,\nлюбой `child` будет игнорироваться.\n\n:::\n\n## Использование BlocProvider\n\nДавайте рассмотрим, как использовать `BlocProvider` для предоставления\n`CounterBloc` в `CounterPage` и реагировать на изменения состояния с помощью\n`BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nНа этом этапе мы успешно отделили наш слой представления от слоя бизнес-логики.\nОбратите внимание, что виджет `CounterPage` ничего не знает о том, что\nпроисходит, когда пользователь нажимает на кнопки. Виджет просто сообщает\n`CounterBloc`, что пользователь нажал либо кнопку увеличения, либо уменьшения.\n\n## Использование RepositoryProvider\n\nМы рассмотрим, как использовать `RepositoryProvider` в контексте примера\n[`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nВ нашем `main.dart` мы вызываем `runApp` с нашим виджетом `WeatherApp`.\n\n<WeatherMainSnippet />\n\nМы внедрим наш экземпляр `WeatherRepository` в дерево виджетов через\n`RepositoryProvider`.\n\nПри создании экземпляра bloc мы можем получить доступ к экземпляру репозитория\nчерез `context.read` и внедрить репозиторий в bloc через конструктор.\n\n<WeatherAppSnippet />\n\n:::tip\n\nЕсли у вас более одного репозитория, вы можете использовать\n`MultiRepositoryProvider` для предоставления нескольких экземпляров репозиториев\nподдереву.\n\n:::\n\n:::note\n\nИспользуйте колбэк `dispose` для освобождения любых ресурсов, когда\n`RepositoryProvider` размонтируется.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Методы расширения\n\n[Методы расширения](https://dart.dev/guides/language/extension-methods),\nпредставленные в Dart 2.7, — это способ добавить функциональность к существующим\nбиблиотекам. В этом разделе мы рассмотрим методы расширения, включенные в\n`package:flutter_bloc`, и как их можно использовать.\n\n`flutter_bloc` имеет зависимость от\n[package:provider](https://pub.dev/packages/provider), которая упрощает\nиспользование\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nВнутренне `package:flutter_bloc` использует `package:provider` для реализации:\n`BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` и виджетов\n`MultiRepositoryProvider`. `package:flutter_bloc` экспортирует расширения\n`ReadContext`, `WatchContext` и `SelectContext` из `package:provider`.\n\n:::note\n\nУзнайте больше о [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` ищет ближайший экземпляр предка типа `T` и функционально\nэквивалентен `BlocProvider.of<T>(context)`. `context.read` чаще всего\nиспользуется для получения экземпляра bloc, чтобы добавить событие в колбэках\n`onPressed`.\n\n:::note\n\n`context.read<T>()` не прослушивает `T` — если предоставленный `Object` типа `T`\nизменяется, `context.read` не вызовет перестроение виджета.\n\n:::\n\n#### Использование\n\n✅ **ИСПОЛЬЗУЙТЕ** `context.read` для добавления событий в колбэках.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **ИЗБЕГАЙТЕ** использования `context.read` для получения состояния в методе\n`build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nВышеуказанное использование подвержено ошибкам, потому что виджет `Text` не\nбудет перестроен, если состояние bloc изменится.\n\n:::caution\n\nИспользуйте `BlocBuilder` или `context.watch` вместо этого, чтобы перестраивать\nв ответ на изменения состояния.\n\n:::\n\n### context.watch\n\nКак и `context.read<T>()`, `context.watch<T>()` предоставляет ближайший\nэкземпляр предка типа `T`, однако он также прослушивает изменения экземпляра.\nЭто функционально эквивалентно `BlocProvider.of<T>(context, listen: true)`.\n\nЕсли предоставленный `Object` типа `T` изменяется, `context.watch` вызовет\nперестроение.\n\n:::caution\n\n`context.watch` доступен только в методе `build` класса `StatelessWidget` или\n`State`.\n\n:::\n\n#### Использование\n\n✅ **ИСПОЛЬЗУЙТЕ** `BlocBuilder` вместо `context.watch` для явного ограничения\nперестроений.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Когда состояние изменяется, перестраивается только Text.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nАльтернативно, используйте `Builder` для ограничения перестроений.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Когда состояние изменяется, перестраивается только Text.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **ИСПОЛЬЗУЙТЕ** `Builder` и `context.watch` как `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // возвращает виджет, который зависит от состояния BlocA, BlocB и BlocC\n  }\n);\n```\n\n❌ **ИЗБЕГАЙТЕ** использования `context.watch`, когда родительский виджет в\nметоде `build` не зависит от состояния.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Когда состояние изменяется, MaterialApp перестраивается\n  // даже если оно используется только в виджете Text.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nИспользование `context.watch` в корне метода `build` приведет к перестроению\nвсего виджета при изменении состояния bloc.\n\n:::\n\n### context.select\n\nКак и `context.watch<T>()`, `context.select<T, R>(R function(T value))`\nпредоставляет ближайший экземпляр предка типа `T` и прослушивает изменения `T`.\nВ отличие от `context.watch`, `context.select` позволяет прослушивать изменения\nв меньшей части состояния.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nВышеуказанное будет перестраивать виджет только когда свойство `name` состояния\n`ProfileBloc` изменится.\n\n#### Использование\n\n✅ **ИСПОЛЬЗУЙТЕ** `BlocSelector` вместо `context.select` для явного ограничения\nперестроений.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Когда state.name изменяется, перестраивается только Text.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nАльтернативно, используйте `Builder` для ограничения перестроений.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Когда state.name изменяется, перестраивается только Text.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **ИЗБЕГАЙТЕ** использования `context.select`, когда родительский виджет в\nметоде build не зависит от состояния.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Когда state.value изменяется, MaterialApp перестраивается\n  // даже если оно используется только в виджете Text.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nИспользование `context.select` в корне метода `build` приведет к перестроению\nвсего виджета при изменении выбранного значения.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ru/getting-started.mdx",
    "content": "---\ntitle: Начало работы\ndescription: Всё, что нужно для начала работы с Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Пакеты\n\nЭкосистема bloc состоит из нескольких пакетов, перечисленных ниже:\n\n| Пакет                                                                                      | Описание                          | Ссылка                                                                                                         |\n| ------------------------------------------------------------------------------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | Компоненты AngularDart            | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Основные API Dart                 | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Преобразователи событий           | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Пользовательский линтер           | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | API для тестирования              | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Инструменты командной строки      | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Виджеты Flutter                   | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Поддержка кэширования/сохранения  | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Поддержка отмены/повтора действий | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Установка\n\n<InstallationTabs />\n\n:::note\n\nДля начала использования bloc у вас должен быть установлен\n[Dart SDK](https://dart.dev/get-dart) на вашем компьютере.\n\n:::\n\n## Импорты\n\nТеперь, когда мы успешно установили bloc, мы можем создать наш `main.dart` и\nимпортировать соответствующий пакет `bloc`.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/ru/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Официальная документация библиотеки управления состоянием bloc. Поддержка\n  Dart, Flutter и AngularDart. Включает примеры и руководства.\nbanner:\n  content: |\n    ✨ Посетите\n    <a href=\"https://shop.bloclibrary.dev\">Магазин Bloc</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Предсказуемая библиотека управления состоянием для Dart.\n  image:\n    alt: Логотип Bloc\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Начать\n      link: /ru/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Посмотреть на GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Начать\" icon=\"rocket\">\n\t```sh\n\t# Добавьте bloc в ваш проект.\n\tdart pub add bloc\n\t```\n\nНаше [руководство по началу работы](/ru/getting-started) содержит пошаговые\nинструкции о том, как начать использовать Bloc всего за несколько минут.\n\n</SplitCard>\n\n<Card title=\"Пройдите экскурсию\" icon=\"star\">\n\tПройдите [официальные руководства](/ru/tutorials/flutter-counter), чтобы\n\tизучить лучшие практики и создать различные приложения на основе Bloc.\n</Card>\n\n<Card title=\"Создавайте с Bloc\" icon=\"laptop\">\n\tИзучайте высококачественные, полностью протестированные [примеры\n\tприложений](https://github.com/felangel/bloc/tree/master/examples), такие как\n\tсчётчик, таймер, бесконечный список, погода, задачи и многое другое!\n</Card>\n\n<ListCard title=\"Обучение\" icon=\"open-book\">\n\n    - [Почему Bloc?](/ru/why-bloc)\n    - [Основные концепции](/ru/bloc-concepts)\n    - [Архитектура](/ru/architecture)\n    - [Тестирование](/ru/testing)\n    - [Соглашения об именовании](/ru/naming-conventions)\n    - [Часто задаваемые вопросы](/ru/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Интеграции\" icon=\"puzzle\">\n    - [Интеграция с VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Интеграция с IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Интеграция с Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Интеграция с Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Пользовательские шаблоны](https://brickhub.dev/search?q=bloc)\n    - [Инструменты разработчика](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint/configuration.mdx",
    "content": "---\ntitle: Конфигурация линтера\ndescription: Настройка линтера bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nПо умолчанию линтер bloc не будет выдавать никакой диагностики, если вы явно не\nнастроили параметры анализа проекта.\n\nДля начала создайте или измените существующий файл `analysis_options.yaml` в\nкорне вашего проекта, чтобы включить список правил под ключом верхнего уровня\nbloc:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nЗапустите линтер с помощью следующей команды в терминале:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nПриведённая выше команда проанализирует все файлы в текущем каталоге и его\nподкаталогах, но вы также можете проверить конкретные файлы и каталоги, передав\nих в качестве аргументов командной строки:\n\n<RunBlocLintInSrcTestSnippet />\n\nПриведённая выше команда проанализирует весь код в каталогах `src` и `test`.\n\nЕсли правило `avoid_flutter_imports` включено, любой файл bloc или cubit,\nкоторый содержит импорт flutter, будет помечен как предупреждение:\n\n<AvoidFlutterImportsWarningSnippet />\n\nВы можете увидеть предупреждение, запустив команду `bloc lint`:\n\n<RunBlocLintCounterCubitSnippet />\n\nВывод должен выглядеть так:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nВот все поддерживаемые правила линтера:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint/customizing-rules.mdx",
    "content": "---\ntitle: Настройка правил линтера\ndescription: Настройка правил линтера bloc\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nВы можете настроить поведение линтера bloc, изменив серьёзность отдельных\nправил, индивидуально включая или отключая правила, а также исключая файлы из\nстатического анализа.\n\n## Включение и отключение правил\n\nЛинтер bloc поддерживает растущий список правил линтера. Обратите внимание, что\nправила линтера не обязательно должны соответствовать друг другу. Например,\nнекоторые разработчики могут предпочитать использовать блоки (`prefer_bloc`), в\nто время как другие могут предпочитать использовать кубиты (`prefer_cubit`).\n\n:::note\n\nВ отличие от статического анализа, правила линтера могут содержать ложные\nсрабатывания. Не стесняйтесь сообщать о любых ложных срабатываниях или других\nпроблемах, [создав issue](https://github.com/felangel/bloc/issues/new/choose).\n\n:::\n\n### Включение рекомендуемых правил\n\nБиблиотека bloc предоставляет набор рекомендуемых правил линтера в составе\nпакета [`bloc_lint`](https://pub.dev/packages/bloc_lint).\n\nДля включения рекомендуемого набора линтов добавьте пакет `bloc_lint` как\ndev-зависимость:\n\n<InstallBlocLintSnippet />\n\nЗатем отредактируйте ваш файл `analysis_options.yaml`, чтобы включить набор\nправил:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nКогда публикуется новая версия `bloc_lint`, код, который ранее проходил\nстатический анализ, может начать проваливаться. Мы рекомендуем обновить ваш код\nдля работы с новыми правилами, или вы также можете опционально включить или\nотключить отдельные правила.\n\n:::\n\n### Включение отдельных правил\n\nДля включения отдельных правил добавьте `bloc:` в файл `analysis_options.yaml` в\nкачестве ключа верхнего уровня и `rules:` в качестве ключа второго уровня. В\nпоследующих строках укажите правила, которые вы хотите, в виде списка YAML (с\nпрефиксом в виде дефисов).\n\nНапример:\n\n<BlocLintEnablingRulesSnippet />\n\n### Отключение отдельных правил\n\nЕсли вы включаете существующий набор правил, такой как набор `recommended`, вы\nможете захотеть отключить одно или несколько включённых правил линтера.\nОтключение правил аналогично их включению, но требует использования YAML-карты,\nа не списка.\n\nНапример, следующее включает рекомендуемый набор правил линтера за исключением\n`avoid_public_bloc_methods` и дополнительно включает правило `prefer_bloc`:\n\n<BlocLintDisablingRulesSnippet />\n\n## Настройка серьёзности правил\n\nВы можете настроить серьёзность любого правила следующим образом:\n\n<BlocLintChangingSeveritySnippet />\n\nТеперь то же самое правило линтера будет отображаться с серьёзностью `info`\nвместо `warning`:\n\n<ImportFlutterInfoSnippet />\n\nВывод команды `bloc lint` должен выглядеть так:\n\n<ImportFlutterInfoOutputSnippet />\n\nПоддерживаемые варианты серьёзности:\n\n| Серьёзность | Описание                                                         |\n| ----------- | ---------------------------------------------------------------- |\n| `error`     | Указывает, что шаблон не разрешён.                               |\n| `warning`   | Указывает, что шаблон подозрителен, но разрешён.                 |\n| `info`      | Предоставляет информацию пользователям, но не является проблемой |\n| `hint`      | Предлагает лучший способ достижения результата.                  |\n\n## Исключение файлов\n\nИногда нормально, что статический анализ проваливается. Например, вы можете\nзахотеть игнорировать предупреждения или ошибки, отображаемые в сгенерированном\nкоде, который не был написан вами и вашей командой. Так же, как и с официальными\nправилами линтера Dart, вы можете использовать опцию анализатора `exclude:`,\nчтобы исключить файлы из статического анализа.\n\nВы можете либо перечислить отдельные файлы, либо использовать паттерны\n[`glob`](https://pub.dev/packages/glob).\n\n:::note\n\nВсе использования паттернов glob должны быть относительно каталога, содержащего\nсоответствующий файл `analysis_options.yaml`.\n\n:::\n\nНапример, мы можем исключить весь сгенерированный код Dart с помощью следующих\nпараметров анализа:\n\n<BlocLintExcludingFilesSnippet />\n\n## Игнорирование правил\n\nТак же, как и с официальными правилами линтера Dart, вы можете игнорировать\nправила линтера bloc для данного файла или строки кода, используя\n`// ignore_for_file` и `// ignore` соответственно.\n\n:::note\n\nЧтобы игнорировать несколько правил для данной строки или файла, укажите список,\nразделённый запятыми.\n\n:::\n\n### Игнорирование строк\n\nМы можем игнорировать конкретные случаи нарушений правил, добавив комментарий\n`ignore` либо прямо над проблемной строкой, либо добавив его в конец проблемной\nстроки.\n\nНапример, мы можем игнорировать конкретные случаи\n`prefer_file_naming_conventions` в данном файле:\n\n<BlocLintIgnoreForLineSnippet />\n\n### Игнорирование файлов\n\nМы можем игнорировать все случаи нарушений правил в файле, добавив комментарий\n`ignore_for_file` в любом месте файла.\n\nНапример, мы можем игнорировать все случаи `prefer_file_naming_conventions` в\nданном файле:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint/index.mdx",
    "content": "---\ntitle: Обзор линтера\ndescription: Введение в линтер bloc.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nЛинтинг — это процесс статического анализа кода для выявления потенциальных\nошибок, а также программных и стилистических проблем.\n\nBloc имеет встроенный линтер, который можно использовать через вашу IDE или\n[`инструменты командной строки bloc`](https://pub.dev/packages/bloc_tools) с\nпомощью команды `bloc lint`.\n\nС помощью линтера bloc вы можете повысить качество вашей кодовой базы и\nобеспечить единообразие без выполнения ни одной строки кода.\n\nНапример, возможно, вы случайно импортировали зависимость Flutter в ваш cubit:\n\n<AvoidFlutterImportsWarningSnippet />\n\nПри правильной настройке линтер bloc укажет на импорт и выдаст следующее\nпредупреждение:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nВ следующих разделах мы рассмотрим, как установить, настроить и кастомизировать\nлинтер bloc, чтобы вы могли воспользоваться преимуществами статического анализа\nв вашей кодовой базе.\n\n## Быстрый старт\n\nНачните использовать линтер bloc всего за несколько быстрых и простых шагов.\n\n:::note\n\nДля начала использования bloc у вас должен быть установлен\n[Dart SDK](https://dart.dev/get-dart) на вашей машине.\n\n:::\n\n1. Установите\n   [инструменты командной строки bloc](https://pub.dev/packages/bloc_tools)\n\n   <InstallBlocToolsSnippet />\n\n1. Установите пакет [bloc_lint](https://pub.dev/packages/bloc_lint)\n\n   <InstallBlocLintSnippet />\n\n1. Добавьте файл `analysis_options.yaml` в корень вашего проекта с\n   рекомендуемыми правилами\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. Запустите линтер\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nВот и всё 🎉\n\nПродолжайте чтение для более подробного изучения настройки и кастомизации\nлинтера bloc.\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint/installation.mdx",
    "content": "---\ntitle: Установка линтера\ndescription: Установка линтера bloc.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## Инструменты командной строки\n\nДля использования линтера из командной строки установите\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) с помощью следующей\nкоманды:\n\n<InstallBlocToolsSnippet />\n\nПосле установки инструментов командной строки bloc вы можете запустить линтер\nbloc с помощью команды `bloc lint`:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## Рекомендуемый набор правил\n\nДля установки рекомендуемого набора правил линтера установите\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) как dev-зависимость с\nпомощью следующей команды:\n\n<InstallBlocLintSnippet />\n\nЗатем добавьте файл `analysis_options.yaml` в корень вашего проекта с\nрекомендуемым набором правил:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nПри необходимости вы можете включить несколько наборов правил, определив их в\nвиде списка:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## Интеграции с IDE\n\nСледующие IDE официально поддерживают линтер bloc и языковой сервер для\nпредоставления мгновенной диагностики непосредственно в вашей IDE.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tПоддержка в [Bloc VSCode\n\t\tExtension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tдоступна начиная с версии v6.8.0.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tПоддержка в [Bloc IntelliJ\n\t\tPlugin](https://plugins.jetbrains.com/plugin/12129-bloc) доступна начиная с\n\t\tверсии v4.1.0.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: Избегайте расширений BuildContext\ndescription: Правило avoid_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nИзбегайте использования расширений `BuildContext` для доступа к экземплярам\n`Bloc` или `Cubit`.\n\n:::note\n\nЭто правило линтера было введено в версии `0.3.0` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обоснование\n\nДля согласованности и ради явности предпочтительно использовать напрямую базовые\nметоды вместо расширений `BuildContext`. Это также полезно для тестирования,\nпоскольку невозможно замокать метод расширения.\n\n| расширение       | явный метод                                                          |\n| ---------------- | -------------------------------------------------------------------- |\n| `context.read`   | `BlocProvider.of<Bloc>(context, listen: false)`                      |\n| `context.watch`  | `BlocBuilder<Bloc, State>(...)` или `BlocProvider.of<Bloc>(context)` |\n| `context.select` | `BlocSelector<Bloc, State>(...)`                                     |\n\n## Примеры\n\n**Избегайте** использования расширений `BuildContext` для взаимодействия с\nэкземплярами `Bloc` или `Cubit`.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `avoid_build_context_extensions`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: Избегайте импортов Flutter\ndescription: Правило линтера bloc avoid_flutter_imports.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nИзбегайте введения зависимостей от Flutter в компонентах бизнес-логики\n(экземплярах `Bloc` или `Cubit`).\n\n## Обоснование\n\nРазделение приложения на слои является ключевой частью построения поддерживаемой\nкодовой базы и помогает разработчикам итерировать быстро и уверенно. Каждый слой\nдолжен иметь единственную ответственность и быть способным функционировать и\nтестироваться изолированно. Это позволяет вам ограничивать изменения конкретными\nслоями, минимизируя влияние на все приложение.\n\nВ результате компоненты бизнес-логики обычно должны управлять состоянием функций\nи быть отделены от слоя пользовательского интерфейса. События должны поступать в\nкомпоненты бизнес-логики из слоя UI, а состояние должно вытекать из слоя\nбизнес-логики в слой UI.\n\nСохранение компонентов бизнес-логики отделенными от Flutter дает возможность\nповторно использовать бизнес-логику на нескольких платформах/фреймворках\n(например, Flutter, AngularDart, Jaspr и т.д.).\n\n## Примеры\n\n**НЕ ИМПОРТИРУЙТЕ** Flutter в ваших компонентах бизнес-логики.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `avoid_flutter_imports`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: Избегайте публичных методов Bloc\ndescription: Правило avoid_public_bloc_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nИзбегайте предоставления публичных методов в экземплярах `Bloc`.\n\n## Обоснование\n\nБлоки реагируют на входящие события и выдают исходящие состояния. В результате\nрекомендуемый способ взаимодействия с экземпляром bloc — через метод `add`. В\nбольшинстве случаев нет необходимости создавать дополнительные абстракции поверх\nAPI `add`.\n\n![Архитектура Bloc](~/assets/concepts/bloc_architecture_full.png)\n\n## Примеры\n\n**НЕ ПРЕДОСТАВЛЯЙТЕ** публичные методы в экземплярах bloc.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `avoid_public_bloc_methods`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: Избегайте публичных полей\ndescription: Правило avoid_public_fields.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nИзбегайте предоставления публичных полей в экземплярах `Bloc` и `Cubit`.\n\n## Обоснование\n\nКомпоненты бизнес-логики поддерживают свое собственное `state` и выдают\nизменения состояния через API `emit`. В результате все публично доступное\nсостояние должно быть предоставлено через объект `state`.\n\n## Примеры\n\n**НЕ ПРЕДОСТАВЛЯЙТЕ** публичные поля в экземплярах bloc и cubit.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `avoid_public_fields`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: Предпочитайте Bloc\ndescription: Правило prefer_bloc.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nПредпочитайте использование экземпляров `Bloc` вместо экземпляров `Cubit`.\n\n## Обоснование\n\nЭто правило является чисто стилистическим. В некоторых случаях команды могут\nпредпочесть стандартизировать использование только экземпляров `Bloc` во всем\nприложении для согласованности.\n\n:::tip\n\nУзнайте больше о преимуществах `Bloc` в разделе\n[Основные концепции](/ru/bloc-concepts/#преимущества-bloc).\n\n:::\n\n## Примеры\n\n**Избегайте** использования экземпляров `Cubit`.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `prefer_bloc`, добавьте его в `analysis_options.yaml` в\nразделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: Предпочитайте расширения BuildContext\ndescription: Правило prefer_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nПредпочитайте использование расширений `BuildContext` для доступа к экземпляру\n`Bloc` или `Repository`.\n\n:::note\n\nЭто правило линтера было введено в версии `0.3.2` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обоснование\n\nДля согласованности предпочитайте использовать расширения `BuildContext` такие\nкак `context.read`, `context.watch` и `context.select` вместо `BlocProvider.of`,\n`RepositoryProvider.of`, `BlocBuilder` или `BlocSelector`.\n\n| явный метод                                                          | расширение            |\n| -------------------------------------------------------------------- | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                      | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` или `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                     | `context.select`      |\n\n## Примеры\n\n**Избегайте** использования `BlocProvider.of<T>(context)` для доступа к\nэкземпляру `Bloc`.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `prefer_build_context_extensions`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: Предпочитайте Cubit\ndescription: Правило prefer_cubit.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nПредпочитайте использование экземпляров `Cubit` вместо экземпляров `Bloc`.\n\n## Обоснование\n\nЭто правило является чисто стилистическим. В некоторых случаях команды могут\nпредпочесть стандартизировать использование только экземпляров `Cubit` во всем\nприложении для согласованности.\n\n:::tip\n\nУзнайте больше о преимуществах `Cubit` в разделе\n[Основные концепции](/ru/bloc-concepts/#преимущества-cubit).\n\n:::\n\n## Примеры\n\n**Избегайте** использования экземпляров `Bloc`.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `prefer_cubit`, добавьте его в `analysis_options.yaml` в\nразделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: Предпочитайте соглашения об именовании файлов\ndescription: Правило prefer_file_naming_conventions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nПредпочитайте следовать соглашениям об именовании файлов.\n\n:::note\n\nЭто правило линтера было введено в версии `0.3.0` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обоснование\n\nДля согласованности, простоты обслуживания и разделения ответственности\nпредпочтительно определять экземпляры bloc и cubit в их соответствующих файлах\nDart вместо встраивания их напрямую.\n\n:::tip\n\nРассмотрите использование команды `bloc new <component>` из пакета\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) для быстрого и\nпоследовательного генерирования новых экземпляров bloc/cubit.\n\n:::\n\n## Примеры\n\n**Предпочитайте** объявлять экземпляры bloc/cubit в их собственных\nсоответствующих файлах.\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n## Включение\n\nЧтобы включить правило `prefer_file_naming_conventions`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: Предпочитайте void публичные методы Cubit\ndescription: Правило prefer_void_public_cubit_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nПредпочитайте void публичные методы в экземплярах `Cubit`.\n\n:::note\n\nЭто правило линтера было введено в версии `0.2.0-dev.2` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обоснование\n\nПубличные методы в экземплярах `Cubit` должны использоваться для уведомления\n`Cubit` и инициации изменений состояния через метод `emit`. Если вызывающей\nстороне нужен доступ к какой-либо информации о состоянии, она должна получать её\nиз `state`.\n\n:::note\n\nСледующие правила связаны и обычно включаются в комбинации с\n`prefer_void_public_cubit_methods`.\n\n- [`avoid_public_bloc_methods`](/ru/lint-rules/avoid_public_bloc_methods)\n- [`avoid_public_fields`](/ru/lint-rules/avoid_public_fields)\n\n:::\n\n## Примеры\n\n**Избегайте** невозвратных (non-void) публичных методов в экземплярах `Cubit`.\n\n**ПЛОХО**:\n\n<BadSnippet />\n\n**ХОРОШО**:\n\n<GoodSnippet />\n\n## Включение\n\nЧтобы включить правило `prefer_void_public_cubit_methods`, добавьте его в\n`analysis_options.yaml` в разделе `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/ru/migration.mdx",
    "content": "---\ntitle: Руководство по миграции\ndescription: Мигрируйте на последнюю стабильную версию Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nПожалуйста, обратитесь к\n[журналу релизов](https://github.com/felangel/bloc/releases) для получения\nдополнительной информации о том, что изменилось в каждом релизе.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ Отделение `blocTest` от `BlocBase`\n\n:::note[Что изменилось?]\n\nВ bloc_test v10.0.0 API `blocTest` больше не тесно связан с `BlocBase`.\n\n:::\n\n##### Обоснование\n\n`blocTest` должен использовать основные интерфейсы bloc, когда это возможно, для\nповышения гибкости и возможности повторного использования. Ранее это было\nневозможно, поскольку `BlocBase` реализовывал `StateStreamableSource`, чего было\nнедостаточно для `blocTest` из-за внутренней зависимости от API `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Поддержка WebAssembly\n\n:::note[Что изменилось?]\n\nВ hydrated_bloc v10.0.0 была добавлена поддержка компиляции в WebAssembly\n(wasm).\n\n:::\n\n##### Обоснование\n\nРанее было невозможно компилировать приложения в wasm при использовании\n`hydrated_bloc`. В v10.0.0 пакет был переработан для поддержки компиляции в\nwasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Удаление устаревших API\n\n:::note[Что изменилось?]\n\nВ bloc v9.0.0 все ранее устаревшие API были удалены.\n\n:::\n\n##### Краткое изложение\n\n- `BlocOverrides` удален в пользу `Bloc.observer` и `Bloc.transformer`\n\n#### ❗✨ Введение нового интерфейса `EmittableStateStreamableSource`\n\n:::note[Что изменилось?]\n\nВ bloc v9.0.0 был введен новый основной интерфейс\n`EmittableStateStreamableSource`.\n\n:::\n\n##### Обоснование\n\n`package:bloc_test` ранее был тесно связан с `BlocBase`. Интерфейс\n`EmittableStateStreamableSource` был введен для того, чтобы позволить `blocTest`\nотделиться от конкретной реализации `BlocBase`.\n\n### `package:hydrated_bloc`\n\n#### ✨ Возврат API `HydratedBloc.storage`\n\n:::note[Что изменилось?]\n\nВ hydrated_bloc v9.0.0 `HydratedBlocOverrides` был удален в пользу API\n`HydratedBloc.storage`.\\*\\*\n\n:::\n\n##### Обоснование\n\nОбратитесь к\n[обоснованию возврата переопределений Bloc.observer и Bloc.transformer](/ru/migration#-возврат-api-blocobserver-и-bloctransformer).\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ Возврат API `Bloc.observer` и `Bloc.transformer`\n\n:::note[Что изменилось?]\n\nВ bloc v8.1.0 `BlocOverrides` был объявлен устаревшим в пользу API\n`Bloc.observer` и `Bloc.transformer`.\n\n:::\n\n##### Обоснование\n\nAPI `BlocOverrides` был введен в v8.0.0 в попытке поддержать ограничение области\nдействия конфигураций, специфичных для bloc, таких как `BlocObserver`,\n`EventTransformer`, и `HydratedStorage`. В чистых приложениях Dart изменения\nработали хорошо; однако в приложениях Flutter новый API вызвал больше проблем,\nчем решил.\n\nAPI `BlocOverrides` был вдохновлен похожими API во Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**Проблемы**\n\nХотя это не было основной причиной этих изменений, API `BlocOverrides` привнес\nдополнительную сложность для разработчиков. Помимо увеличения количества\nвложенности и строк кода, необходимых для достижения того же эффекта, API\n`BlocOverrides` требовал от разработчиков глубокого понимания\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) в Dart.\n`Zones` — это не удобная для начинающих концепция, и непонимание того, как\nработают Zones, может привести к появлению ошибок (таких как\nнеинициализированные наблюдатели, трансформеры, экземпляры хранилища).\n\nНапример, многие разработчики имели бы что-то вроде:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nПриведенный выше код, хотя и выглядит безобидным, на самом деле может привести\nко многим трудно отслеживаемым ошибкам. Какая бы зона ни вызвала первоначально\n`WidgetsFlutterBinding.ensureInitialized`, будет зоной, в которой обрабатываются\nсобытия жестов (например, колбэки `onTap`, `onPressed`) из-за\n`GestureBinding.initInstances`. Это всего лишь одна из многих проблем, вызванных\nиспользованием `zoneValues`.\n\nКроме того, Flutter делает много вещей за кулисами, которые включают\nразветвление/манипуляцию Zones (особенно при запуске тестов), что может привести\nк неожиданному поведению (и во многих случаях к поведению, которое находится вне\nконтроля разработчика — см. проблемы ниже).\n\nИз-за использования\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), переход на\nAPI `BlocOverrides` привел к обнаружению нескольких ошибок/ограничений во\nFlutter (особенно в виджетных и интеграционных тестах):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nкоторые затронули многих разработчиков, использующих библиотеку bloc:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ Введение нового API `BlocOverrides`\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 `Bloc.observer` и `Bloc.transformer` были удалены в пользу API\n`BlocOverrides`.\n\n:::\n\n##### Обоснование\n\nПредыдущий API, используемый для переопределения `BlocObserver` и\n`EventTransformer` по умолчанию, полагался на глобальный синглтон как для\n`BlocObserver`, так и для `EventTransformer`.\n\nВ результате было невозможно:\n\n- Иметь несколько реализаций `BlocObserver` или `EventTransformer`, ограниченных\n  различными частями приложения\n- Иметь переопределения `BlocObserver` или `EventTransformer`, ограниченные\n  пакетом\n  - Если пакет зависел от `package:bloc` и регистрировал свой собственный\n    `BlocObserver`, любой потребитель пакета должен был бы либо перезаписать\n    `BlocObserver` пакета, либо отчитываться перед `BlocObserver` пакета.\n\nТакже было сложнее тестировать из-за общего глобального состояния в тестах.\n\nBloc v8.0.0 вводит класс `BlocOverrides`, который позволяет разработчикам\nпереопределять `BlocObserver` и/или `EventTransformer` для конкретной `Zone`, а\nне полагаться на глобальный изменяемый синглтон.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nЭкземпляры `Bloc` будут использовать `BlocObserver` и/или `EventTransformer` для\nтекущей `Zone` через `BlocOverrides.current`. Если для зоны нет `BlocOverrides`,\nони будут использовать существующие внутренние значения по умолчанию (никаких\nизменений в поведении/функциональности).\n\nЭто позволяет каждой `Zone` функционировать независимо со своими\n`BlocOverrides`.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA и eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Bloc'и в этой зоне отчитываются перед BlocObserverA\n    // и используют eventTransformerA как трансформер по умолчанию.\n    // ...\n\n    // Позже...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB и eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Bloc'и в этой зоне отчитываются перед BlocObserverB\n        // и используют eventTransformerB как трансформер по умолчанию.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Улучшение обработки и отчетности об ошибках\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 `BlocUnhandledErrorException` удален. Кроме того, любые\nнеперехваченные исключения всегда сообщаются в `onError` и пробрасываются\nповторно (независимо от режима отладки или релиза). API `addError` сообщает об\nошибках в `onError`, но не рассматривает сообщенные ошибки как неперехваченные\nисключения.\n\n:::\n\n##### Обоснование\n\nЦель этих изменений:\n\n- сделать внутренние необработанные исключения чрезвычайно очевидными, сохраняя\n  при этом функциональность bloc\n- поддерживать `addError` без нарушения потока управления\n\nРанее обработка и отчетность об ошибках различались в зависимости от того,\nработало ли приложение в режиме отладки или релиза. Кроме того, ошибки,\nсообщенные через `addError`, рассматривались как неперехваченные исключения в\nрежиме отладки, что приводило к плохому опыту разработчика при использовании API\n`addError` (особенно при написании модульных тестов).\n\nВ v8.0.0 `addError` можно безопасно использовать для сообщения об ошибках, а\n`blocTest` можно использовать для проверки того, что ошибки сообщаются. Все\nошибки по-прежнему сообщаются в `onError`, однако повторно пробрасываются только\nнеперехваченные исключения (независимо от режима отладки или релиза).\n\n#### ❗🧹 Сделать `BlocObserver` абстрактным\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 `BlocObserver` был преобразован в `abstract` класс, что означает,\nчто экземпляр `BlocObserver` не может быть создан.\n\n:::\n\n##### Обоснование\n\n`BlocObserver` предназначался для использования в качестве интерфейса. Поскольку\nреализация API по умолчанию — это no-ops, `BlocObserver` теперь является\n`abstract` классом, чтобы четко показать, что класс предназначен для расширения,\nа не для прямого создания экземпляра.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // Было возможно создать экземпляр базового класса.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // Невозможно создать экземпляр базового класса.\n  final observer = BlocObserver(); // ОШИБКА\n\n  // Вместо этого расширьте `BlocObserver`.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ `add` выбрасывает `StateError`, если Bloc закрыт\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 вызов `add` на закрытом bloc приведет к `StateError`.\n\n:::\n\n##### Обоснование\n\nРанее было возможно вызвать `add` на закрытом bloc, и внутренняя ошибка\nпроглатывалась, что затрудняло отладку того, почему добавленное событие не\nобрабатывалось. Чтобы сделать этот сценарий более видимым, в v8.0.0 вызов `add`\nна закрытом bloc выбросит `StateError`, который будет сообщен как\nнеперехваченное исключение и передан в `onError`.\n\n#### ❗✨ `emit` выбрасывает `StateError`, если Bloc закрыт\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 вызов `emit` внутри закрытого bloc приведет к `StateError`.\n\n:::\n\n##### Обоснование\n\nРанее было возможно вызвать `emit` внутри закрытого bloc, и никакого изменения\nсостояния не происходило, но также не было никакого указания на то, что пошло не\nтак, что затрудняло отладку. Чтобы сделать этот сценарий более видимым, в v8.0.0\nвызов `emit` внутри закрытого bloc выбросит `StateError`, который будет сообщен\nкак неперехваченное исключение и передан в `onError`.\n\n#### ❗🧹 Удаление устаревших API\n\n:::note[Что изменилось?]\n\nВ bloc v8.0.0 все ранее устаревшие API были удалены.\n\n:::\n\n##### Краткое изложение\n\n- `mapEventToState` удален в пользу `on<Event>`\n- `transformEvents` удален в пользу API `EventTransformer`\n- typedef `TransitionFunction` удален в пользу API `EventTransformer`\n- `listen` удален в пользу `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` и `MockCubit` больше не требуют `registerFallbackValue`\n\n:::note[Что изменилось?]\n\nВ bloc_test v9.0.0 разработчикам больше не нужно явно вызывать\n`registerFallbackValue` при использовании `MockBloc` или `MockCubit`.\n\n:::\n\n##### Краткое изложение\n\n`registerFallbackValue` требуется только при использовании матчера `any()` из\n`package:mocktail` для пользовательского типа. Ранее `registerFallbackValue` был\nнеобходим для каждого `Event` и `State` при использовании `MockBloc` или\n`MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Тесты...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Тесты...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Введение нового API `HydratedBlocOverrides`\n\n:::note[Что изменилось?]\n\nВ hydrated_bloc v8.0.0 `HydratedBloc.storage` был удален в пользу API\n`HydratedBlocOverrides`.\n\n:::\n\n##### Обоснование\n\nРанее для переопределения реализации `Storage` использовался глобальный\nсинглтон.\n\nВ результате было невозможно иметь несколько реализаций `Storage`, ограниченных\nразличными частями приложения. Также было сложнее тестировать из-за общего\nглобального состояния в тестах.\n\n`HydratedBloc` v8.0.0 вводит класс `HydratedBlocOverrides`, который позволяет\nразработчикам переопределять `Storage` для конкретной `Zone`, а не полагаться на\nглобальный изменяемый синглтон.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\nЭкземпляры `HydratedBloc` будут использовать `Storage` для текущей `Zone` через\n`HydratedBlocOverrides.current`.\n\nЭто позволяет каждой `Zone` функционировать независимо со своими\n`BlocOverrides`.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ Введение нового API `on<Event>`\n\n:::note[Что изменилось?]\n\nВ bloc v7.2.0 `mapEventToState` был объявлен устаревшим в пользу `on<Event>`.\n`mapEventToState` будет удален в bloc v8.0.0.\n\n:::\n\n##### Обоснование\n\nAPI `on<Event>` был введен в рамках\n[[Предложение] Заменить mapEventToState на on\\<Event\\> в Bloc](https://github.com/felangel/bloc/issues/2526).\nИз-за [проблемы в Dart](https://github.com/dart-lang/sdk/issues/44616) не всегда\nочевидно, каким будет значение `state` при работе с вложенными асинхронными\nгенераторами (`async*`). Несмотря на то, что существуют способы обойти эту\nпроблему, одним из основных принципов библиотеки bloc является предсказуемость.\nAPI `on<Event>` был создан, чтобы сделать библиотеку максимально безопасной для\nиспользования и устранить любую неопределенность в отношении изменений\nсостояния.\n\n:::tip\n\nДля получения дополнительной информации\n[прочитайте полное предложение](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**Краткое изложение**\n\n`on<E>` позволяет зарегистрировать обработчик события для всех событий типа `E`.\nПо умолчанию события будут обрабатываться параллельно при использовании `on<E>`,\nв отличие от `mapEventToState`, который обрабатывает события `последовательно`.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nКаждая зарегистрированная функция `EventHandler` работает независимо, поэтому\nважно регистрировать обработчики событий на основе типа трансформера, который вы\nхотите применить.\n\n:::\n\nЕсли вы хотите сохранить точно такое же поведение, как в v7.1.0, вы можете\nзарегистрировать один обработчик событий для всех событий и применить\nтрансформер `sequential`:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: логика идет сюда...\n  }\n}\n```\n\nВы также можете переопределить `EventTransformer` по умолчанию для всех bloc'ов\nв вашем приложении:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ Введение нового API `EventTransformer`\n\n:::note[Что изменилось?]\n\nВ bloc v7.2.0 `transformEvents` был объявлен устаревшим в пользу API\n`EventTransformer`. `transformEvents` будет удален в bloc v8.0.0.\n\n:::\n\n##### Обоснование\n\nAPI `on<Event>` открыл возможность предоставления пользовательского трансформера\nсобытий для каждого обработчика событий. Был введен новый typedef\n`EventTransformer`, который позволяет разработчикам трансформировать входящий\nпоток событий для каждого обработчика событий, а не указывать один трансформер\nсобытий для всех событий.\n\n**Краткое изложение**\n\n`EventTransformer` отвечает за прием входящего потока событий вместе с\n`EventMapper` (ваш обработчик события) и возврат нового потока событий.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\n`EventTransformer` по умолчанию обрабатывает все события параллельно и выглядит\nпримерно так:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nОзнакомьтесь с\n[package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) для\nполучения предвзятого набора пользовательских трансформеров событий\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Определите пользовательский `EventTransformer`\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Примените пользовательский `EventTransformer` к `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ Устаревание API `transformTransitions`\n\n:::note[Что изменилось?]\n\nВ bloc v7.2.0 `transformTransitions` был объявлен устаревшим в пользу\nпереопределения API `stream`. `transformTransitions` будет удален в bloc v8.0.0.\n\n:::\n\n##### Обоснование\n\nГеттер `stream` в `Bloc` упрощает переопределение исходящего потока состояний,\nпоэтому больше нет необходимости поддерживать отдельный API\n`transformTransitions`.\n\n**Краткое изложение**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc и Cubit расширяют BlocBase\n\n##### Обоснование\n\nКак разработчик, отношения между bloc'ами и cubit'ами были немного неудобными.\nКогда cubit был впервые представлен, он начинался как базовый класс для bloc'ов,\nчто имело смысл, потому что он имел подмножество функциональности, а bloc'и\nпросто расширяли Cubit и определяли дополнительные API. Это привело к нескольким\nнедостаткам:\n\n- Все API должны были бы либо быть переименованы, чтобы принимать cubit для\n  точности, либо они должны были бы быть оставлены как bloc для\n  последовательности, даже несмотря на то, что иерархически это неточно\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- Cubit должен был расширять Stream и реализовывать EventSink, чтобы иметь общую\n  базу, на основе которой можно реализовать виджеты, такие как BlocBuilder,\n  BlocListener и т. д. ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nПозже мы экспериментировали с инверсией отношений и сделали bloc базовым\nклассом, что частично решило первый пункт выше, но привело к другим проблемам:\n\n- API cubit раздут из-за базовых API bloc, таких как mapEventToState, add и т.\n  д. ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - Разработчики технически могут вызывать эти API и ломать вещи\n- У нас все еще есть та же проблема с cubit, раскрывающим весь API stream, как и\n  раньше ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nЧтобы решить эти проблемы, мы ввели базовый класс как для `Bloc`, так и для\n`Cubit` под названием `BlocBase`, чтобы вышестоящие компоненты по-прежнему могли\nвзаимодействовать как с экземплярами bloc, так и с cubit, но без раскрытия всего\nAPI `Stream` и `EventSink` напрямую.\n\n**Краткое изложение**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed возвращает функцию для поддержки динамических значений\n\n##### Обоснование\n\nЧтобы поддерживать изменяемое начальное значение, которое можно динамически\nобновлять в `setUp`, `seed` возвращает функцию.\n\n**Краткое изложение**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect возвращает функцию для поддержки динамических значений и включает поддержку матчеров\n\n##### Обоснование\n\nЧтобы поддерживать изменяемое ожидание, которое можно динамически обновлять в\n`setUp`, `expect` возвращает функцию. `expect` также поддерживает `Matchers`.\n\n**Краткое изложение**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// Это также может быть `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors возвращает функцию для поддержки динамических значений и включает поддержку матчеров\n\n##### Обоснование\n\nЧтобы поддерживать изменяемые ошибки, которые можно динамически обновлять в\n`setUp`, `errors` возвращает функцию. `errors` также поддерживает `Matchers`.\n\n**Краткое изложение**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// Это также может быть `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc и MockCubit\n\n##### Обоснование\n\nДля поддержки заглушек различных основных API `MockBloc` и `MockCubit`\nэкспортируются как часть пакета `bloc_test`. Ранее `MockBloc` приходилось\nиспользовать как для экземпляров `Bloc`, так и для `Cubit`, что было\nнеинтуитивно.\n\n**Краткое изложение**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Интеграция с Mocktail\n\n##### Обоснование\n\nИз-за различных ограничений null-safe\n[package:mockito](https://pub.dev/packages/mockito), описанных\n[здесь](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) используется `MockBloc` и\n`MockCubit`. Это позволяет разработчикам продолжать использовать знакомый API\nдля моков без необходимости вручную писать заглушки или полагаться на генерацию\nкода.\n\n**Краткое изложение**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Пожалуйста, обратитесь к\n> [#347](https://github.com/dart-lang/mockito/issues/347), а также к\n> [документации mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> для получения дополнительной информации.\n\n### `package:flutter_bloc`\n\n#### ❗ переименование параметра `cubit` в `bloc`\n\n##### Обоснование\n\nВ результате рефакторинга в `package:bloc` для введения `BlocBase`, который\nрасширяют `Bloc` и `Cubit`, параметры `BlocBuilder`, `BlocConsumer` и\n`BlocListener` были переименованы из `cubit` в `bloc`, потому что виджеты\nработают с типом `BlocBase`. Это также дополнительно согласуется с названием\nбиблиотеки и, надеюсь, улучшает читаемость.\n\n**Краткое изложение**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory является обязательным при вызове HydratedStorage.build\n\n##### Обоснование\n\nЧтобы сделать `package:hydrated_bloc` чистым пакетом Dart, зависимость от\n[package:path_provider](https://pub.dev/packages/path_provider) была удалена, и\nпараметр `storageDirectory` при вызове `HydratedStorage.build` является\nобязательным и больше не использует `getTemporaryDirectory` по умолчанию.\n\n**Краткое изложение**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc и context.repository устарели в пользу context.read и context.watch\n\n##### Обоснование\n\n`context.read`, `context.watch` и `context.select` были добавлены для\nсогласования с существующим API [provider](https://pub.dev/packages/provider), с\nкоторым знакомы многие разработчики, и для решения проблем, поднятых\nсообществом. Чтобы повысить безопасность кода и поддерживать согласованность,\n`context.bloc` был объявлен устаревшим, потому что его можно заменить либо\n`context.read`, либо `context.watch` в зависимости от того, используется ли он\nнепосредственно внутри `build`.\n\n**context.watch**\n\n`context.watch` решает запрос на наличие\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538), потому что мы\nможем наблюдать за несколькими bloc'ами в одном `Builder`, чтобы отрендерить UI\nна основе нескольких состояний:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // вернуть Widget, который зависит от состояния BlocA, BlocB и BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` позволяет разработчикам рендерить/обновлять UI на основе части\nсостояния bloc и решает запрос на наличие\n[более простого buildWhen](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nПриведенный выше фрагмент позволяет нам получить доступ и перестроить виджет\nтолько тогда, когда имя текущего пользователя изменяется.\n\n**context.read**\n\nХотя кажется, что `context.read` идентичен `context.bloc`, есть некоторые\nтонкие, но значительные различия. Оба позволяют получить доступ к bloc с помощью\n`BuildContext` и не приводят к перестроениям; однако `context.read` не может\nбыть вызван непосредственно внутри метода `build`. Есть две основные причины\nиспользовать `context.bloc` внутри `build`:\n\n1. **Чтобы получить доступ к состоянию bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nПриведенное выше использование подвержено ошибкам, потому что виджет `Text` не\nбудет перестроен, если состояние bloc изменится. В этом сценарии следует\nиспользовать либо `BlocBuilder`, либо `context.watch`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nили\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nИспользование `context.watch` в корне метода `build` приведет к тому, что весь\nвиджет будет перестроен при изменении состояния bloc. Если весь виджет не нужно\nперестраивать, либо используйте `BlocBuilder`, чтобы обернуть части, которые\nдолжны перестраиваться, используйте `Builder` с `context.watch` для ограничения\nперестроений, либо разбейте виджет на более мелкие виджеты.\n\n:::\n\n2. **Чтобы получить доступ к bloc для добавления события**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nПриведенное выше использование неэффективно, потому что оно приводит к поиску\nbloc на каждом перестроении, когда bloc нужен только тогда, когда пользователь\nнажимает `ElevatedButton`. В этом сценарии предпочтительнее использовать\n`context.read` для доступа к bloc непосредственно там, где он нужен (в данном\nслучае в колбэке `onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Краткое изложение**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> Если доступ к bloc для добавления события, выполняйте доступ к bloc,\nиспользуя `context.read` в колбэке, где это необходимо.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Используйте `context.watch` при доступе к состоянию bloc, чтобы убедиться,\nчто виджет перестраивается при изменении состояния.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError принимает Cubit\n\n##### Обоснование\n\nИз-за интеграции `Cubit`, `onError` теперь является общим как для экземпляров\n`Bloc`, так и для `Cubit`. Поскольку `Cubit` является базовым, `BlocObserver`\nбудет принимать тип `Cubit`, а не тип `Bloc` в переопределении `onError`.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc не испускает последнее состояние при подписке\n\n##### Обоснование\n\nЭто изменение было внесено для согласования `Bloc` и `Cubit` с встроенным\nповедением `Stream` в `Dart`. Кроме того, соответствие старому поведению в\nконтексте `Cubit` привело ко многим непредвиденным побочным эффектам и в целом\nусложнило внутренние реализации других пакетов, таких как `flutter_bloc` и\n`bloc_test`, без необходимости (требующие `skip(1)` и т. д.).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nРанее приведенный выше фрагмент выводил начальное состояние bloc, за которым\nследовали последующие изменения состояния.\n\n**v6.x.x**\n\nВ v6.0.0 приведенный выше фрагмент не выводит начальное состояние и выводит\nтолько последующие изменения состояния. Предыдущее поведение может быть\nдостигнуто следующим образом:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Примечание**: Это изменение повлияет только на код, который зависит от\nпрямых подписок на bloc. При использовании `BlocBuilder`, `BlocListener` или\n`BlocConsumer` не будет заметного изменения в поведении.\n\n### `package:bloc_test`\n\n#### ❗MockBloc требует только тип State\n\n##### Обоснование\n\nЭто не обязательно и устраняет лишний код, а также делает `MockBloc` совместимым\nс `Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen требует только тип State\n\n##### Обоснование\n\nЭто не обязательно и устраняет лишний код, а также делает `whenListen`\nсовместимым с `Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest не требует тип Event\n\n##### Обоснование\n\nЭто не обязательно и устраняет лишний код, а также делает `blocTest` совместимым\nс `Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip по умолчанию равен 0\n\n##### Обоснование\n\nПоскольку экземпляры `bloc` и `cubit` больше не будут испускать последнее\nсостояние для новых подписок, больше не было необходимости устанавливать `skip`\nпо умолчанию равным `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nНачальное состояние bloc или cubit можно протестировать следующим образом:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest делает build синхронным\n\n##### Обоснование\n\nРанее `build` был сделан `async`, чтобы можно было выполнить различные\nподготовительные действия для приведения тестируемого bloc в определенное\nсостояние. Это больше не нужно и также решает несколько проблем из-за\nдополнительной задержки между build и подпиской внутренне. Вместо того чтобы\nвыполнять асинхронную подготовку для приведения bloc в желаемое состояние, мы\nтеперь можем установить состояние bloc, связав `emit` с желаемым состоянием.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` виден только для тестирования и никогда не должен использоваться вне\nтестов.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗параметр bloc в BlocBuilder переименован в cubit\n\n##### Обоснование\n\nЧтобы сделать `BlocBuilder` совместимым с экземплярами `bloc` и `cubit`,\nпараметр `bloc` был переименован в `cubit` (поскольку `Cubit` является базовым\nклассом).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗параметр bloc в BlocListener переименован в cubit\n\n##### Обоснование\n\nЧтобы сделать `BlocListener` совместимым с экземплярами `bloc` и `cubit`,\nпараметр `bloc` был переименован в `cubit` (поскольку `Cubit` является базовым\nклассом).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗параметр bloc в BlocConsumer переименован в cubit\n\n##### Обоснование\n\nЧтобы сделать `BlocConsumer` совместимым с экземплярами `bloc` и `cubit`,\nпараметр `bloc` был переименован в `cubit` (поскольку `Cubit` является базовым\nклассом).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState был удален\n\n##### Обоснование\n\nКак разработчик, необходимость переопределять `initialState` при создании bloc\nпредставляет две основные проблемы:\n\n- `initialState` bloc может быть динамическим и также может быть указан позже\n  (даже вне самого bloc). В некотором смысле это можно рассматривать как утечку\n  внутренней информации bloc на уровень UI.\n- Это многословно.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> Для получения дополнительной информации ознакомьтесь с\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate переименован в BlocObserver\n\n##### Обоснование\n\nНазвание `BlocDelegate` не было точным описанием роли, которую играл класс.\n`BlocDelegate` предполагает, что класс играет активную роль, тогда как в\nдействительности предполагаемая роль `BlocDelegate` заключалась в том, чтобы\nбыть пассивным компонентом, который просто наблюдает за всеми bloc'ами в\nприложении.\n\n:::note\n\nВ идеале не должно быть никаких пользовательских функций или возможностей,\nобрабатываемых внутри `BlocObserver`.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor был удален\n\n##### Обоснование\n\n`BlocSupervisor` был еще одним компонентом, о котором разработчикам нужно было\nзнать и взаимодействовать с ним исключительно для указания пользовательского\n`BlocDelegate`. С изменением на `BlocObserver` мы почувствовали, что это\nулучшило опыт разработчика, установив наблюдателя непосредственно на самом bloc.\n\n?> Это изменение также позволило нам отделить другие дополнения к bloc, такие\nкак `HydratedStorage`, от `BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗condition в BlocBuilder переименовано в buildWhen\n\n##### Обоснование\n\nПри использовании `BlocBuilder` мы ранее могли указать `condition`, чтобы\nопределить, должен ли `builder` перестраиваться.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nНазвание `condition` не очень самообъяснительно или очевидно, и, что более\nважно, при взаимодействии с `BlocConsumer` API стал несогласованным, потому что\nразработчики могут предоставить два условия (одно для `builder` и одно для\n`listener`). В результате API `BlocConsumer` раскрыл `buildWhen` и `listenWhen`\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nЧтобы согласовать API и обеспечить более последовательный опыт разработчика,\n`condition` был переименован в `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗condition в BlocListener переименовано в listenWhen\n\n##### Обоснование\n\nПо тем же причинам, что описаны выше, условие `BlocListener` также было\nпереименовано.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // вернуть true/false, чтобы определить, нужно ли вызывать listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage и HydratedBlocStorage переименованы\n\n##### Обоснование\n\nЧтобы улучшить повторное использование кода между\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) и\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), конкретная реализация\nхранилища по умолчанию была переименована из `HydratedBlocStorage` в\n`HydratedStorage`. Кроме того, интерфейс `HydratedStorage` был переименован из\n`HydratedStorage` в `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage отделен от BlocDelegate\n\n##### Обоснование\n\nКак упоминалось ранее, `BlocDelegate` был переименован в `BlocObserver` и был\nустановлен непосредственно как часть `bloc` через:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nСледующее изменение было внесено для:\n\n- Оставаться согласованным с новым API наблюдателя bloc\n- Ограничить область действия хранилища только `HydratedBloc`\n- Отделить `BlocObserver` от `Storage`\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Упрощенная инициализация\n\n##### Обоснование\n\nРанее разработчикам приходилось вручную вызывать\n`super.initialState ?? DefaultInitialState()` для настройки своих экземпляров\n`HydratedBloc`. Это неудобно и многословно, а также несовместимо с критическими\nизменениями в `initialState` в `bloc`. В результате в v5.0.0 инициализация\n`HydratedBloc` идентична обычной инициализации `Bloc`.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/ru/modeling-state.mdx",
    "content": "---\ntitle: Моделирование состояния\ndescription:\n  Обзор нескольких способов моделирования состояний при использовании\n  package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nСуществует множество различных подходов к структурированию состояния приложения.\nКаждый из них имеет свои преимущества и недостатки. В этом разделе мы рассмотрим\nнесколько подходов, их плюсы и минусы, а также когда использовать каждый из них.\n\nСледующие подходы являются просто рекомендациями и являются полностью\nнеобязательными. Не стесняйтесь использовать любой подход, который вы\nпредпочитаете. Вы можете обнаружить, что некоторые примеры/документация не\nследуют подходам в основном для простоты/краткости.\n\n:::tip\n\nСледующие фрагменты кода сосредоточены на структуре состояния. На практике вы\nтакже можете захотеть:\n\n- Расширить `Equatable` из\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- Аннотировать класс с помощью `@Data()` из\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- Аннотировать класс с помощью **@immutable** из\n  [`package:meta`](https://pub.dev/packages/meta)\n- Реализовать метод `copyWith`\n- Использовать ключевое слово `const` для конструкторов\n\n:::\n\n## Конкретный класс и enum статуса\n\nЭтот подход состоит из **одного конкретного класса** для всех состояний вместе с\n`enum`, представляющим различные статусы. Свойства делаются nullable и\nобрабатываются на основе текущего статуса. Этот подход лучше всего работает для\nсостояний, которые не являются строго эксклюзивными и/или содержат много общих\nсвойств.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Плюсы\n\n- **Просто**: Легко управлять одним классом и enum статуса, и все свойства легко\n  доступны.\n- **Кратко**: Обычно требует меньше строк кода по сравнению с другими подходами.\n\n#### Минусы\n\n- **Не типобезопасно**: Требует проверки `status` перед доступом к свойствам.\n  Возможно `emit` неправильно сформированное состояние, что может привести к\n  ошибкам. Свойства для конкретных состояний являются nullable, что может быть\n  обременительным для управления и требует либо принудительного извлечения, либо\n  выполнения проверок на null. Некоторые из этих минусов могут быть смягчены\n  написанием модульных тестов и написанием специализированных именованных\n  конструкторов.\n- **Раздутый**: Приводит к одному состоянию, которое может стать раздутым с\n  множеством свойств со временем.\n\n#### Вердикт\n\nЭтот подход лучше всего работает для простых состояний или когда требования\nтребуют состояний, которые не являются эксклюзивными (например, показ snackbar\nпри возникновении ошибки при сохранении старых данных из последнего успешного\nсостояния). Этот подход обеспечивает гибкость и краткость за счет\nтипобезопасности.\n\n## Запечатанный класс и подклассы\n\nЭтот подход состоит из **запечатанного класса**, который содержит любые общие\nсвойства, и нескольких подклассов для отдельных состояний. Этот подход отлично\nподходит для раздельных эксклюзивных состояний.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Плюсы\n\n- **Типобезопасно**: Код безопасен на этапе компиляции, и невозможно случайно\n  получить доступ к недопустимому свойству. Каждый подкласс содержит свои\n  собственные свойства, что делает ясным, какие свойства принадлежат какому\n  состоянию.\n- **Явно:** Разделяет общие свойства от специфичных для состояния свойств.\n- **Исчерпывающе**: Использование оператора `switch` для проверки\n  исчерпывающести, чтобы гарантировать, что каждое состояние явно обработано.\n  - Если вы не хотите\n    [исчерпывающего переключения](https://dart.dev/language/branches#exhaustiveness-checking)\n    или хотите иметь возможность добавлять подтипы позже без нарушения API,\n    используйте модификатор\n    [final](https://dart.dev/language/class-modifiers#final).\n  - См.\n    [документацию по запечатанным классам](https://dart.dev/language/class-modifiers#sealed)\n    для получения более подробной информации.\n\n#### Минусы\n\n- **Многословно**: Требует больше кода (один базовый класс и подкласс для\n  каждого состояния). Также может потребоваться дублирование кода для общих\n  свойств в подклассах.\n- **Сложно**: Добавление новых свойств требует обновления каждого подкласса и\n  базового класса, что может быть обременительным и привести к увеличению\n  сложности состояния. Кроме того, может потребоваться ненужная/избыточная\n  проверка типов для доступа к свойствам.\n\n#### Вердикт\n\nЭтот подход лучше всего работает для хорошо определенных эксклюзивных состояний\nс уникальными свойствами. Этот подход обеспечивает типобезопасность и\nисчерпывающие проверки и подчеркивает безопасность над краткостью и простотой.\n"
  },
  {
    "path": "docs/src/content/docs/ru/naming-conventions.mdx",
    "content": "---\ntitle: Соглашения об именовании\ndescription:\n  Обзор рекомендуемых соглашений об именовании при использовании bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nСледующие соглашения об именовании являются просто рекомендациями и являются\nполностью необязательными. Не стесняйтесь использовать любые соглашения об\nименовании, которые вы предпочитаете. Вы можете обнаружить, что некоторые\nпримеры/документация не следуют соглашениям об именовании в основном для\nпростоты/краткости. Эти соглашения настоятельно рекомендуются для больших\nпроектов с несколькими разработчиками.\n\n## Соглашения о событиях\n\nСобытия должны быть названы в **прошедшем времени**, потому что события — это\nто, что уже произошло с точки зрения блока.\n\n### Анатомия\n\n`BlocSubject` + `Существительное (необязательно)` + `Глагол (событие)`\n\nСобытия начальной загрузки должны следовать соглашению: `BlocSubject` +\n`Started`\n\n:::note\n\nБазовый класс события должен быть назван: `BlocSubject` + `Event`.\n\n:::\n\n### Примеры\n\n✅ **Хорошо**\n\n<EventExamplesGood1 />\n\n❌ **Плохо**\n\n<EventExamplesBad1 />\n\n## Соглашения о состояниях\n\nСостояния должны быть существительными, потому что состояние — это просто снимок\nв определенный момент времени. Существует два распространенных способа\nпредставления состояния: использование подклассов или использование одного\nкласса.\n\n### Анатомия\n\n#### Подклассы\n\n`BlocSubject` + `Глагол (действие)` + `State`\n\nПри представлении состояния в виде нескольких подклассов `State` должно быть\nодним из следующих:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nНачальные состояния должны следовать соглашению: `BlocSubject` + `Initial`.\n\n:::\n\n#### Один класс\n\n`BlocSubject` + `State`\n\nПри представлении состояния в виде одного базового класса должен использоваться\nenum с именем `BlocSubject` + `Status` для представления статуса состояния:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nБазовый класс состояния всегда должен быть назван: `BlocSubject` + `State`.\n\n:::\n\n### Примеры\n\n✅ **Хорошо**\n\n##### Подклассы\n\n<StateExamplesGood1Snippet />\n\n##### Один класс\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Плохо**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/ru/testing.mdx",
    "content": "---\ntitle: Тестирование\ndescription: Основы написания тестов для ваших блоков.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc был разработан так, чтобы его было чрезвычайно легко тестировать. В этом\nразделе мы рассмотрим, как провести модульное тестирование блока.\n\nДля простоты давайте напишем тесты для `CounterBloc`, который мы создали в\nразделе [Основные концепции](/ru/bloc-concepts).\n\nНапомним, что реализация `CounterBloc` выглядит так:\n\n<CounterBlocSnippet />\n\n## Настройка\n\nПрежде чем начать писать наши тесты, нам нужно добавить фреймворк для\nтестирования в наши зависимости.\n\nНам нужно добавить [test](https://pub.dev/packages/test) и\n[bloc_test](https://pub.dev/packages/bloc_test) в наш проект.\n\n<AddDevDependenciesSnippet />\n\n## Тестирование\n\nДавайте начнем с создания файла для наших тестов `CounterBloc`,\n`counter_bloc_test.dart`, и импортируем пакет test.\n\n<CounterBlocTestImportsSnippet />\n\nДалее нам нужно создать нашу функцию `main`, а также нашу группу тестов.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nГруппы используются для организации отдельных тестов, а также для создания\nконтекста, в котором вы можете совместно использовать общие `setUp` и `tearDown`\nдля всех отдельных тестов.\n\n:::\n\nДавайте начнем с создания экземпляра нашего `CounterBloc`, который будет\nиспользоваться во всех наших тестах.\n\n<CounterBlocTestSetupSnippet />\n\nТеперь мы можем начать писать наши индивидуальные тесты.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nМы можем запустить все наши тесты с помощью команды `dart test`.\n\n:::\n\nНа этом этапе у нас должен быть первый пройденный тест! Теперь давайте напишем\nболее сложный тест, используя пакет\n[bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nМы должны иметь возможность запустить тесты и увидеть, что все они проходят.\n\nВот и все, тестирование должно быть легким, и мы должны чувствовать уверенность\nпри внесении изменений и рефакторинге нашего кода.\n\nВы можете обратиться к\n[приложению Weather](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nв качестве примера полностью протестированного приложения.\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Счетчик Flutter\ndescription:\n  Подробное руководство по созданию приложения-счетчика Flutter с использованием\n  bloc.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nВ следующем руководстве мы собираемся создать счетчик на Flutter используя\nбиблиотеку Bloc.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## Ключевые темы\n\n- Наблюдение за изменениями состояния с помощью\n  [BlocObserver](/ru/bloc-concepts#blocobserver).\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), виджет Flutter который\n  предоставляет bloc своим дочерним элементам.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), виджет Flutter который\n  обрабатывает построение виджета в ответ на новые состояния.\n- Использование Cubit вместо Bloc.\n  [В чем разница?](/ru/bloc-concepts/#cubit-против-bloc)\n- Добавление событий с помощью\n  [context.read](/ru/flutter-bloc-concepts#contextread).\n\n## Настройка\n\nНачнем с создания нового Flutter проекта\n\n<FlutterCreateSnippet />\n\nЗатем можем заменить содержимое `pubspec.yaml` на\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nи затем установить все наши зависимости\n\n<FlutterPubGetSnippet />\n\n## Структура проекта\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nПриложение использует структуру каталогов, ориентированную на функции. Эта\nструктура проекта позволяет нам масштабировать проект, имея автономные функции.\nВ этом примере у нас будет только одна функция (сам счетчик), но в более сложных\nприложениях у нас может быть сотни различных функций.\n\n## BlocObserver\n\nПервое, на что мы обратим внимание, это как создать `BlocObserver` который\nпоможет нам наблюдать все изменения состояния в приложении.\n\nДавайте создадим `lib/counter_observer.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\nВ данном случае мы переопределяем только `onChange` чтобы увидеть все изменения\nсостояния, которые происходят.\n\n:::note\n\n`onChange` работает одинаково для экземпляров `Bloc` и `Cubit`.\n\n:::\n\n## main.dart\n\nДалее, заменим содержимое `lib/main.dart` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nМы инициализируем только что созданный `CounterObserver` и вызываем `runApp` с\nвиджетом `CounterApp`, который мы рассмотрим далее.\n\n## Counter App\n\nСоздадим `lib/app.dart`:\n\n`CounterApp` будет `MaterialApp` и указывает `home` как `CounterPage`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nМы расширяем `MaterialApp` потому что `CounterApp` _является_ `MaterialApp`. В\nбольшинстве случаев мы будем создавать экземпляры `StatelessWidget` или\n`StatefulWidget` и компоновать виджеты в `build`, но в данном случае нет\nвиджетов для компоновки, поэтому проще просто расширить `MaterialApp`.\n\n:::\n\nДавайте рассмотрим `CounterPage` далее!\n\n## Counter Page\n\nСоздадим `lib/counter/view/counter_page.dart`:\n\nВиджет `CounterPage` отвечает за создание `CounterCubit` (который мы рассмотрим\nдалее) и предоставление его `CounterView`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\nВажно отделить или разъединить создание `Cubit` от использования `Cubit`, чтобы\nиметь код, который намного более тестируем и пригоден для повторного\nиспользования.\n\n:::\n\n## Counter Cubit\n\nСоздадим `lib/counter/cubit/counter_cubit.dart`:\n\nКласс `CounterCubit` предоставит два метода:\n\n- `increment`: добавляет 1 к текущему состоянию\n- `decrement`: вычитает 1 из текущего состояния\n\nТип состояния, которым управляет `CounterCubit`, это просто `int` и начальное\nсостояние равно `0`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\nИспользуйте\n[VSCode расширение](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nили [IntelliJ плагин](https://plugins.jetbrains.com/plugin/12129-bloc) для\nавтоматического создания новых cubits.\n\n:::\n\nДалее, давайте рассмотрим `CounterView`, который будет отвечать за использование\nсостояния и взаимодействие с `CounterCubit`.\n\n## Counter View\n\nСоздадим `lib/counter/view/counter_view.dart`:\n\n`CounterView` отвечает за отображение текущего счета и отображение двух\nFloatingActionButtons для увеличения/уменьшения счетчика.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\n`BlocBuilder` используется для обертывания виджета `Text` чтобы обновлять текст\nкаждый раз когда изменяется состояние `CounterCubit`. Кроме того,\n`context.read<CounterCubit>()` используется для поиска ближайшего экземпляра\n`CounterCubit`.\n\n:::note\n\nТолько виджет `Text` обернут в `BlocBuilder` потому что это единственный виджет,\nкоторый необходимо перестроить в ответ на изменения состояния в `CounterCubit`.\nИзбегайте ненужного оборачивания виджетов, которые не нужно перестраивать при\nизменении состояния.\n\n:::\n\n## Barrel\n\nСоздадим `lib/counter/view/view.dart`:\n\nДобавим `view.dart` для экспорта всех публичных частей counter view.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\nСоздадим `lib/counter/counter.dart`:\n\nДобавим `counter.dart` для экспорта всех публичных частей функции counter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\nВот и все! Мы отделили слой представления от слоя бизнес-логики. `CounterView`\nне знает, что происходит, когда пользователь нажимает кнопку; он просто\nуведомляет `CounterCubit`. Кроме того, `CounterCubit` не знает, что происходит\nсо состоянием (значением счетчика); он просто выдает новые состояния в ответ на\nвызываемые методы.\n\nМы можем запустить наше приложение с помощью `flutter run` и можем просмотреть\nего на нашем устройстве или симуляторе/эмуляторе.\n\nПолный исходный код (включая unit и widget тесты) этого примера можно найти\n[здесь](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: Вход Flutter Firebase\ndescription:\n  Подробное руководство по созданию потока входа Flutter с использованием bloc и\n  Firebase.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn the following tutorial, we're going to build a Firebase Login Flow in Flutter\nusing the Bloc library.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## Ключевые темы\n\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), a Flutter widget which\n  provides a bloc to its children.\n- Using Cubit instead of Bloc.\n  [What's the difference?](/ru/bloc-concepts/#cubit-против-bloc)\n- Adding events with [context.read](/ru/flutter-bloc-concepts#contextread).\n- Prevent unnecessary rebuilds with\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- [RepositoryProvider](/ru/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget which provides a repository to its children.\n- [BlocListener](/ru/flutter-bloc-concepts#bloclistener), a Flutter widget which\n  invokes the listener code in response to state changes в каталоге bloc.\n- Adding events with [context.read](/ru/flutter-bloc-concepts#contextselect).\n\n## Настройка\n\nНачнем с создания нового Flutter проекта.\n\n<FlutterCreateSnippet />\n\nТак же, как в [руководстве по входу](/ru/tutorials/flutter-login), we're going\nto create internal packages to better layer our application architecture and\nmaintain clear boundaries and to maximize both reusability as well as improve\ntestability.\n\nIn this case, the [firebase_auth](https://pub.dev/packages/firebase_auth) and\n[google_sign_in](https://pub.dev/packages/google_sign_in) packages are going to\nbe our data layer so we're only going to be creating an\n`AuthenticationRepository` to compose data from the two API clients.\n\n## Authentication Repository\n\nThe `AuthenticationRepository` will be responsible for abstracting the internal\nimplementation details of how we authenticate and fetch user information. In\nthis case, it will be integrating with Firebase but we can always change the\ninternal implementation later on and our application will be unaffected.\n\n### Настройка\n\nWe'll start by creating `packages/authentication_repository` and a\n`pubspec.yaml` at the root of the project.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\nДалее, мы можем установить зависимости, запустив:\n\n<FlutterPubGetSnippet />\n\nв каталоге `authentication_repository` .\n\nJust like most packages, the `authentication_repository` will define it's API\nsurface via\n`packages/authentication_repository/lib/authentication_repository.dart`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\nThe `authentication_repository` package will be exposing an\n`AuthenticationRepository` as well as models.\n\n:::\n\nДалее, рассмотрим модели.\n\n### User\n\nThe `User` model will describe a user в каталоге context of the authentication\ndomain. For the purposes of this example, a user will consist of an `email`,\n`id`, `name`, and `photo`.\n\n:::note\n\nIt's completely up to you to define what a user needs to look like в каталоге\ncontext of your domain.\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\nThe `User` class is extending [equatable](https://pub.dev/packages/equatable) in\norder to override equality comparisons so that we can compare different\ninstances of `User` by value.\n\n:::\n\n:::tip\n\nIt's useful to define a `static` empty `User` so that we don't have to handle\n`null` Users and can always work with a concrete `User` object.\n\n:::\n\n### Repository\n\nThe `AuthenticationRepository` is responsible for abstracting the underlying\nimplementation of how a user is authenticated, as well as how a user is fetched.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nThe `AuthenticationRepository` exposes a `Stream<User>` which we can subscribe\nto in order to be notified of when a `User` changes. In addition, it exposes\nmethods to `signUp`, `logInWithGoogle`, `logInWithEmailAndPassword`, and\n`logOut`.\n\n:::note\n\nThe `AuthenticationRepository` is also responsible for handling low-level errors\nthat can occur в каталоге data layer and exposes a clean, simple set of errors\nthat align with the domain.\n\n:::\n\nThat's it for the `AuthenticationRepository`. Next, let's take a look at how to\nintegrate it into the Flutter project we created.\n\n## Настройка Firebase\n\nWe need to follow the\n[firebase_auth usage instructions](https://pub.dev/packages/firebase_auth#usage)\nin order to hook up our application to Firebase and enable\n[google_sign_in](https://pub.dev/packages/google_sign_in).\n\n:::caution\n\nRemember to update the `google-services.json` on Android and the\n`GoogleService-Info.plist` & `Info.plist` on iOS, otherwise the application will\ncrash.\n\n:::\n\n## Зависимости проекта\n\nWe can replace the generated `pubspec.yaml` at the root of the project with the\nfollowing:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nNotice that we are specifying an assets directory for all of our applications\nlocal assets. Create an `assets` directory в каталоге root of your project and\nadd the\n[bloc logo](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\nasset (which we'll use later).\n\nThen install all of the dependencies:\n\n<FlutterPubGetSnippet />\n\n:::note\n\nWe are depending on the `authentication_repository` package via path which will\nallow us to iterate quickly while still maintaining a clear separation.\n\n:::\n\n## main.dart\n\nThe `main.dart` file can be replaced with the following:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nIt's simply setting up some global configuration for the application and calling\n`runApp` with an instance of `App`.\n\n:::note\n\nWe're injecting a single instance of `AuthenticationRepository` into the `App`\nand it is an explicit constructor dependency.\n\n:::\n\n## App\n\nТак же, как в [руководстве по входу](/ru/tutorials/flutter-login), our\n`app.dart` will provide an instance of the `AuthenticationRepository` to the\napplication via `RepositoryProvider` and also creates and provides an instance\nof `AuthenticationBloc`. Then `AppView` consumes the `AuthenticationBloc` and\nhandles updating the current route based on the `AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\nThe `AppBloc` is responsible for managing the global state of the application.\nIt has a dependency on the `AuthenticationRepository` and subscribes to the\n`user` Stream in order to emit new states in response to changes в каталоге\ncurrent user.\n\n### State\n\nThe `AppState` consists of an `AppStatus` and a `User`. The default constructor\naccepts an optional `User` and redirects to the private constructor with the\nappropriate authentication status.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### Event\n\nThe `AppEvent` has two subclasses:\n\n- `AppUserSubscriptionRequested` which notifies the bloc to subscribe to the\n  user stream.\n- `AppLogoutPressed` which notifies the bloc of a user logout action.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\nIn the constructor body, `AppEvent` subclasses are mapped to their corresponding\nevent handlers.\n\nIn the `_onUserSubscriptionRequested` event handler, the `AppBloc` uses\n`emit.onEach` to subscribe to the user stream of the `AuthenticationRepository`\nand emit a state in response to each `User`.\n\n`emit.onEach` creates a stream subscription internally and takes care of\ncanceling it when either `AppBloc` or the user stream is closed.\n\nIf the user stream emits an error, `addError` forwards the error and stack trace\nto any `BlocObserver` listening.\n\n:::caution\n\nIf `onError` is omitted, any errors on the user stream are considered unhandled,\nand will be thrown by `onEach`. As a result, the subscription to the user stream\nwill be canceled.\n\n:::\n\n:::tip\n\nA [`BlocObserver`](/ru/bloc-concepts/#blocobserver-1) is great for logging Bloc\nevents, errors, and state changes especially в каталоге context analytics and\ncrash reporting.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## Модели\n\nAn `Email` and `Password` input model are useful for encapsulating the\nvalidation logic and will be used in both the `LoginForm` and `SignUpForm`\n(later в каталоге tutorial).\n\nBoth input models are made using the [formz](https://pub.dev/packages/formz)\npackage and allow us to work with a validated object rather than a primitive\ntype like a `String`.\n\n### Email\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## Страница входа\n\nThe `LoginPage` is responsible for creating and providing an instance of\n`LoginCubit` to the `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nIt's very important to keep the creation of blocs/cubits separate from where\nthey are consumed. This will allow you to easily inject mock instances and test\nyour view in isolation.\n\n:::\n\n## Login Cubit\n\nThe `LoginCubit` is responsible for managing the `LoginState` of the form. It\nexposes APIs to `logInWithCredentials`, `logInWithGoogle`, as well as gets\nnotified when the email/password are updated.\n\n### State\n\nThe `LoginState` consists of an `Email`, `Password`, and `FormzStatus`. The\n`Email` and `Password` models extend `FormzInput` from the\n[formz](https://pub.dev/packages/formz) package.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\nThe `LoginCubit` has a dependency on the `AuthenticationRepository` in order to\nsign the user in either via credentials or via google sign in.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\nWe used a `Cubit` instead of a `Bloc` here because the `LoginState` is fairly\nsimple and localized. Even without events, we can still have a fairly good sense\nof what happened just by looking at the changes from one state to another and\nour code is a lot simpler and more concise.\n\n:::\n\n## Форма входа\n\nThe `LoginForm` is responsible for rendering the form in response to the\n`LoginState` and invokes methods on the `LoginCubit` in response to user\ninteractions.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\nThe `LoginForm` also renders a \"Create Account\" button which navigates to the\n`SignUpPage` where a user can create a brand new account.\n\n## Страница регистрации\n\nThe `SignUp` structure mirrors the `Login` structure and consists of a\n`SignUpPage`, `SignUpView`, and `SignUpCubit`.\n\nThe `SignUpPage` is just responsible for creating and providing an instance of\nthe `SignUpCubit` to the `SignUpForm` (exactly like in `LoginPage`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\nJust as в каталоге `LoginCubit`, the `SignUpCubit` has a dependency on the\n`AuthenticationRepository` in order to create new user accounts.\n\n:::\n\n## Sign Up Cubit\n\nThe `SignUpCubit` manages the state of the `SignUpForm` and communicates with\nthe `AuthenticationRepository` in order to create new user accounts.\n\n### State\n\nThe `SignUpState` reuses the same `Email` and `Password` form input models\nbecause the validation logic is the same.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\nThe `SignUpCubit` is extremely similar to the `LoginCubit` with the main\nexception being it exposes an API to submit the form as opposed to login.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## Форма регистрации\n\nThe `SignUpForm` is responsible for rendering the form in response to the\n`SignUpState` and invokes methods on the `SignUpCubit` in response to user\ninteractions.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## Домашняя страница\n\nAfter a user either successfully logs in or signs up, the `user` stream will be\nupdated which will trigger a state change в каталоге `AuthenticationBloc` and\nwill result в каталоге `AppView` pushing the `HomePage` route onto the\nnavigation stack.\n\nFrom the `HomePage`, the user can view their profile information and log out by\ntapping the exit icon в каталоге `AppBar`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\nA `widgets` directory was created alongside the `view` directory withв каталоге\n`home` feature for reusable components that are specific to that particular\nfeature. In this case a simple `Avatar` widget is exported and used withв\nкаталоге `HomePage`.\n\n:::\n\n:::note\n\nWhen the logout `IconButton` is tapped, an `AuthenticationLogoutRequested` event\nis added to the `AuthenticationBloc` which signs the user out and navigates them\nback to the `LoginPage`.\n\n:::\n\nAt this point we have a pretty solid login implementation using Firebase and we\nhave decoupled our presentation layer from the business logic layer by using the\nBloc Library.\n\nПолный исходный код этого примера можно найти\n[here](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: Бесконечный список Flutter\ndescription:\n  Подробное руководство по созданию бесконечного списка Flutter с использованием\n  bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nВ этом руководстве мы создадим приложение, которое извлекает данные по сети и\nзагружает их по мере прокрутки пользователем, используя Flutter и библиотеку\nbloc.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## Ключевые темы\n\n- Наблюдение за изменениями состояния с помощью\n  [BlocObserver](/ru/bloc-concepts#blocobserver).\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), виджет Flutter который\n  предоставляет bloc своим дочерним элементам.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), виджет Flutter который\n  обрабатывает построение виджета в ответ на новые состояния.\n- Добавление событий с помощью\n  [context.read](/ru/flutter-bloc-concepts#contextread).\n- Предотвращение ненужных перестроек с помощью\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- Использование метода `transformEvents` с Rx.\n\n## Настройка\n\nНачнем с создания нового Flutter проекта\n\n<FlutterCreateSnippet />\n\nЗатем можем заменить содержимое pubspec.yaml на\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nи затем установить все наши зависимости\n\n<FlutterPubGetSnippet />\n\n## Структура проекта\n\n```\n├── lib\n|   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n|   |   |   └── post_event.dart\n|   |   |   └── post_state.dart\n|   |   └── models\n|   |   |   └── models.dart*\n|   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n|   |   |   └── view.dart*\n|   |   └── widgets\n|   |   |   └── bottom_loader.dart\n|   |   |   └── post_list_item.dart\n|   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nПриложение использует структуру каталогов, ориентированную на функции. Эта\nструктура проекта позволяет нам масштабировать проект, имея автономные функции.\nВ этом примере у нас будет только одна функция (функция post), и она разделена\nна соответствующие папки с barrel файлами, обозначенными звездочкой (\\*).\n\n## REST API\n\nДля этого демонстрационного приложения мы будем использовать\n[jsonplaceholder](http://jsonplaceholder.typicode.com) в качестве нашего\nисточника данных.\n\n:::note\n\njsonplaceholder - это онлайн REST API, который предоставляет фальшивые данные;\nон очень полезен для создания прототипов.\n\n:::\n\nОткройте новую вкладку в браузере и посетите\nhttps://jsonplaceholder.typicode.com/posts?_start=0&_limit=2, чтобы увидеть, что\nвозвращает API.\n\n<PostsJsonSnippet />\n\n:::note\n\nВ нашем url мы указали start и limit в качестве параметров запроса для GET\nзапроса.\n\n:::\n\nОтлично, теперь, когда мы знаем, как будут выглядеть наши данные, создадим\nмодель.\n\n## Модель данных\n\nСоздайте `post.dart` и приступим к работе над созданием модели нашего объекта\nPost.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post` - это просто класс с `id`, `title` и `body`.\n\n:::note\n\nМы расширяем [`Equatable`](https://pub.dev/packages/equatable), чтобы мы могли\nсравнивать `Posts`. Без этого нам пришлось бы вручную изменить наш класс, чтобы\nпереопределить equality и hashCode, чтобы мы могли определить разницу между\nдвумя объектами `Posts`. См. [пакет](https://pub.dev/packages/equatable) для\nболее подробной информации.\n\n:::\n\nТеперь, когда у нас есть наша модель объекта `Post`, начнем работать над\nкомпонентом бизнес-логики (bloc).\n\n## Post Events\n\nПрежде чем погрузиться в реализацию, нам нужно определить, что будет делать наш\n`PostBloc`.\n\nНа высоком уровне он будет реагировать на пользовательский ввод (прокрутку) и\nизвлекать больше постов, чтобы слой представления мог их отобразить. Начнем с\nсоздания нашего `Event`.\n\nНаш `PostBloc` будет реагировать только на одно событие; `PostFetched`, которое\nбудет добавлено слоем представления всякий раз, когда ему нужно больше Posts для\nпредставления. Поскольку наше событие `PostFetched` является типом `PostEvent`,\nмы можем создать `bloc/post_event.dart` и реализовать событие следующим образом.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\nПодводя итог, наш `PostBloc` будет получать `PostEvents` и преобразовывать их в\n`PostStates`. Мы определили все наши `PostEvents` (PostFetched), поэтому далее\nопределим наш `PostState`.\n\n## Post States\n\nНашему слою представления понадобится несколько частей информации, чтобы\nправильно расположить себя:\n\n- `PostInitial`- сообщит слою представления, что ему нужно отобразить индикатор\n  загрузки пока загружается начальная партия постов\n- `PostSuccess`- сообщит слою представления, что у него есть контент для\n  отображения\n  - `posts`- будет `List<Post>`, который будет отображаться\n  - `hasReachedMax`- сообщит слою представления, достигнуто ли максимальное\n    количество постов\n- `PostFailure`- сообщит слою представления, что произошла ошибка при извлечении\n  постов\n\nТеперь мы можем создать `bloc/post_state.dart` и реализовать его следующим\nобразом.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\nМы реализовали `copyWith`, чтобы мы могли скопировать экземпляр `PostSuccess` и\nобновить ноль или более свойств удобным образом (это пригодится позже).\n\n:::\n\nТеперь, когда у нас реализованы наши `Events` и `States`, мы можем создать наш\n`PostBloc`.\n\n## Post Bloc\n\nДля простоты наш `PostBloc` будет иметь прямую зависимость от `http client`;\nоднако в производственном приложении мы предлагаем вместо этого внедрить api\nclient и использовать паттерн repository [docs](/ru/architecture).\n\nСоздадим `post_bloc.dart` и создадим наш пустой `PostBloc`.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\nПросто из объявления класса мы можем сказать, что наш PostBloc будет принимать\nPostEvents в качестве входных данных и выводить PostStates.\n\n:::\n\nДалее, нам нужно зарегистрировать обработчик событий для обработки входящих\nсобытий `PostFetched`. В ответ на событие `PostFetched` мы вызовем `_fetchPosts`\nдля извлечения постов из API.\n\n<PostBlocOnPostFetchedSnippet />\n\nНаш `PostBloc` будет `emit` новые состояния через `Emitter<PostState>`,\nпредоставленный в обработчике событий. Смотрите\n[основные концепции](/ru/bloc-concepts/#потоки-streams) для более подробной\nинформации.\n\nТеперь каждый раз, когда добавляется `PostEvent`, если это событие `PostFetched`\nи есть больше постов для извлечения, наш `PostBloc` извлечет следующие 20\nпостов.\n\nAPI вернет пустой массив, если мы попытаемся извлечь за пределами максимального\nколичества постов (100), поэтому если мы получим пустой массив, наш bloc `emit`\ncurrentState, за исключением того, что мы установим `hasReachedMax` в true.\n\nЕсли мы не можем извлечь посты, мы выдаем `PostStatus.failure`.\n\nЕсли мы можем извлечь посты, мы выдаем `PostStatus.success` и весь список\nпостов.\n\nОдна оптимизация, которую мы можем сделать, это `throttle` событие\n`PostFetched`, чтобы предотвратить ненужную рассылку спама нашему API. Мы можем\nсделать это, используя параметр `transform`, когда регистрируем обработчик\nсобытия `_onFetched`.\n\n:::note\n\nПередача `transformer` в `on<PostFetched>` позволяет нам настроить, как\nобрабатываются события.\n\n:::\n\n:::note\n\nУбедитесь, что импортировали\n[`package:stream_transform`](https://pub.dev/packages/stream_transform), чтобы\nиспользовать api `throttle`.\n\n:::\n\n<PostBlocTransformerSnippet />\n\nНаш законченный `PostBloc` теперь должен выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\nОтлично! Теперь, когда мы закончили реализацию бизнес-логики, все, что осталось\nсделать, это реализовать слой представления.\n\n## Слой представления\n\nВ нашем `main.dart` мы можем начать с реализации нашей главной функции и вызова\n`runApp` для отображения нашего корневого виджета. Здесь мы также можем включить\nнаш bloc observer для логирования переходов и любых ошибок.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nВ нашем виджете `App`, корне нашего проекта, мы можем затем установить home в\n`PostsPage`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nВ нашем виджете `PostsPage` мы используем `BlocProvider` для создания и\nпредоставления экземпляра `PostBloc` поддереву. Также мы добавляем событие\n`PostFetched`, чтобы когда приложение загружается, оно запрашивало начальную\nпартию Posts.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\nДалее, нам нужно реализовать наше представление `PostsList`, которое будет\nпредставлять наши посты и подключиться к нашему `PostBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList` - это `StatefulWidget`, потому что ему нужно поддерживать\n`ScrollController`. В `initState` мы добавляем слушателя к нашему\n`ScrollController`, чтобы мы могли реагировать на события прокрутки. Мы также\nполучаем доступ к нашему экземпляру `PostBloc` через `context.read<PostBloc>()`.\n\n:::\n\nДвигаясь дальше, наш метод build возвращает `BlocBuilder`. `BlocBuilder` - это\nвиджет Flutter из пакета [flutter_bloc](https://pub.dev/packages/flutter_bloc),\nкоторый обрабатывает построение виджета в ответ на новые состояния bloc. Каждый\nраз, когда изменяется состояние нашего `PostBloc`, наша функция builder будет\nвызвана с новым `PostState`.\n\n:::caution\n\nНам нужно не забыть очистить за собой и удалить наш `ScrollController`, когда\nStatefulWidget удаляется.\n\n:::\n\nВсякий раз, когда пользователь прокручивает, мы вычисляем, как далеко вы\nпрокрутили вниз по странице, и если наше расстояние ≥ 90% нашего\n`maxScrollextent`, мы добавляем событие `PostFetched`, чтобы загрузить больше\nпостов.\n\nДалее, нам нужно реализовать наш виджет `BottomLoader`, который будет указывать\nпользователю, что мы загружаем больше постов.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\nНаконец, нам нужно реализовать наш `PostListItem`, который будет отображать\nотдельный `Post`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\nНа этом этапе мы должны быть в состоянии запустить наше приложение, и все должно\nработать; однако, есть еще одна вещь, которую мы можем сделать.\n\nОдин дополнительный бонус использования библиотеки bloc заключается в том, что\nмы можем иметь доступ ко всем `Transitions` в одном месте.\n\nИзменение от одного состояния к другому называется `Transition`.\n\n:::note\n\n`Transition` состоит из текущего состояния, события и следующего состояния.\n\n:::\n\nДаже несмотря на то, что в этом приложении у нас только один bloc, довольно\nчасто в больших приложениях иметь много blocs, управляющих различными частями\nсостояния приложения.\n\nЕсли мы хотим иметь возможность что-то делать в ответ на все `Transitions`, мы\nможем просто создать свой собственный `BlocObserver`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\nВсе, что нам нужно сделать, это расширить `BlocObserver` и переопределить метод\n`onTransition`.\n\n:::\n\nТеперь каждый раз, когда происходит `Transition` Bloc, мы можем видеть переход,\nнапечатанный в консоли.\n\n:::note\n\nНа практике вы можете создавать различные `BlocObservers`, и поскольку каждое\nизменение состояния записывается, мы можем очень легко инструментировать наши\nприложения и отслеживать все взаимодействия пользователей и изменения состояния\nв одном месте!\n\n:::\n\nВот и все! Мы теперь успешно реализовали бесконечный список во flutter,\nиспользуя пакеты [bloc](https://pub.dev/packages/bloc) и\n[flutter_bloc](https://pub.dev/packages/flutter_bloc), и мы успешно отделили наш\nслой представления от нашей бизнес-логики.\n\nНаш `PostsPage` не знает, откуда берутся `Posts` или как они извлекаются. И\nнаоборот, наш `PostBloc` не знает, как `State` отображается, он просто\nпреобразует события в состояния.\n\nПолный исходный код этого примера можно найти\n[здесь](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-login.mdx",
    "content": "---\ntitle: Вход Flutter\ndescription:\n  Подробное руководство по созданию потока входа Flutter с использованием bloc.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nВ следующем руководстве мы собираемся создать поток входа в Flutter используя\nбиблиотеку Bloc.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## Ключевые темы\n\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), виджет Flutter который\n  предоставляет bloc своим дочерним элементам.\n- Добавление событий с помощью\n  [context.read](/ru/flutter-bloc-concepts#contextread).\n- Предотвращение ненужных перестроек с помощью\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- [RepositoryProvider](/ru/flutter-bloc-concepts#repositoryprovider), виджет\n  Flutter который предоставляет repository своим дочерним элементам.\n- [BlocListener](/ru/flutter-bloc-concepts#bloclistener), виджет Flutter который\n  вызывает код слушателя в ответ на изменения состояния в bloc.\n- Обновление UI на основе части состояния bloc с помощью\n  [context.select](/ru/flutter-bloc-concepts#contextselect).\n\n## Настройка проекта\n\nНачнем с создания нового Flutter проекта\n\n<FlutterCreateSnippet />\n\nДалее, мы можем установить все наши зависимости\n\n<FlutterPubGetSnippet />\n\n## Authentication Repository\n\nПервое, что мы собираемся сделать, это создать пакет\n`authentication_repository`, который будет отвечать за управление доменом\nаутентификации.\n\nНачнем с создания каталога `packages/authentication_repository` в корне проекта,\nкоторый будет содержать все внутренние пакеты.\n\nНа высоком уровне структура каталогов должна выглядеть так:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\nДалее, мы можем создать `pubspec.yaml` для пакета `authentication_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\n`package:authentication_repository` будет чистым Dart пакетом без каких-либо\nвнешних зависимостей.\n\n:::\n\nДалее, нам нужно реализовать сам класс `AuthenticationRepository`, который будет\nнаходиться в\n`packages/authentication_repository/lib/src/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository` предоставляет `Stream` обновлений\n`AuthenticationStatus`, которые будут использоваться для уведомления приложения,\nкогда пользователь входит или выходит.\n\nКроме того, есть методы `logIn` и `logOut`, которые заглушены для простоты, но\nмогут быть легко расширены для аутентификации с помощью `FirebaseAuth`,\nнапример, или какого-либо другого провайдера аутентификации.\n\n:::note\n\nПоскольку мы поддерживаем `StreamController` внутренне, метод `dispose`\nпредоставляется, чтобы контроллер мог быть закрыт, когда он больше не нужен.\n\n:::\n\nНаконец, нам нужно создать\n`packages/authentication_repository/lib/authentication_repository.dart`, который\nбудет содержать публичные экспорты:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\nВот и все для `AuthenticationRepository`, далее мы будем работать над\n`UserRepository`.\n\n## User Repository\n\nТак же, как и с `AuthenticationRepository`, мы создадим пакет `user_repository`\nвнутри каталога `packages`.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\nДалее, мы создадим `pubspec.yaml` для `user_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\n`user_repository` будет отвечать за домен пользователя и будет предоставлять API\nдля взаимодействия с текущим пользователем.\n\nПервое, что мы определим, это модель пользователя в\n`packages/user_repository/lib/src/models/user.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\nДля простоты у пользователя есть только свойство `id`, но на практике у нас\nмогут быть дополнительные свойства, такие как `firstName`, `lastName`,\n`avatarUrl` и т.д...\n\n:::note\n\n[`package:equatable`](https://pub.dev/packages/equatable) используется для\nвключения сравнения значений объекта `User`.\n\n:::\n\nДалее, мы можем создать `models.dart` в\n`packages/user_repository/lib/src/models`, который будет экспортировать все\nмодели, чтобы мы могли использовать один оператор импорта для импорта нескольких\nмоделей.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\nТеперь, когда модели определены, мы можем реализовать класс `UserRepository` в\n`packages/user_repository/lib/src/user_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\nДля этого простого примера `UserRepository` предоставляет один метод `getUser`,\nкоторый будет извлекать текущего пользователя. Мы заглушаем это, но на практике\nэто где мы запрашивали бы текущего пользователя с backend.\n\nПочти закончили с пакетом `user_repository` -- осталось только создать файл\n`user_repository.dart` в `packages/user_repository/lib`, который определяет\nпубличные экспорты:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\nТеперь, когда у нас есть пакеты `authentication_repository` и `user_repository`,\nмы можем сосредоточиться на приложении Flutter.\n\n## Установка зависимостей\n\nНачнем с обновления сгенерированного `pubspec.yaml` в корне нашего проекта:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nМы можем установить зависимости, запустив:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\n`AuthenticationBloc` будет отвечать за реагирование на изменения в состоянии\nаутентификации (предоставляемом `AuthenticationRepository`) и будет выдавать\nсостояния, на которые мы можем реагировать в слое представления.\n\nРеализация `AuthenticationBloc` находится внутри `lib/authentication`, потому\nчто мы рассматриваем аутентификацию как функцию в нашем слое приложения.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\nИспользуйте\n[VSCode расширение](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nили [IntelliJ плагин](https://plugins.jetbrains.com/plugin/12129-bloc) для\nавтоматического создания blocs.\n\n:::\n\n### authentication_event.dart\n\nЭкземпляры `AuthenticationEvent` будут входными данными для `AuthenticationBloc`\nи будут обрабатываться и использоваться для выдачи новых экземпляров\n`AuthenticationState`.\n\nВ этом приложении `AuthenticationBloc` будет реагировать на два различных\nсобытия:\n\n- `AuthenticationSubscriptionRequested`: начальное событие, которое уведомляет\n  bloc о подписке на поток `AuthenticationStatus`\n- `AuthenticationLogoutPressed`: уведомляет bloc о действии выхода пользователя\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\nДалее, рассмотрим `AuthenticationState`.\n\n### authentication_state.dart\n\nЭкземпляры `AuthenticationState` будут выходными данными `AuthenticationBloc` и\nбудут использоваться слоем представления.\n\nКласс `AuthenticationState` имеет три именованных конструктора:\n\n- `AuthenticationState.unknown()`: состояние по умолчанию, которое указывает,\n  что bloc еще не знает, аутентифицирован ли текущий пользователь.\n\n- `AuthenticationState.authenticated()`: состояние, которое указывает, что\n  пользователь в настоящее время аутентифицирован.\n\n- `AuthenticationState.unauthenticated()`: состояние, которое указывает, что\n  пользователь в настоящее время не аутентифицирован.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\nТеперь, когда мы видели реализации `AuthenticationEvent` и\n`AuthenticationState`, давайте рассмотрим `AuthenticationBloc`.\n\n### authentication_bloc.dart\n\n`AuthenticationBloc` управляет состоянием аутентификации приложения, которое\nиспользуется для определения таких вещей, как запускать ли пользователя на\nстранице входа или домашней странице.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\n`AuthenticationBloc` имеет зависимость как от `AuthenticationRepository`, так и\nот `UserRepository` и определяет начальное состояние как\n`AuthenticationState.unknown()`.\n\nВ теле конструктора подклассы `AuthenticationEvent` сопоставляются с их\nсоответствующими обработчиками событий.\n\nВ обработчике события `_onSubscriptionRequested` `AuthenticationBloc` использует\n`emit.onEach` для подписки на поток `status` из `AuthenticationRepository` и\nвыдает состояние в ответ на каждый `AuthenticationStatus`.\n\n`emit.onEach` создает подписку на поток внутренне и заботится о ее отмене, когда\nлибо `AuthenticationBloc`, либо поток `status` закрыт.\n\nЕсли поток `status` выдает ошибку, `addError` пересылает ошибку и stackTrace\nлюбому слушающему `BlocObserver`.\n\n:::caution\n\nЕсли `onError` опущен, любые ошибки в потоке `status` считаются необработанными\nи будут выброшены `onEach`. В результате подписка на поток `status` будет\nотменена.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/ru/bloc-concepts/#blocobserver-1) отлично подходит для\nлогирования событий Bloc, ошибок и изменений состояния, особенно в контексте\nаналитики и отчетов о сбоях.\n\n:::\n\nКогда поток `status` выдает `AuthenticationStatus.unknown` или\n`unauthenticated`, выдается соответствующий `AuthenticationState`.\n\nКогда выдается `AuthenticationStatus.authenticated`, `AuthentictionBloc`\nзапрашивает пользователя через `UserRepository`.\n\n## main.dart\n\nДалее, мы можем заменить `main.dart` по умолчанию на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## App\n\nТак же, как в [руководстве по входу](/ru/tutorials/flutter-login), наш\n`app.dart` будет предоставлять экземпляр `AuthenticationRepository` приложению\nчерез `RepositoryProvider` и также создавать и предоставлять экземпляр\n`AuthenticationBloc`. Затем `AppView` использует `AuthenticationBloc` и\nобрабатывает обновление текущего маршрута на основе `AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`app.dart` разделен на две части: `App` и `AppView`. `App` отвечает за\nсоздание/предоставление `AuthenticationBloc`, который будет использоваться\n`AppView`. Это разделение позволит нам легко протестировать как виджеты `App`,\nтак и `AppView` позже.\n\n:::\n\n:::note\n\n`RepositoryProvider` используется для предоставления единственного экземпляра\n`AuthenticationRepository` всему приложению, что пригодится позже.\n\n:::\n\nПо умолчанию `BlocProvider` ленив и не вызывает `create`, пока первый раз не\nбудет осуществлен доступ к Bloc. Поскольку `AuthenticationBloc` всегда должен\nподписываться на поток `AuthenticationStatus` немедленно (через событие\n`AuthenticationSubscriptionRequested`), мы можем явно отказаться от этого\nповедения, установив `lazy: false`.\n\n`AppView` - это `StatefulWidget`, потому что он поддерживает `GlobalKey`,\nкоторый используется для доступа к `NavigatorState`. По умолчанию `AppView`\nбудет отображать `SplashPage` (который мы увидим позже), и он использует\n`BlocListener` для навигации к различным страницам на основе изменений в\n`AuthenticationState`.\n\n## Splash\n\nФункция splash будет содержать только простое представление, которое будет\nотображаться сразу когда приложение запускается, пока приложение определяет,\nаутентифицирован ли пользователь.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage` предоставляет статический `Route`, что делает очень легкой\nнавигацию к нему через `Navigator.of(context).push(SplashPage.route())`;\n\n:::\n\n## Login\n\nФункция login содержит `LoginPage`, `LoginForm` и `LoginBloc` и позволяет\nпользователям вводить имя пользователя и пароль для входа в приложение.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### Login Models\n\nМы используем [`package:formz`](https://pub.dev/packages/formz) для создания\nповторно используемых и стандартных моделей для `username` и `password`.\n\n#### Username\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\nДля простоты мы просто проверяем, что имя пользователя не пустое, но на практике\nвы можете применять использование специальных символов, длину и т.д...\n\n#### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\nОпять же, мы просто выполняем простую проверку, чтобы убедиться, что пароль не\nпустой.\n\n#### Models Barrel\n\nТак же, как и раньше, есть файл barrel `models.dart` для облегчения импорта\nмоделей `Username` и `Password` одним импортом.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\n`LoginBloc` управляет состоянием `LoginForm` и заботится о валидации имени\nпользователя и пароля, а также о состоянии формы.\n\n#### login_event.dart\n\nВ этом приложении есть три различных типа `LoginEvent`:\n\n- `LoginUsernameChanged`: уведомляет bloc, что имя пользователя было изменено.\n- `LoginPasswordChanged`: уведомляет bloc, что пароль был изменен.\n- `LoginSubmitted`: уведомляет bloc, что форма была отправлена.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\n`LoginState` будет состоять из статуса формы, а также из состояний входных\nданных имени пользователя и пароля.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\nМодели `Username` и `Password` используются как часть `LoginState`, и статус\nтакже является частью [package:formz](https://pub.dev/packages/formz).\n\n:::\n\n#### login_bloc.dart\n\n`LoginBloc` отвечает за реагирование на взаимодействия пользователя в\n`LoginForm` и обработку валидации и отправки формы.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\n`LoginBloc` имеет зависимость от `AuthenticationRepository`, потому что когда\nформа отправлена, он вызывает `logIn`. Начальное состояние bloc `pure`, что\nозначает, что ни входные данные, ни форма не были затронуты или не\nвзаимодействовали.\n\nВсякий раз, когда изменяется `username` или `password`, bloc создает \"грязный\"\nвариант модели `Username`/`Password` и обновляет статус формы через API\n`Formz.validate`.\n\nКогда добавляется событие `LoginSubmitted`, если текущий статус формы valid,\nbloc делает вызов `logIn` и обновляет статус на основе результата запроса.\n\nДалее рассмотрим `LoginPage` и `LoginForm`.\n\n### Login Page\n\n`LoginPage` отвечает за предоставление `Route`, а также за создание и\nпредоставление `LoginBloc` в `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\n`context.read<AuthenticationRepository>()` используется для поиска экземпляра\n`AuthenticationRepository` через `BuildContext`.\n\n:::\n\n### Login Form\n\n`LoginForm` обрабатывает уведомление `LoginBloc` о событиях пользователя, а\nтакже реагирует на изменения состояния, используя `BlocBuilder` и\n`BlocListener`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`BlocListener` используется для показа `SnackBar`, если отправка входа не\nудалась. В дополнение, `context.select` используется для эффективного доступа к\nконкретным частям `LoginState` для каждого виджета, предотвращая ненужные\nперестроения. Обратный вызов `onChanged` используется для уведомления\n`LoginBloc` об изменениях имени пользователя/пароля.\n\nВиджет `_LoginButton` включен только если статус формы valid, и\n`CircularProgressIndicator` показывается на его месте во время отправки формы.\n\n## Home\n\nПосле успешного запроса `logIn` состояние `AuthenticationBloc` изменится на\n`authenticated`, и пользователь будет перенаправлен на `HomePage`, где мы\nотображаем `id` пользователя, а также кнопку выхода.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### Home Page\n\n`HomePage` может получить доступ к текущему id пользователя через\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` и отображает\nего через виджет `Text`. Кроме того, когда нажата кнопка выхода, событие\n`AuthenticationLogoutPressed` добавляется в `AuthenticationBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` будет вызывать\nобновления, если id пользователя изменяется.\n\n:::\n\nНа этом этапе у нас есть довольно надежная реализация входа, и мы разделили наш\nслой представления от слоя бизнес-логики, используя Bloc.\n\nПолный исходный код этого примера (включая unit и widget тесты) можно найти\n[здесь](https://github.com/felangel/Bloc/tree/master/examples/flutter_login).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: Таймер Flutter\ndescription:\n  Подробное руководство по созданию приложения таймера Flutter с использованием\n  bloc.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nВ следующем руководстве мы рассмотрим, как создать приложение таймера используя\nбиблиотеку bloc. Готовое приложение должно выглядеть так:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## Ключевые темы\n\n- Наблюдение за изменениями состояния с помощью\n  [BlocObserver](/ru/bloc-concepts#blocobserver).\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), виджет Flutter который\n  предоставляет bloc своим дочерним элементам.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), виджет Flutter который\n  обрабатывает построение виджета в ответ на новые состояния.\n- Предотвращение ненужных перестроек с помощью\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- Изучение использования `StreamSubscription` в Bloc.\n- Предотвращение ненужных перестроек с помощью `buildWhen`.\n\n## Настройка\n\nНачнем с создания нового Flutter проекта:\n\n<FlutterCreateSnippet />\n\nЗатем можем заменить содержимое pubspec.yaml на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nМы будем использовать пакеты\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) и\n[equatable](https://pub.dev/packages/equatable) в этом приложении.\n\n:::\n\nДалее, запустите `flutter pub get` для установки всех зависимостей.\n\n## Структура проекта\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## Ticker\n\nTicker будет нашим источником данных для приложения таймера. Он предоставит\nпоток тиков, на который мы можем подписаться и реагировать.\n\nНачните с создания `ticker.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\nВесь наш класс `Ticker` предоставляет функцию tick, которая принимает количество\nтиков (секунд), которое мы хотим, и возвращает поток, который выдает оставшиеся\nсекунды каждую секунду.\n\nДалее, нам нужно создать наш `TimerBloc`, который будет использовать `Ticker`.\n\n## Timer Bloc\n\n### TimerState\n\nНачнем с определения `TimerStates`, в которых может находиться наш `TimerBloc`.\n\nНаше состояние `TimerBloc` может быть одним из следующих:\n\n- `TimerInitial`: готов начать обратный отсчет с указанной продолжительности.\n- `TimerRunInProgress`: активно ведет обратный отсчет от указанной\n  продолжительности.\n- `TimerRunPause`: приостановлен на некоторой оставшейся продолжительности.\n- `TimerRunComplete`: завершен с оставшейся продолжительностью 0.\n\nКаждое из этих состояний будет иметь влияние на пользовательский интерфейс и\nдействия, которые пользователь может выполнять. Например:\n\n- если состояние `TimerInitial`, пользователь сможет запустить таймер.\n- если состояние `TimerRunInProgress`, пользователь сможет приостановить и\n  сбросить таймер, а также увидеть оставшуюся продолжительность.\n- если состояние `TimerRunPause`, пользователь сможет возобновить таймер и\n  сбросить таймер.\n- если состояние `TimerRunComplete`, пользователь сможет сбросить таймер.\n\nЧтобы держать все наши файлы bloc вместе, создадим каталог bloc с\n`bloc/timer_state.dart`.\n\n:::tip\n\nВы можете использовать\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) или\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nрасширения для автоматической генерации следующих файлов bloc.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\nОбратите внимание, что все `TimerStates` расширяют абстрактный базовый класс\n`TimerState`, который имеет свойство duration. Это потому, что независимо от\nтого, в каком состоянии находится наш `TimerBloc`, мы хотим знать, сколько\nвремени осталось. Кроме того, `TimerState` расширяет `Equatable` для оптимизации\nнашего кода, гарантируя, что наше приложение не вызывает перестроение, если\nпроисходит то же самое состояние.\n\nДалее, определим и реализуем `TimerEvents`, которые будет обрабатывать наш\n`TimerBloc`.\n\n### TimerEvent\n\nНаш `TimerBloc` должен знать, как обрабатывать следующие события:\n\n- `TimerStarted`: информирует TimerBloc, что таймер должен быть запущен.\n- `TimerPaused`: информирует TimerBloc, что таймер должен быть приостановлен.\n- `TimerResumed`: информирует TimerBloc, что таймер должен быть возобновлен.\n- `TimerReset`: информирует TimerBloc, что таймер должен быть сброшен к\n  исходному состоянию.\n- `_TimerTicked`: информирует TimerBloc, что произошел тик и что он должен\n  обновить свое состояние соответственно.\n\nЕсли вы не использовали\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) или\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nрасширения, то создайте `bloc/timer_event.dart` и реализуем эти события.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\nДалее, реализуем `TimerBloc`!\n\n### TimerBloc\n\nЕсли вы еще не сделали это, создайте `bloc/timer_bloc.dart` и создайте пустой\n`TimerBloc`.\n\n<TimerBlocEmptySnippet />\n\nПервое, что нам нужно сделать, это определить начальное состояние нашего\n`TimerBloc`. В этом случае мы хотим, чтобы `TimerBloc` начинался в состоянии\n`TimerInitial` с предустановленной продолжительностью 1 минута (60 секунд).\n\n<TimerBlocInitialStateSnippet />\n\nДалее, нам нужно определить зависимость от нашего `Ticker`.\n\n<TimerBlocTickerSnippet />\n\nМы также определяем `StreamSubscription` для нашего `Ticker`, к которому мы\nвернемся чуть позже.\n\nНа этом этапе все, что осталось сделать, это реализовать обработчики событий.\nДля улучшения читаемости, мне нравится разбивать каждый обработчик событий на\nсвою собственную вспомогательную функцию. Начнем с события `TimerStarted`.\n\n<TimerBlocOnStartedSnippet />\n\nЕсли `TimerBloc` получает событие `TimerStarted`, он отправляет состояние\n`TimerRunInProgress` с начальной продолжительностью. Кроме того, если уже был\nоткрыт `_tickerSubscription`, нам нужно отменить его для освобождения памяти.\nНам также нужно переопределить метод `close` в нашем `TimerBloc`, чтобы мы могли\nотменить `_tickerSubscription`, когда `TimerBloc` закрыт. Наконец, мы слушаем\nпоток `_ticker.tick` и на каждый тик добавляем событие `_TimerTicked` с\nоставшейся продолжительностью.\n\nДалее, реализуем обработчик события `_TimerTicked`.\n\n<TimerBlocOnTickedSnippet />\n\nКаждый раз, когда получено событие `_TimerTicked`, если продолжительность тика\nбольше 0, нам нужно отправить обновленное состояние `TimerRunInProgress` с новой\nпродолжительностью. В противном случае, если продолжительность тика равна 0, наш\nтаймер завершился, и нам нужно отправить состояние `TimerRunComplete`.\n\nТеперь реализуем обработчик события `TimerPaused`.\n\n<TimerBlocOnPausedSnippet />\n\nВ `_onPaused`, если `state` нашего `TimerBloc` является `TimerRunInProgress`, то\nмы можем приостановить `_tickerSubscription` и отправить состояние\n`TimerRunPause` с текущей продолжительностью таймера.\n\nДалее, реализуем обработчик события `TimerResumed`, чтобы мы могли снять паузу с\nтаймера.\n\n<TimerBlocOnResumedSnippet />\n\nОбработчик события `TimerResumed` очень похож на обработчик события\n`TimerPaused`. Если `TimerBloc` имеет `state` равный `TimerRunPause` и получает\nсобытие `TimerResumed`, то он возобновляет `_tickerSubscription` и отправляет\nсостояние `TimerRunInProgress` с текущей продолжительностью.\n\nНаконец, нам нужно реализовать обработчик события `TimerReset`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\nЕсли `TimerBloc` получает событие `TimerReset`, ему нужно отменить текущий\n`_tickerSubscription`, чтобы он не получал уведомления о дополнительных тиках и\nотправляет состояние `TimerInitial` с исходной продолжительностью.\n\nЭто все, что касается `TimerBloc`. Теперь все, что осталось, это реализовать UI\nдля нашего приложения таймера.\n\n## Пользовательский интерфейс приложения\n\n### MyApp\n\nМожем начать с удаления содержимого `main.dart` и замены его на следующее.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nДалее, создадим наш виджет 'App' в `app.dart`, который будет корнем нашего\nприложения.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nДалее, нам нужно реализовать наш виджет `Timer`.\n\n### Timer\n\nНаш виджет `Timer` (`lib/timer/view/timer_page.dart`) будет отвечать за\nотображение оставшегося времени вместе с соответствующими кнопками, которые\nпозволят пользователям запускать, приостанавливать и сбрасывать таймер.\n\n<TimerPageSnippet />\n\nПока мы просто используем `BlocProvider` для доступа к экземпляру нашего\n`TimerBloc`.\n\nДалее, мы реализуем наш виджет `Actions`, который будет иметь соответствующие\nдействия (запуск, пауза и сброс).\n\n### Barrel\n\nЧтобы очистить наши импорты из раздела `Timer`, нам нужно создать файл barrel\n`timer/timer.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### Actions\n\n<ActionsSnippet />\n\nВиджет `Actions` это просто еще один `StatelessWidget`, который использует\n`BlocBuilder` для перестроения UI каждый раз, когда мы получаем новый\n`TimerState`. `Actions` использует `context.read<TimerBloc>()` для доступа к\nэкземпляру `TimerBloc` и возвращает различные `FloatingActionButtons` в\nзависимости от текущего состояния `TimerBloc`. Каждая из `FloatingActionButtons`\nдобавляет событие в свой обратный вызов `onPressed` для уведомления `TimerBloc`.\n\nЕсли вы хотите более точный контроль над тем, когда вызывается функция\n`builder`, вы можете предоставить необязательный `buildWhen` в `BlocBuilder`.\n`buildWhen` принимает предыдущее состояние bloc и текущее состояние bloc и\nвозвращает `boolean`. Если `buildWhen` возвращает `true`, `builder` будет вызван\nс `state` и виджет будет перестроен. Если `buildWhen` возвращает `false`,\n`builder` не будет вызван с `state` и перестроение не произойдет.\n\nВ этом случае мы не хотим, чтобы виджет `Actions` перестраивался на каждом тике,\nпотому что это было бы неэффективно. Вместо этого мы хотим, чтобы `Actions`\nперестраивался только если `runtimeType` `TimerState` изменяется (TimerInitial\n=> TimerRunInProgress, TimerRunInProgress => TimerRunPause, и т.д...).\n\nВ результате, если бы мы случайно окрашивали виджеты при каждом перестроении,\nэто выглядело бы так:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\nНесмотря на то, что виджет `Text` перестраивается на каждом тике, мы\nперестраиваем только `Actions`, если они должны быть перестроены.\n\n:::\n\n### Background\n\nНаконец, добавьте виджет background следующим образом:\n\n<BackgroundSnippet />\n\n### Собираем все вместе\n\nВот и все! На этом этапе у нас есть довольно надежное приложение таймера,\nкоторое эффективно перестраивает только те виджеты, которые необходимо\nперестроить.\n\nПолный исходный код этого примера можно найти\n[здесь](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Задачи Flutter\ndescription:\n  Подробное руководство по созданию приложения задач Flutter с использованием\n  bloc.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nВ следующем руководстве мы собираемся создать приложение задач во Flutter,\nиспользуя библиотеку Bloc.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## Ключевые темы\n\n- [Bloc and Cubit](/ru/bloc-concepts/#cubit-против-bloc) to manage the various\n  feature states.\n- [Layered Architecture](/ru/architecture) for separation of concerns and to\n  facilitate reusability.\n- [BlocObserver](/ru/bloc-concepts#blocobserver) to observe state changes.\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), a Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), a Flutter widget that\n  handles building the widget in response to new states.\n- [BlocListener](/ru/flutter-bloc-concepts#bloclistener), a Flutter widget that\n  handles performing side effects in response to state changes.\n- [RepositoryProvider](/ru/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget to provide a repository to its children.\n- [Equatable](/ru/faqs/#когда-использовать-equatable) to prevent unnecessary\n  rebuilds.\n- [MultiBlocListener](/ru/flutter-bloc-concepts#multibloclistener), a Flutter\n  widget that reduces nesting when using multiple BlocListeners.\n\n## Настройка\n\nНачнем с создания нового Flutter проекта, используя\n[very_good_cli](https://pub.dev/packages/very_good_cli).\n\n<FlutterCreateSnippet />\n\n:::note\n\nInstall `very_good_cli` using the following command\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\nNext we'll create the `todos_api`, `local_storage_todos_api`, and\n`todos_repository` packages using `very_good_cli`:\n\n<FlutterCreatePackagesSnippet />\n\nWe can then replace the contents of `pubspec.yaml` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nFinally, we can install all the dependencies:\n\n<VeryGoodPackagesGetSnippet />\n\n## Структура проекта\n\nOur application project structure should look like:\n\n<ProjectStructureSnippet />\n\nWe split the project into multiple packages in order to maintain explicit\ndependencies for each package with clear boundaries that enforce the\n[single responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle).\nModularizing our project like this has many benefits including but not limited\nto:\n\n- easy to reuse packages across multiple projects\n- CI/CD improvements in terms of efficiency (run checks on only the code that\n  has changed)\n- easy to maintain the packages in isolation with their dedicated test suites,\n  semantic versioning, and release cycle/cadence\n\n## Архитектура\n\n![Todos Architecture Diagram](~/assets/tutorials/todos-architecture.png)\n\nLayering our code is incredibly important and helps us iterate quickly and with\nconfidence. Each layer has a single responsibility and can be used and tested in\nisolation. This allows us to keep changes contained to a specific layer in order\nto minimize the impact on the entire application. In addition, layering our\napplication allows us to easily reuse libraries across multiple projects\n(especially with respect to the data layer).\n\nOur application consists of three main layers:\n\n- data layer\n- domain layer\n- feature layer\n  - presentation/UI (widgets)\n  - business logic (blocs/cubits)\n\n**Data Layer**\n\nThis layer is the lowest layer and is responsible for retrieving raw data from\nexternal sources such as a databases, APIs, and more. Packages in the data layer\ngenerally should not depend on any UI and can be reused and even published on\n[pub.dev](https://pub.dev) as a standalone package. In this example, our data\nlayer consists of the `todos_api` and `local_storage_todos_api` packages.\n\n**Domain Layer**\n\nThis layer combines one or more data providers and applies \"business rules\" to\nthe data. Each component in this layer is called a repository and each\nrepository generally manages a single domain. Packages in the repository layer\nshould generally only interact with the data layer. In this example, our\nrepository layer consists of the `todos_repository` package.\n\n**Feature Layer**\n\nThis layer contains all of the application-specific features and use cases. Each\nfeature generally consists of some UI and business logic. Features should\ngenerally be independent of other features so that they can easily be\nadded/removed without impacting the rest of the codebase. Within each feature,\nthe state of the feature along with any business logic is managed by blocs.\nBlocs interact with zero or more repositories. Blocs react to events and emit\nstates which trigger changes in the UI. Widgets within each feature should\ngenerally only depend on the corresponding bloc and render UI based on the\ncurrent state. The UI can notify the bloc of user input via events. In this\nexample, our application will consist of the `home`, `todos_overview`, `stats`,\nand `edit_todos` features.\n\nNow that we've gone over the layers at a high level, let's start building our\napplication starting with the data layer!\n\n## Слой данных\n\nThe data layer is the lowest layer in our application and consists of raw data\nproviders. Packages in this layer are primarily concerned with where/how data is\ncoming from. In this case our data layer will consist of the `TodosApi`, which\nis an interface, and the `LocalStorageTodosApi`, which is an implementation of\nthe `TodosApi` backed by `shared_preferences`.\n\n### TodosApi\n\nThe `todos_api` package will export a generic interface for interacting/managing\ntodos. Later we'll implement the `TodosApi` using `shared_preferences`. Having\nan abstraction will make it easy to support other implementations without having\nto change any other part of our application. For example, we can later add a\n`FirestoreTodosApi`, which uses `cloud_firestore` instead of\n`shared_preferences`, with minimal code changes to the rest of the application.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### Todo model\n\nNext we'll define our `Todo` model.\n\nThe first thing of note is that the `Todo` model doesn't live in our app — it's\npart of the `todos_api` package. This is because the `TodosApi` defines APIs\nthat return/accept `Todo` objects. The model is a Dart representation of the raw\nTodo object that will be stored/retrieved.\n\nThe `Todo` model uses\n[json_serializable](https://pub.dev/packages/json_serializable) to handle the\njson (de)serialization. If you are following along, you will have to run the\n[code generation step](https://pub.dev/packages/json_serializable#running-the-code-generator)\nto resolve the compiler errors.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\n`json_map.dart` provides a `typedef` for code checking and linting.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\nThe model of the `Todo` is defined in `todos_api/models/todo.dart` and is\nexported by `package:todos_api/todos_api.dart`.\n\n#### Update Exports\n\nOur `Todo` model and the `TodosApi` are exported via barrel files. Notice how we\ndon't import the model directly, but we import it in `lib/src/todos_api.dart`\nwith a reference to the package barrel file:\n`import 'package:todos_api/todos_api.dart';`. Update the barrel files to resolve\nany remaining import errors:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Streams vs Futures\n\nIn a previous version of this tutorial, the `TodosApi` was `Future`-based rather\nthan `Stream`-based.\n\nFor an example of a `Future`-based API see\n[Brian Egan's implementation in his Architecture Samples](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core).\n\nA `Future`-based implementation could consist of two methods: `loadTodos` and\n`saveTodos` (note the plural). This means, a full list of todos must be provided\nto the method each time.\n\n- One limitation of this approach is that the standard CRUD (Create, Read,\n  Update, and Delete) operation requires sending the full list of todos with\n  each call. For example, on an Add Todo screen, one cannot just send the added\n  todo item. Instead, we must keep track of the entire list and provide the\n  entire new list of todos when persisting the updated list.\n- A second limitation is that `loadTodos` is a one-time delivery of data. The\n  app must contain logic to ask for updates periodically.\n\nIn the current implementation, the `TodosApi` exposes a `Stream<List<Todo>>` via\n`getTodos()` which will report real-time updates to all subscribers when the\nlist of todos has changed.\n\nIn addition, todos can be created, deleted, or updated individually. For\nexample, both deleting and saving a todo are done with only the `todo` as the\nargument. It's not necessary to provide the newly updated list of todos each\ntime.\n\n### LocalStorageTodosApi\n\nThis package implements the `todos_api` using the\n[`shared_preferences`](https://pub.dev/packages/shared_preferences) package.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## Слой Repository\n\nA [repository](/ru/architecture/#репозиторий) is part of the business layer. A\nrepository depends on one or more data providers that have no business value,\nand combines their public API into APIs that provide business value. In\naddition, having a repository layer helps abstract data acquisition from the\nrest of the application, allowing us to change where/how data is being stored\nwithout affecting other parts of the app.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\nInstantiating the repository requires specifying a `TodosApi`, which we\ndiscussed earlier in this tutorial, so we added it as a dependency in our\n`pubspec.yaml`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### Library Exports\n\nIn addition to exporting the `TodosRepository` class, we also export the `Todo`\nmodel from the `todos_api` package. This step prevents tight coupling between\nthe application and the data providers.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\nWe decided to re-export the same `Todo` model from the `todos_api`, rather than\nredefining a separate model in the `todos_repository`, because in this case we\nare in complete control of the data model. In many cases, the data provider will\nnot be something that you can control. In those cases, it becomes increasingly\nimportant to maintain your own model definitions in the repository layer to\nmaintain full control of the interface and API contract.\n\n## Слой функций\n\n### Точка входа\n\nOur app's entrypoint is `main.dart`. In this case, there are three versions:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\nThe most notable thing is the concrete implementation of the\n`local_storage_todos_api` is instantiated within each entrypoint.\n\n### Начальная загрузка\n\n`bootstrap.dart` loads our `BlocObserver` and creates the instance of\n`TodosRepository`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### App\n\n`App` wraps a `RepositoryProvider` widget that provides the repository to all\nchildren. Since both the `EditTodoPage` and `HomePage` subtrees are descendents,\nall the blocs and cubits can access the repository.\n\n`AppView` creates the `MaterialApp` and configures the theme and localizations.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### Тема\n\nThis provides theme definition for light and dark mode.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### Главная\n\nThe home feature is responsible for managing the state of the currently-selected\ntab and displays the correct subtree.\n\n#### ГлавнаяState\n\nThere are only two states associated with the two screens: `todos` and `stats`.\n\n:::note\n\n`EditTodo` is a separate route therefore it isn't part of the `HomeState`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### ГлавнаяCubit\n\nA cubit is appropriate in this case due to the simplicity of the business logic.\nWe have one method `setTab` to change the tab.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### ГлавнаяView\n\n`view.dart` is a barrel file that exports all relevant UI components for the\nhome feature.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\n`home_page.dart` contains the UI for the root page that the user will see when\nthe app is launched.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `HomePage` is:\n\n<HomePageTreeSnippet />\n\nThe `HomePage` provides an instance of `HomeCubit` to `HomeView`. `HomeView`\nuses `context.select` to selectively rebuild whenever the tab changes. This\nallows us to easily widget test `HomeView` by providing a mock `HomeCubit` and\nstubbing the state.\n\nThe `BottomAppBar` contains `HomeTabButton` widgets which call `setTab` on the\n`HomeCubit`. The instance of the cubit is looked up via `context.read` and the\nappropriate method is invoked on the cubit instance.\n\n:::caution\n\n`context.read` doesn't listen for changes, it is just used to access to\n`HomeCubit` and call `setTab`.\n\n:::\n\n### TodosOverview\n\nThe todos overview feature allows users to manage their todos by creating,\nediting, deleting, and filtering todos.\n\n#### TodosOverviewEvent\n\nLet's create `todos_overview/bloc/todos_overview_event.dart` and define the\nevents.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: This is the startup event. In response,\n  the bloc subscribes to the stream of todos from the `TodosRepository`.\n- `TodosOverviewTodoDeleted`: This deletes a Todo.\n- `TodosOverviewTodoCompletionToggled`: This toggles a todo's completed status.\n- `TodosOverviewToggleAllRequested`: This toggles completion for all todos.\n- `TodosOverviewClearCompletedRequested`: This deletes all completed todos.\n- `TodosOverviewUndoDeletionRequested`: This undoes a todo deletion, e.g. an\n  accidental deletion.\n- `TodosOverviewFilterChanged`: This takes a `TodosViewFilter` as an argument\n  and changes the view by applying a filter.\n\n#### TodosOverviewState\n\nLet's create `todos_overview/bloc/todos_overview_state.dart` and define the\nstate.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState` will keep track of a list of todos, the active filter, the\n`lastDeletedTodo`, and the status.\n\n:::note\n\nIn addition to the default getters and setters, we have a custom getter called\n`filteredTodos`. The UI uses `BlocBuilder` to access either\n`state.filteredTodos` or `state.todos`.\n\n:::\n\n#### TodosOverviewBloc\n\nLet's create `todos_overview/bloc/todos_overview_bloc.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nThe bloc does not create an instance of the `TodosRepository` internally.\nInstead, it relies on an instance of the repository to be injected via\nconstructor.\n\n:::\n\n##### onSubscriptionRequested\n\nWhen `TodosOverviewSubscriptionRequested` is added, the bloc starts by emitting\na `loading` state. In response, the UI can then render a loading indicator.\n\nNext, we use `emit.forEach<List<Todo>>( ... )` which creates a subscription on\nthe todos stream from the `TodosRepository`.\n\n:::caution\n\n`emit.forEach()` is not the same `forEach()` used by lists. This `forEach`\nenables the bloc to subscribe to a `Stream` and emit a new state for each update\nfrom the stream.\n\n:::\n\n:::note\n\n`stream.listen` is never called directly in this tutorial. Using\n`await emit.forEach()` is a newer pattern for subscribing to a stream which\nallows the bloc to manage the subscription internally.\n\n:::\n\nNow that the subscription is handled, we will handle the other events, like\nadding, modifying, and deleting todos.\n\n##### onTodoSaved\n\n`_onTodoSaved` simply calls `_todosRepository.saveTodo(event.todo)`.\n\n:::note\n\n`emit` is never called from within `onTodoSaved` and many other event handlers.\nInstead, they notify the repository which emits an updated list via the todos\nstream. See the [data flow](#поток-данных) section for more information.\n\n:::\n\n##### Undo\n\nThe undo feature allows users to restore the last deleted item.\n\n`_onTodoDeleted` does two things. First, it emits a new state with the `Todo` to\nbe deleted. Then, it deletes the `Todo` via a call to the repository.\n\n`_onUndoDeletionRequested` runs when the undo deletion request event comes from\nthe UI.\n\n`_onUndoDeletionRequested` does the following:\n\n- Temporarily saves a copy of the last deleted todo.\n- Updates the state by removing the `lastDeletedTodo`.\n- Reverts the deletion.\n\n##### Filtering\n\n`_onFilterChanged` emits a new state with the new event filter.\n\n#### Models\n\nThere is one model file that deals with the view filtering.\n\n`todos_view_filter.dart` is an enum that represents the three view filters and\nthe methods to apply the filter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart` is the barrel file for exports.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\nNext, let's take a look at the `TodosOverviewPage`.\n\n#### TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `TodosOverviewPage` is:\n\n<TodosOverviewPageTreeSnippet />\n\nJust as with the `Home` feature, the `TodosOverviewPage` provides an instance of\nthe `TodosOverviewBloc` to the subtree via `BlocProvider<TodosOverviewBloc>`.\nThis scopes the `TodosOverviewBloc` to just the widgets below\n`TodosOverviewPage`.\n\nThere are three widgets that are listening for changes in the\n`TodosOverviewBloc`.\n\n1. The first is a `BlocListener` that listens for errors. The `listener` will\n   only be called when `listenWhen` returns `true`. If the status is\n   `TodosOverviewStatus.failure`, a `SnackBar` is displayed.\n\n2. We created a second `BlocListener` that listens for deletions. When a todo\n   has been deleted, a `SnackBar` is displayed with an undo button. If the user\n   taps undo, the `TodosOverviewUndoDeletionRequested` event will be added to\n   the bloc.\n\n3. Finally, we use a `BlocBuilder` to builds the ListView that displays the\n   todos.\n\nThe `AppBar`contains two actions which are dropdowns for filtering and\nmanipulating the todos.\n\n:::note\n\n`TodosOverviewTodoCompletionToggled` and `TodosOverviewTodoDeleted` are added to\nthe bloc via `context.read`.\n\n:::\n\n`view.dart` is the barrel file that exports `todos_overview_page.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Виджеты\n\n`widgets.dart` is another barrel file that exports all the components used\nwithin the `todos_overview` feature.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart` is the `ListTile` for each todo item.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart` exposes two options for manipulating todos:\n\n- `toggleAll`\n- `clearCompleted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart` exposes three filter options:\n\n- `all`\n- `activeOnly`\n- `completedOnly`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### Статистика\n\nThe stats feature displays statistics about the active and completed todos.\n\n#### СтатистикаState\n\n`StatsState` keeps track of summary information and the current `StatsStatus`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### СтатистикаEvent\n\n`StatsEvent` has only one event called `StatsSubscriptionRequested`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### СтатистикаBloc\n\n`StatsBloc` depends on the `TodosRepository` just like `TodosOverviewBloc`. It\nsubscribes to the todos stream via `_todosRepository.getTodos`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### Статистика View\n\n`view.dart` is the barrel file for the `stats_page`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart` contains the UI for the page that displays the todos\nstatistics.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `StatsPage` is:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\nThe `TodosOverviewBloc` and `StatsBloc` both communicate with the\n`TodosRepository`, but it is important to note there is no direct communication\nbetween the blocs. See the [data flow](#поток-данных) section for more\ninformation.\n\n:::\n\n### EditTodo\n\nThe `EditTodo` feature allows users to edit an existing todo item and save the\nchanges.\n\n#### EditTodoState\n\n`EditTodoState` keeps track of the information needed when editing a todo.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### EditTodoEvent\n\nThe different events the bloc will react to are:\n\n- `EditTodoTitleChanged`\n- `EditTodoDescriptionChanged`\n- `EditTodoSubmitted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc` depends on the `TodosRepository`, just like `TodosOverviewBloc`\nand `StatsBloc`.\n\n:::caution\n\nUnlike the other Blocs, `EditTodoBloc` does not subscribe to\n`_todosRepository.getTodos`. It is a \"write-only\" bloc meaning it doesn't need\nto read any information from the repository.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### Поток данных\n\nEven though there are many features that depend on the same list of todos, there\nis no bloc-to-bloc communication. Instead, all features are independent of each\nother and rely on the `TodosRepository` to listen for changes in the list of\ntodos, as well as perform updates to the list.\n\nFor example, the `EditTodos` doesn't know anything about the `TodosOverview` or\n`Stats` features.\n\nWhen the UI submits a `EditTodoSubmitted` event:\n\n- `EditTodoBloc` handles the business logic to update the `TodosRepository`.\n- `TodosRepository` notifies `TodosOverviewBloc` and `StatsBloc`.\n- `TodosOverviewBloc` and `StatsBloc` notify the UI which update with the new\n  state.\n\n#### EditTodoPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\nJust like with the previous features, the `EditTodosPage` provides an instance\nof the `EditTodosBloc` via `BlocProvider`. Unlike the other features, the\n`EditTodosPage` is a separate route which is why it exposes a `static` `route`\nmethod. This makes it easy to push the `EditTodosPage` onto the navigation stack\nvia `Navigator.of(context).push(...)`.\n\nA simplified representation of the widget tree for the `EditTodosPage` is:\n\n<EditTodosPageTreeSnippet />\n\n## Резюме\n\nВот и все, мы завершили руководство! 🎉\n\nПолный исходный код этого примера, включая unit и widget тесты, можно найти\n[here](https://github.com/felangel/bloc/tree/master/examples/flutter_todos).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: Погода Flutter\ndescription:\n  Подробное руководство по созданию приложения погоды Flutter с использованием\n  bloc.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nВ этом руководстве мы собираемся создать приложение погоды на Flutter, которое\nдемонстрирует, как управлять несколькими cubits для реализации динамического\nоформления, обновления по pull-to-refresh и многого другого. Наше приложение\nпогоды будет получать актуальные данные о погоде из публичного API OpenMeteo и\nдемонстрировать, как разделить наше приложение на слои (данные, repository,\nбизнес-логика и представление).\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## Требования к проекту\n\nНаше приложение должно позволять пользователям\n\n- Искать город на выделенной странице поиска\n- Видеть приятное отображение данных о погоде, возвращаемых\n  [Open Meteo API](https://open-meteo.com)\n- Изменять отображаемые единицы (метрические против имперских)\n\nДополнительно,\n\n- Тема приложения должна отражать погоду для выбранного города\n- Состояние приложения должно сохраняться между сеансами: т.е. приложение должно\n  запоминать свое состояние после закрытия и повторного открытия (используя\n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc))\n\n## Ключевые концепции\n\n- Наблюдение за изменениями состояния с помощью\n  [BlocObserver](/ru/bloc-concepts#blocobserver).\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), виджет Flutter который\n  предоставляет bloc своим дочерним элементам.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), виджет Flutter который\n  обрабатывает построение виджета в ответ на новые состояния.\n- Предотвращение ненужных перестроек с помощью\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- [RepositoryProvider](/ru/flutter-bloc-concepts#repositoryprovider), виджет\n  Flutter который предоставляет repository своим дочерним элементам.\n- [BlocListener](/ru/flutter-bloc-concepts#bloclistener), виджет Flutter который\n  вызывает код слушателя в ответ на изменения состояния в bloc.\n- [MultiBlocProvider](/ru/flutter-bloc-concepts#multiblocprovider), виджет\n  Flutter, который объединяет несколько виджетов BlocProvider в один\n- [BlocConsumer](/ru/flutter-bloc-concepts#blocconsumer), виджет Flutter,\n  который предоставляет builder и listener для реагирования на новые состояния\n- [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  для управления и сохранения состояния\n\n## Настройка\n\nДля начала создадим новый flutter проект\n\n<FlutterCreateSnippet />\n\n### Структура проекта\n\nНаше приложение будет состоять из изолированных функций в соответствующих\nкаталогах. Это позволяет нам масштабироваться по мере увеличения количества\nфункций и позволяет разработчикам работать над различными функциями параллельно.\n\nНаше приложение можно разбить на четыре основные функции: **search, settings,\ntheme, weather**. Создадим эти каталоги.\n\n<FeatureTreeSnippet />\n\n### Архитектура\n\nСледуя руководствам [архитектуры bloc](/ru/architecture), наше приложение будет\nсостоять из нескольких слоев.\n\nВ этом руководстве вот что будут делать эти слои:\n\n- **Data**: извлечение необработанных данных о погоде из API\n- **Repository**: абстрагирование слоя данных и предоставление доменных моделей\n  для потребления приложением\n- **Business Logic**: управление состоянием каждой функции (информация о\n  единицах, детали города, темы и т.д.)\n- **Presentation**: отображение информации о погоде и сбор ввода от\n  пользователей (страница настроек, страница поиска и т.д.)\n\n## Слой данных\n\nДля этого приложения мы будем обращаться к\n[Open Meteo API](https://open-meteo.com).\n\nМы сосредоточимся на двух конечных точках:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` для\n  получения местоположения для данного названия города\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true`\n  для получения погоды для данного местоположения\n\nОткройте\n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)\nв своем браузере, чтобы увидеть ответ для города Чикаго. Мы будем использовать\n`latitude` и `longitude` в ответе для обращения к конечной точке погоды.\n\n`latitude`/`longitude` для Чикаго равны `41.85003`/`-87.65005`. Перейдите к\n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)\nв своем браузере, и вы увидите ответ для погоды в Чикаго, который содержит все\nданные, которые нам понадобятся для нашего приложения.\n\n### OpenMeteo API Client\n\nOpenMeteo API Client не зависит от нашего приложения. В результате мы создадим\nего как внутренний пакет (и даже можем опубликовать его на\n[pub.dev](https://pub.dev)). Затем мы можем использовать пакет, добавив его в\n`pubspec.yaml` для слоя repository, который будет обрабатывать запросы данных\nдля нашего основного приложения погоды.\n\nСоздайте новый каталог на уровне проекта под названием `packages`. Этот каталог\nбудет хранить все наши внутренние пакеты.\n\nВ этом каталоге запустите встроенную команду `flutter create` для создания\nнового пакета под названием `open_meteo_api` для нашего API client.\n\n<FlutterCreateApiClientSnippet />\n\n### Weather Data Model\n\nДалее, создадим `location.dart` и `weather.dart`, которые будут содержать модели\nдля ответов конечных точек API `location` и `weather`.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### Location Model\n\nМодель `location.dart` должна хранить данные, возвращаемые API местоположения,\nкоторые выглядят следующим образом:\n\n<LocationJsonSnippet />\n\nВот незавершенный файл `location.dart`, который хранит вышеуказанный ответ:\n\n<LocationDartSnippet />\n\n#### Weather Model\n\nДалее, поработаем над `weather.dart`. Наша модель погоды должна хранить данные,\nвозвращаемые API погоды, которые выглядят следующим образом:\n\n<WeatherJsonSnippet />\n\nВот незавершенный файл `weather.dart`, который хранит вышеуказанный ответ:\n\n<WeatherDartSnippet />\n\n### Barrel Files\n\nПока мы здесь, быстро создадим\n[barrel файл](https://adrianfaciu.dev/posts/barrel-files/) для очистки некоторых\nиз наших импортов в дальнейшем.\n\nСоздайте barrel файл `models.dart` и экспортируйте две модели:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\nТакже создадим barrel файл на уровне пакета, `open_meteo_api.dart`\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\nВ верхнем уровне, `open_meteo_api.dart` экспортируем модели:\n\n<OpenMeteoLibrarySnippet />\n\n### Настройка\n\nНам нужно иметь возможность\n[сериализовать и десериализовать](https://en.wikipedia.org/wiki/Serialization)\nнаши модели, чтобы работать с данными API. Для этого мы добавим методы `toJson`\nи `fromJson` в наши модели.\n\nДополнительно, нам нужен способ\n[делать HTTP сетевые запросы](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)\nдля получения данных из API. К счастью, есть ряд популярных пакетов для этого.\n\nМы будем использовать пакеты\n[json_annotation](https://pub.dev/packages/json_annotation),\n[json_serializable](https://pub.dev/packages/json_serializable) и\n[build_runner](https://pub.dev/packages/build_runner) для генерации реализаций\n`toJson` и `fromJson` для нас.\n\nНа более позднем этапе мы также будем использовать пакет\n[http](https://pub.dev/packages/http) для отправки сетевых запросов к API\nпогоды, чтобы наше приложение могло отображать текущие данные о погоде.\n\nДобавим эти зависимости в `pubspec.yaml`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\nНе забудьте запустить `flutter pub get` после добавления зависимостей.\n\n:::\n\n### (Де)Сериализация\n\nЧтобы генерация кода работала, нам нужно аннотировать наш код, используя\nследующее:\n\n- `@JsonSerializable` для пометки классов, которые могут быть сериализованы\n- `@JsonKey` для предоставления строковых представлений имен полей\n- `@JsonValue` для предоставления строковых представлений значений полей\n- Реализовать `JSONConverter` для преобразования объектных представлений в JSON\n  представления\n\nДля каждого файла нам также нужно:\n\n- Импортировать `json_annotation`\n- Включить сгенерированный код, используя ключевое слово\n  [part](https://dart.dev/tools/pub/create-packages#organizing-a-package)\n- Включить методы `fromJson` для десериализации\n\n#### Location Model\n\nВот наш полный файл модели `location.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### Weather Model\n\nВот наш полный файл модели `weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### Create Build File\n\nВ папке `open_meteo_api` создайте файл `build.yaml`. Цель этого файла -\nобработка несоответствий между соглашениями именования в именах полей\n`json_serializable`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### Генерация кода\n\nИспользуем `build_runner` для генерации кода.\n\n<BuildRunnerBuildSnippet />\n\n`build_runner` должен сгенерировать файлы `location.g.dart` и `weather.g.dart`.\n\n### OpenMeteo API Client\n\nСоздадим наш API client в `open_meteo_api_client.dart` в каталоге `src`.\nСтруктура нашего проекта теперь должна выглядеть так:\n\n<OpenMeteoApiClientTreeSnippet />\n\nТеперь мы можем использовать пакет [http](https://pub.dev/packages/http),\nкоторый мы добавили ранее в файл `pubspec.yaml`, чтобы делать HTTP запросы к API\nпогоды и использовать эту информацию в нашем приложении.\n\nНаш API client будет предоставлять два метода:\n\n- `locationSearch`, который возвращает `Future<Location>`\n- `getWeather`, который возвращает `Future<Weather>`\n\n#### Location Search\n\nМетод `locationSearch` обращается к API местоположения и выбрасывает ошибки\n`LocationRequestFailure` по мере необходимости. Завершенный метод выглядит так:\n\n<LocationSearchMethodSnippet />\n\n#### Get Weather\n\nАналогично, метод `getWeather` обращается к API погоды и выбрасывает ошибки\n`WeatherRequestFailure` по мере необходимости. Завершенный метод выглядит так:\n\n<GetWeatherMethodSnippet />\n\nЗавершенный файл выглядит так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### Обновления Barrel File\n\nЗавершим этот пакет, добавив наш API client в barrel файл.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### Unit Tests\n\nОсобенно важно писать unit тесты для слоя данных, поскольку это основа нашего\nприложения. Unit тесты дадут нам уверенность, что пакет ведет себя, как\nожидается.\n\n#### Настройка\n\nРанее мы добавили пакет [test](https://pub.dev/packages/test) в наш\npubspec.yaml, который позволяет легко писать unit тесты.\n\nМы создадим тестовый файл для api client, а также для двух моделей.\n\n#### Location Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### Weather Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### API Client Tests\n\nДалее, протестируем наш API client. Мы должны убедиться, что наш API client\nобрабатывает оба вызова API правильно, включая граничные случаи.\n\n:::note\n\nМы не хотим, чтобы наши тесты делали реальные вызовы API, поскольку наша цель -\nпротестировать логику API client (включая все граничные случаи), а не сам API.\nЧтобы иметь согласованную, контролируемую тестовую среду, мы будем использовать\n[mocktail](https://github.com/felangel/mocktail) (который мы добавили в файл\npubspec.yaml ранее) для мока http client.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### Покрытие тестами\n\nНаконец, соберем покрытие тестами, чтобы убедиться, что мы покрыли каждую строку\nкода хотя бы одним тестовым случаем.\n\n<FlutterTestCoverageSnippet />\n\n## Слой Repository\n\nЦель нашего слоя repository - абстрагировать наш слой данных и облегчить\nкоммуникацию со слоем bloc. Делая это, остальная часть нашей кодовой базы\nзависит только от функций, предоставляемых нашим слоем repository, вместо\nконкретных реализаций провайдеров данных. Это позволяет нам изменять провайдеры\nданных без нарушения какого-либо кода на уровне приложения. Например, если мы\nрешим мигрировать от этого конкретного API погоды, мы должны быть в состоянии\nсоздать новый API client и заменить его без необходимости вносить изменения в\nпубличный API слоев repository или приложения.\n\n### Настройка\n\nВнутри каталога packages запустите следующую команду:\n\n<FlutterCreateRepositorySnippet />\n\nМы будем использовать те же пакеты, что и в пакете `open_meteo_api`, включая\nпакет `open_meteo_api` из последнего шага. Обновите свой `pubspec.yaml` и\nзапустите `flutter pub get`.\n\n:::note\n\nМы используем `path` для указания местоположения `open_meteo_api`, что позволяет\nнам рассматривать его так же, как внешний пакет из `pub.dev`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### Weather Repository Models\n\nМы создадим новый файл `weather.dart` для предоставления доменной модели погоды.\nЭта модель будет содержать только данные, относящиеся к нашим бизнес-случаям --\nдругими словами, она должна быть полностью отделена от API client и формата\nнеобработанных данных. Как обычно, мы также создадим barrel файл `models.dart`.\n\n<RepositoryModelsBarrelTreeSnippet />\n\nНа этот раз наша модель погоды будет хранить только свойства\n`location, temperature, condition`. Мы также продолжим аннотировать наш код,\nчтобы разрешить сериализацию и десериализацию.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\nОбновите barrel файл, который мы создали ранее, чтобы включить модели.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### Create Build File\n\nКак и раньше, нам нужно создать файл `build.yaml` со следующим содержимым:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### Генерация кода\n\nКак мы делали ранее, запустите следующую команду для генерации реализации\n(де)сериализации.\n\n<BuildRunnerBuildSnippet />\n\n#### Barrel File\n\nТакже создадим barrel файл на уровне пакета с именем\n`packages/weather_repository/lib/weather_repository.dart` для экспорта наших\nмоделей:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\nОсновная цель `WeatherRepository` - предоставить интерфейс, который абстрагирует\nпровайдер данных. В данном случае `WeatherRepository` будет иметь зависимость от\n`WeatherApiClient` и предоставлять один публичный метод,\n`getWeather(String city)`.\n\n:::note\n\nПотребители `WeatherRepository` не знают о детайлях реализации, таких как тот\nфакт, что два сетевых запроса выполняются к API погоды. Цель\n`WeatherRepository` - отделить \"что\" от \"как\" -- другими словами, мы хотим иметь\nспособ получить погоду для данного города, но не заботимся о том, как или откуда\nэти данные приходят.\n\n:::\n\n#### Настройка\n\nСоздадим файл `weather_repository.dart` в каталоге `src` нашего пакета и\nпоработаем над реализацией repository.\n\nОсновной метод, на котором мы сосредоточимся, это `getWeather(String city)`. Мы\nможем реализовать его, используя два вызова API client следующим образом:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### Barrel File\n\nОбновите barrel файл, который мы создали ранее.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### Unit Tests\n\nТак же, как и со слоем данных, критически важно тестировать слой repository,\nчтобы убедиться, что логика на уровне домена корректна. Для тестирования нашего\n`WeatherRepository` мы будем использовать библиотеку\n[mocktail](https://github.com/felangel/mocktail). Мы замокаем базовый api\nclient, чтобы протестировать логику `WeatherRepository` в изолированной,\nконтролируемой среде.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## Слой бизнес-логики\n\nВ слое бизнес-логики мы будем использовать доменную модель погоды из\n`WeatherRepository` и предоставлять модель на уровне функции, которая будет\nпредставлена пользователю через UI.\n\n:::note\n\nЭто третья различная модель погоды, которую мы реализуем. В API client наша\nмодель погоды содержала всю информацию, возвращаемую API. В слое repository наша\nмодель погоды содержала только абстрагированную модель на основе нашего\nбизнес-случая. На этом слое наша модель погоды будет содержать соответствующую\nинформацию, необходимую специально для текущего набора функций.\n\n:::\n\n### Настройка\n\nПоскольку наш слой бизнес-логики находится в нашем основном приложении, нам\nнужно отредактировать `pubspec.yaml` для всего проекта `flutter_weather` и\nвключить все пакеты, которые мы будем использовать.\n\n- Использование [equatable](https://pub.dev/packages/equatable) позволяет\n  экземплярам класса состояния нашего приложения сравниваться с помощью\n  оператора equals `==`. Под капотом bloc будет сравнивать наши состояния, чтобы\n  увидеть, равны ли они, и если они не равны, это вызовет перестроение. Это\n  гарантирует, что наше дерево виджетов будет перестраиваться только когда это\n  необходимо, чтобы поддерживать производительность быстрой и отзывчивой.\n- Мы можем приукрасить наш пользовательский интерфейс с помощью\n  [google_fonts](https://pub.dev/packages/google_fonts).\n- [HydratedBloc](https://pub.dev/packages/hydrated_bloc) позволяет нам сохранять\n  состояние приложения, когда приложение закрывается и снова открывается.\n- Мы включим пакет `weather_repository`, который мы только что создали, чтобы\n  позволить нам получать текущие данные о погоде!\n\nДля тестирования мы захотим включить обычный пакет `test`, вместе с `mocktail`\nдля мока зависимостей и [bloc_test](https://pub.dev/packages/bloc_test), чтобы\nобеспечить легкое тестирование единиц бизнес-логики или blocs!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nДалее, мы будем работать над слоем приложения внутри каталога функции `weather`.\n\n### Weather Model\n\nЦель нашей модели погоды - отслеживать данные о погоде, отображаемые нашим\nприложением, а также настройки температуры (Celsius или Fahrenheit).\n\nСоздайте `flutter_weather/lib/weather/models/weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### Create Build File\n\nСоздайте файл `build.yaml` для слоя бизнес-логики.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### Генерация кода\n\nЗапустите `build_runner` для генерации реализаций (де)сериализации.\n\n<BuildRunnerBuildSnippet />\n\n### Barrel File\n\nЭкспортируем наши модели из barrel файла\n(`flutter_weather/lib/weather/models/models.dart`):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\nЗатем создадим верхнеуровневый barrel файл weather\n(`flutter_weather/lib/weather/weather.dart`);\n\n<WeatherBarrelDartSnippet />\n\n### Weather\n\nМы будем использовать `HydratedCubit` чтобы позволить нашему приложению\nзапоминать его состояние приложения, даже после того, как оно закрыто и снова\nоткрыто.\n\n:::note\n\n`HydratedCubit` - это расширение `Cubit`, которое обрабатывает сохранение и\nвосстановление состояния между сеансами.\n\n:::\n\n#### Weather State\n\nИспользуя\n[Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nили [Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) расширение,\nщелкните правой кнопкой мыши на каталоге `weather` и создайте новый cubit с\nименем `Weather`. Структура проекта должна выглядеть так:\n\n<WeatherCubitTreeSnippet />\n\nЕсть четыре состояния, в которых может находиться наше приложение погоды:\n\n- `initial` до того, как что-либо загрузится\n- `loading` во время вызова API\n- `success` если вызов API успешен\n- `failure` если вызов API неуспешен\n\nПеречисление `WeatherStatus` будет представлять вышеуказанное.\n\nПолное состояние погоды должно выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\nТеперь, когда мы определили `WeatherState`, напишем `WeatherCubit`, который\nпредоставляет следующие методы:\n\n- `fetchWeather(String? city)` использует наш weather repository, чтобы\n  попытаться получить объект погоды для данного города\n- `refreshWeather()` извлекает новый объект погоды, используя weather repository\n  при данном текущем состоянии погоды\n- `toggleUnits()` переключает состояние между Celsius и Fahrenheit\n- `fromJson(Map<String, dynamic> json)`, `toJson(WeatherState state)`\n  используются для сохранения\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\nНе забудьте сгенерировать код (де)сериализации с помощью:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### Unit Tests\n\nПодобно слоям данных и repository, критически важно протестировать слой\nбизнес-логики, чтобы убедиться, что логика на уровне функции ведет себя так, как\nмы ожидаем. Мы будем полагаться на\n[bloc_test](https://pub.dev/packages/bloc_test) в дополнение к `mocktail` и\n`test`.\n\nДобавим пакеты `test`, `bloc_test` и `mocktail` в `dev_dependencies`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nПакет [bloc_test](https://pub.dev/packages/bloc_test) позволяет нам легко\nподготавливать наши blocs для тестирования, обрабатывать изменения состояния и\nпроверять результаты в согласованном порядке.\n\n:::\n\n#### Weather Cubit Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## Слой представления\n\n### Weather Page\n\nНачнем с `WeatherPage`, который использует `BlocProvider` для предоставления\nэкземпляра `WeatherCubit` дереву виджетов.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\nВы заметите, что страница зависит от виджетов `SettingsPage` и `SearchPage`,\nкоторые мы создадим далее.\n\n### SettingsPage\n\nСтраница настроек позволяет пользователям обновлять свои предпочтения для единиц\nтемпературы.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### SearchPage\n\nСтраница поиска позволяет пользователям ввести название желаемого города и\nпредоставляет результат поиска предыдущему маршруту через\n`Navigator.of(context).pop`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Weather Widgets\n\nПриложение будет отображать разные экраны в зависимости от четырех возможных\nсостояний `WeatherCubit`.\n\n#### WeatherEmpty\n\nЭтот экран будет показан, когда нет данных для отображения, потому что\nпользователь еще не выбрал город.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### WeatherError\n\nЭтот экран будет отображаться, если есть ошибка.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### WeatherLoading\n\nЭтот экран будет отображаться, пока приложение получает данные.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### WeatherPopulated\n\nЭтот экран будет отображаться после того, как пользователь выбрал город, и мы\nполучили данные.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### Barrel File\n\nДобавим эти состояния в barrel файл для очистки наших импортов.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### Entrypoint\n\nНаш `main.dart` файл должен инициализировать наш `WeatherApp` и `BlocObserver`\n(для целей отладки), а также настроить наш `HydratedStorage` для сохранения\nсостояния между сеансами.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nНаш виджет `app.dart` будет обрабатывать построение представления `WeatherPage`,\nкоторое мы ранее создали, и использовать `BlocProvider` для внедрения нашего\n`WeatherCubit`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### Widget Tests\n\nБиблиотека [`bloc_test`](https://pub.dev/packages/bloc_test) также предоставляет\n`MockBlocs` и `MockCubits`, которые упрощают тестирование UI. Мы можем мокать\nсостояния различных cubits и убедиться, что UI реагирует правильно.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\nМы используем `MockWeatherCubit` вместе с API `when` из `mocktail`, чтобы\nзаглушить состояние cubit в каждом из тестовых случаев. Это позволяет нам\nсимулировать все состояния и проверять, что UI ведет себя правильно при всех\nобстоятельствах.\n\n:::\n\n## Резюме\n\nВот и все, мы завершили руководство! 🎉\n\nМы можем запустить финальное приложение, используя команду `flutter run`.\n\nПолный исходный код этого примера, включая unit и widget тесты, можно найти\n[здесь](https://github.com/felangel/bloc/tree/master/examples/flutter_weather).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/github-search.mdx",
    "content": "---\ntitle: Поиск GitHub\ndescription:\n  Подробное руководство по созданию приложения поиска GitHub во Flutter и\n  AngularDart с использованием bloc.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nВ следующем руководстве мы собираемся создать приложение поиска GitHub во\nFlutter и AngularDart, чтобы продемонстрировать, как мы можем делиться данными и\nслоями бизнес-логики между двумя проектами.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## Ключевые темы\n\n- [BlocProvider](/ru/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/ru/flutter-bloc-concepts#blocbuilder), Flutter widget that\n  handles building the widget in response to new states.\n- Using Cubit instead of Bloc.\n  [What's the difference?](/ru/bloc-concepts/#cubit-против-bloc)\n- Prevent unnecessary rebuilds with\n  [Equatable](/ru/faqs/#когда-использовать-equatable).\n- Use a custom `EventTransformer` with\n  [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency).\n- Making network requests using the `http` package.\n\n## Общая библиотека поиска GitHub\n\nThe Common GitHub Search library will contain models, the data provider, the\nrepository, as well as the bloc that will be shared between AngularDart and\nFlutter.\n\n### Настройка\n\nWe'll start off by creating a new directory for our application.\n\n<SetupSnippet />\n\n:::note\n\nThe `common_github_search` directory will contain the shared library.\n\n:::\n\nWe need to create a `pubspec.yaml` with the required dependencies.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\nLastly, we need to install our dependencies.\n\n<DartPubGetSnippet />\n\nThat's it for the project setup! Now we can get to work on building out the\n`common_github_search` package.\n\n### Github Client\n\nThe `GithubClient` which will be providing raw data from the\n[GitHub API](https://developer.github.com/v3/).\n\n:::note\n\nYou can see a sample of what the data we get back will look like\n[here](https://api.github.com/search/repositories?q=dartlang).\n\n:::\n\nLet's create `github_client.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\nOur `GithubClient` is simply making a network request to Github's Repository\nSearch API and converting the result into either a `SearchResult` or\n`SearchResultError` as a `Future`.\n\n:::\n\n:::note\n\nThe `GithubClient` implementation depends on `SearchResult.fromJson`, which we\nhave not yet implemented.\n\n:::\n\nNext we need to define our `SearchResult` and `SearchResultError` models.\n\n#### Модель результата поиска\n\nCreate `search_result.dart`, which represents a list of `SearchResultItems`\nbased on the user's query:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\nThe `SearchResult` implementation depends on `SearchResultItem.fromJson`, which\nwe have not yet implemented.\n\n:::\n\n:::note\n\nWe aren't including properties that aren't going to be used in our model.\n\n:::\n\n#### Модель элемента результата поиска\n\nNext, we'll create `search_result_item.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\nAgain, the `SearchResultItem` implementation dependes on `GithubUser.fromJson`,\nwhich we have not yet implemented.\n\n:::\n\n#### Модель пользователя GitHub\n\nNext, we'll create `github_user.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\nAt this point, we have finished implementing `SearchResult` and its\ndependencies. Now we'll move onto `SearchResultError`.\n\n#### Модель ошибки результата поиска\n\nCreate `search_result_error.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\nOur `GithubClient` is finished so next we'll move onto the `GithubCache`, which\nwill be responsible for [memoizing](https://en.wikipedia.org/wiki/Memoization)\nas a performance optimization.\n\n### GitHub Cache\n\nOur `GithubCache` will be responsible for remembering all past queries so that\nwe can avoid making unnecessary network requests to the GitHub API. This will\nalso help improve our application's performance.\n\nCreate `github_cache.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\nNow we're ready to create our `GithubRepository`!\n\n### GitHub Repository\n\nThe Github Repository is responsible for creating an abstraction between the\ndata layer (`GithubClient`) and the Business Logic Layer (`Bloc`). This is also\nwhere we're going to put our `GithubCache` to use.\n\nCreate `github_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\nThe `GithubRepository` has a dependency on the `GithubCache` and the\n`GithubClient` and abstracts the underlying implementation. Our application\nnever has to know about how the data is being retrieved or where it's coming\nfrom since it shouldn't care. We can change how the repository works at any time\nand as long as we don't change the interface we shouldn't need to change any\nclient code.\n\n:::\n\nAt this point, we've completed the data provider layer and the repository layer\nso we're ready to move on to the business logic layer.\n\n### GitHub Search Event\n\nOur Bloc will be notified when a user has typed the name of a repository which\nwe will represent as a `TextChanged` `GithubSearchEvent`.\n\nCreate `github_search_event.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\nWe extend [`Equatable`](https://pub.dev/packages/equatable) so that we can\ncompare instances of `GithubSearchEvent`. By default, the equality operator\nreturns true if and only if this and other are the same instance.\n\n:::\n\n### Github Search State\n\nOur presentation layer will need to have several pieces of information in order\nto properly lay itself out:\n\n- `SearchStateEmpty`- will tell the presentation layer that no input has been\n  given by the user.\n\n- `SearchStateLoading`- will tell the presentation layer it has to display some\n  sort of loading indicator.\n\n- `SearchStateSuccess`- will tell the presentation layer that it has data to\n  present.\n\n  - `items`- will be the `List<SearchResultItem>` which will be displayed.\n\n- `SearchStateError`- will tell the presentation layer that an error has\n  occurred while fetching repositories.\n\n  - `error`- will be the exact error that occurred.\n\nWe can now create `github_search_state.dart` and implement it like so.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\nWe extend [`Equatable`](https://pub.dev/packages/equatable) so that we can\ncompare instances of `GithubSearchState`. By default, the equality operator\nreturns true if and only if this and other are the same instance.\n\n:::\n\nNow that we have our Events and States implemented, we can create our\n`GithubSearchBloc`.\n\n### GitHub Search Bloc\n\nCreate `github_search_bloc.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\nOur `GithubSearchBloc` converts `GithubSearchEvent` to `GithubSearchState` and\nhas a dependency on the `GithubRepository`.\n\n:::\n\n:::note\n\nWe create a custom `EventTransformer` to\n[debounce](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)\nthe `GithubSearchEvents`. One of the reasons why we created a `Bloc` instead of\na `Cubit` was to take advantage of stream transformers.\n\n:::\n\nAwesome! We're all done with our `common_github_search` package. The finished\nproduct should look like\n[this](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search).\n\nNext, we'll work on the Flutter implementation.\n\n## Flutter GitHub Search\n\nFlutter Github Search will be a Flutter application which reuses the models,\ndata providers, repositories, and blocs from `common_github_search` to implement\nGithub Search.\n\n### Настройка\n\nWe need to start by creating a new Flutter project in our `github_search`\ndirectory at the same level as `common_github_search`.\n\n<FlutterCreateSnippet />\n\nNext, we need to update our `pubspec.yaml` to include all the necessary\ndependencies.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\nWe are including our newly created `common_github_search` library as a\ndependency.\n\n:::\n\nNow, we need to install the dependencies.\n\n<FlutterPubGetSnippet />\n\nThat's it for project setup. Since the `common_github_search` package contains\nour data layer as well as our business logic layer, all we need to build is the\npresentation layer.\n\n### Форма поиска\n\nWe're going to need to create a form with a `_SearchBar` and `_SearchBody`\nwidget.\n\n- `_SearchBar` will be responsible for taking user input.\n- `_SearchBody` will be responsible for displaying search results, loading\n  indicators, and errors.\n\nLet's create `search_form.dart`.\n\nOur `SearchForm` will be a `StatelessWidget` which renders the `_SearchBar` and\n`_SearchBody` widgets.\n\n`_SearchBar` is also going to be a `StatefulWidget` because it will need to\nmaintain its own `TextEditingController` so that we can keep track of what a\nuser has entered as input.\n\n`_SearchBody` is a `StatelessWidget` which will be responsible for displaying\nsearch results, errors, and loading indicators. It will be the consumer of the\n`GithubSearchBloc`.\n\nIf our state is `SearchStateSuccess`, we render `_SearchResults` which we will\nimplement next.\n\n`_SearchResults` is a `StatelessWidget` which takes a `List<SearchResultItem>`\nand displays them as a list of `_SearchResultItems`.\n\n`_SearchResultItem` is a `StatelessWidget` and is responsible for rendering the\ninformation for a single search result. It is also responsible for handling user\ninteraction and navigating to the repository url on a user tap.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar` accesses `GitHubSearchBloc` via `context.read<GithubSearchBloc>()`\nand notifies the bloc of `TextChanged` events.\n\n:::\n\n:::note\n\n`_SearchBody` uses `BlocBuilder` in order to rebuild in response to state\nchanges. Since the bloc parameter of the `BlocBuilder` object was omitted,\n`BlocBuilder` will automatically perform a lookup using `BlocProvider` and the\ncurrent `BuildContext`. Read more [here.](/ru/flutter-bloc-concepts#blocbuilder)\n:::\n\n:::note\n\nWe use `ListView.builder` in order to construct a scrollable list of\n`_SearchResultItem`.\n\n:::\n\n:::note\n\nWe use the [url_launcher](https://pub.dev/packages/url_launcher) package to open\nexternal urls.\n\n:::\n\n### Собираем все вместе\n\nNow all that's left to do is implement our main app in `main.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\nOur `GithubRepository` is created in `main` and injected into our `App`. Our\n`SearchForm` is wrapped in a `BlocProvider` which is responsible for\ninitializing, closing, and making the instance of `GithubSearchBloc` available\nto the `SearchForm` widget and its children.\n\n:::\n\nThat's all there is to it! We've now successfully implemented a GitHub search\napp in Flutter using the [bloc](https://pub.dev/packages/bloc) and\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) packages and we've\nsuccessfully separated our presentation layer from our business logic.\n\nПолный исходный код можно найти\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search).\n\nFinally, we're going to build our AngularDart GitHub Search app.\n\n## AngularDart GitHub Search\n\nAngularDart GitHub Search will be an AngularDart application which reuses the\nmodels, data providers, repositories, and blocs from `common_github_search` to\nimplement Github Search.\n\n### Настройка\n\nWe need to start by creating a new AngularDart project in our github_search\ndirectory at the same level as `common_github_search`.\n\n<StagehandSnippet />\n\n:::note\n\nYou can install `stagehand` via:\n\n<ActivateStagehandSnippet />\n:::\n\nWe can then go ahead and replace the contents of `pubspec.yaml` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### Форма поиска\n\nJust like in our Flutter app, we're going to need to create a `SearchForm` with\na `SearchBar` and `SearchBody` component.\n\nOur `SearchForm` component will implement `OnInit` and `OnDestroy` because it\nwill need to create and close a `GithubSearchBloc`.\n\n- `SearchBar` will be responsible for taking user input.\n- `SearchBody` will be responsible for displaying search results, loading\n  indicators, and errors.\n\nLet's create `search_form_component.dart.`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\nThe `GithubRepository` is injected into the `SearchFormComponent`.\n\n:::\n\n:::note\n\nThe `GithubSearchBloc` is created and closed by the `SearchFormComponent`.\n\n:::\n\nOur template (`search_form_component.html`) will look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\nNext, we'll implement the `SearchBar` component.\n\n### Панель поиска\n\n`SearchBar` is a component which will be responsible for taking in user input\nand notifying the `GithubSearchBloc` of text changes.\n\nCreate `search_bar_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent` has a dependency on `GitHubSearchBloc` because it is\nresponsible for notifying the bloc of `TextChanged` events.\n\n:::\n\nNext, we can create `search_bar_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\nWe're done with `SearchBar`, now onto `SearchBody`.\n\n### Тело поиска\n\n`SearchBody` is a component which will be responsible for displaying search\nresults, errors, and loading indicators. It will be the consumer of the\n`GithubSearchBloc`.\n\nCreate `search_body_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent` has a dependency on `GithubSearchState` which is provided\nby the `GithubSearchBloc` using the `angular_bloc` bloc pipe.\n\n:::\n\nCreate `search_body_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\nIf our state `isSuccess`, we render `SearchResults`. We will implement it next.\n\n### Результаты поиска\n\n`SearchResults` is a component which takes a `List<SearchResultItem>` and\ndisplays them as a list of `SearchResultItems`.\n\nCreate `search_results_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\nNext up we'll create `search_results_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\nWe use `ngFor` in order to construct a list of `SearchResultItem` components.\n:::\n\nIt's time to implement `SearchResultItem`.\n\n### Элемент результата поиска\n\n`SearchResultItem` is a component that is responsible for rendering the\ninformation for a single search result. It is also responsible for handling user\ninteraction and navigating to the repository url on a user tap.\n\nCreate `search_result_item_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\nand the corresponding template in `search_result_item_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### Собираем все вместе\n\nWe have all of our components and now it's time to put them all together in our\n`app_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\nWe're creating the `GithubRepository` in the `AppComponent` and injecting it\ninto the `SearchForm` component.\n\n:::\n\nThat's all there is to it! We've now successfully implemented a GitHub search\napp in AngularDart using the `bloc` and `angular_bloc` packages and we've\nsuccessfully separated our presentation layer from our business logic.\n\nПолный исходный код можно найти\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search).\n\n## Резюме\n\nВ этом руководстве мы создали приложение Flutter и AngularDart, делясь всеми\nмоделями, провайдерами данных и blocs между ними.\n\nThe only thing we actually had to write twice was the presentation layer (UI)\nwhich is awesome in terms of efficiency and development speed. In addition, it's\nfairly common for web apps and mobile apps to have different user experiences\nand styles and this approach really demonstrates how easy it is to build two\napps that look totally different but share the same data and business logic\nlayers.\n\nПолный исходный код можно найти\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search).\n"
  },
  {
    "path": "docs/src/content/docs/ru/tutorials/ngdart-counter.mdx",
    "content": "---\ntitle: Счетчик AngularDart\ndescription:\n  Подробное руководство по созданию приложения-счетчика AngularDart с\n  использованием bloc.\nsidebar:\n  order: 8\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/ngdart-counter/StagehandSnippet.astro';\nimport InstallDependenciesSnippet from '~/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nВ следующем руководстве мы собираемся создать счетчик в AngularDart используя\nбиблиотеку Bloc.\n\n![demo](~/assets/tutorials/ngdart-counter.gif)\n\n## Настройка\n\nНачнем с создания нового проекта AngularDart с\n[stagehand](https://github.com/dart-lang/stagehand).\n\nЕсли у вас не установлен stagehand, активируйте его с помощью:\n\n<ActivateStagehandSnippet />\n\nЗатем сгенерируйте новый проект с помощью:\n\n<StagehandSnippet />\n\nЗатем можем заменить содержимое `pubspec.yaml` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nи затем установить все наши зависимости\n\n<InstallDependenciesSnippet />\n\nНаше приложение счетчика будет иметь только две кнопки для увеличения/уменьшения\nзначения счетчика и элемент для отображения текущего значения. Начнем\nпроектировать `CounterEvents`.\n\n## Counter Bloc\n\nПоскольку состояние нашего счетчика может быть представлено целым числом, нам не\nнужно создавать пользовательский класс, и мы можем разместить события и bloc\nвместе.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_bloc.dart\"\n\ttitle=\"lib/src/counter_page/counter_bloc.dart\"\n/>\n\n:::note\n\nПросто из объявления класса мы можем сказать, что наш `CounterBloc` будет\nпринимать `CounterEvents` в качестве входных данных и выводить целые числа.\n\n:::\n\n## Counter App\n\nТеперь, когда у нас есть полностью реализованный `CounterBloc`, мы можем начать\nсоздавать наш компонент AngularDart App.\n\nНаш `app.component.dart` должен выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.dart\"\n\ttitle=\"lib/app_component.dart\"\n/>\n\nи наш `app.component.html` должен выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.html\"\n\ttitle=\"lib/app_component.html\"\n/>\n\n## Counter Page\n\nНаконец, все, что осталось, это построить наш компонент Counter Page.\n\nНаш `counter_page_component.dart` должен выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.dart\"\n\ttitle=\"lib/src/counter_page/counter_page_component.dart\"\n/>\n\n:::note\n\nМы можем получить доступ к экземпляру `CounterBloc`, используя систему внедрения\nзависимостей AngularDart. Поскольку мы зарегистрировали его как `Provider`,\nAngularDart может правильно разрешить `CounterBloc`.\n\n:::\n\n:::note\n\nМы закрываем `CounterBloc` в `ngOnDestroy`.\n\n:::\n\n:::note\n\nМы импортируем `BlocPipe`, чтобы мы могли использовать его в нашем шаблоне.\n\n:::\n\nНаконец, наш `counter_page_component.html` должен выглядеть так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.html\"\n\ttitle=\"lib/src/counter_page/counter_page_component.html\"\n/>\n\n:::note\n\nМы используем `BlocPipe`, чтобы мы могли отображать состояние нашего\n`CounterBloc` по мере его обновления.\n\n:::\n\nВот и все! Мы отделили наш слой представления от нашего слоя бизнес-логики. Наш\n`CounterPageComponent` не знает, что происходит, когда пользователь нажимает\nкнопку; он просто добавляет событие, чтобы уведомить `CounterBloc`. Кроме того,\nнаш `CounterBloc` не знает, что происходит с состоянием (значением счетчика); он\nпросто преобразует `CounterEvents` в целые числа.\n\nМы можем запустить наше приложение с помощью `webdev serve` и можем просмотреть\nего локально.\n\nПолный исходный код этого примера можно найти\n[здесь](https://github.com/felangel/bloc/tree/master/examples/angular_counter).\n"
  },
  {
    "path": "docs/src/content/docs/ru/why-bloc.mdx",
    "content": "---\ntitle: Почему Bloc?\ndescription:\n  Обзор того, что делает Bloc надежным решением для управления состоянием.\nsidebar:\n  order: 1\n---\n\nBloc упрощает отделение представления от бизнес-логики, делая ваш код _быстрым_,\n_легко тестируемым_ и _переиспользуемым_.\n\nПри создании качественных приложений управление состоянием становится критически\nважным.\n\nКак разработчики мы хотим:\n\n- знать, в каком состоянии находится наше приложение в любой момент времени.\n- легко тестировать каждый случай, чтобы убедиться, что наше приложение\n  реагирует надлежащим образом.\n- записывать каждое взаимодействие пользователя в нашем приложении, чтобы мы\n  могли принимать решения на основе данных.\n- работать максимально эффективно и повторно использовать компоненты как внутри\n  нашего приложения, так и в других приложениях.\n- иметь возможность многим разработчикам без проблем работать в одной кодовой\n  базе, следуя одним и тем же шаблонам и соглашениям.\n- разрабатывать быстрые и отзывчивые приложения.\n\nBloc был разработан для удовлетворения всех этих потребностей и многих других.\n\nСуществует множество решений для управления состоянием, и выбор подходящего\nможет стать сложной задачей. Не существует одного идеального решения для\nуправления состоянием! Важно выбрать то, которое лучше всего подходит для вашей\nкоманды и вашего проекта.\n\nBloc был разработан с учетом трех основных ценностей:\n\n- **Простой:** Легко понять и может использоваться разработчиками с разным\n  уровнем навыков.\n- **Мощный:** Помогает создавать потрясающие, сложные приложения, составляя их\n  из более мелких компонентов.\n- **Тестируемый:** Легко тестировать каждый аспект приложения, чтобы мы могли\n  итерировать с уверенностью.\n\nВ целом, Bloc пытается сделать изменения состояния предсказуемыми, регулируя,\nкогда может произойти изменение состояния, и обеспечивая единый способ изменения\nсостояния во всём приложении.\n"
  },
  {
    "path": "docs/src/content/docs/testing.mdx",
    "content": "---\ntitle: Testing\ndescription: The basics of how to write tests for your blocs.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc was designed to be extremely easy to test. In this section, we'll walk\nthrough how to unit test a bloc.\n\nFor the sake of simplicity, let's write tests for the `CounterBloc` we created\nin [Core Concepts](/bloc-concepts).\n\nTo recap, the `CounterBloc` implementation looks like:\n\n<CounterBlocSnippet />\n\n## Setup\n\nBefore we start writing our tests we're going to need to add a testing framework\nto our dependencies.\n\nWe need to add [test](https://pub.dev/packages/test) and\n[bloc_test](https://pub.dev/packages/bloc_test) to our project.\n\n<AddDevDependenciesSnippet />\n\n## Testing\n\nLet's get started by creating the file for our `CounterBloc` Tests,\n`counter_bloc_test.dart` and importing the test package.\n\n<CounterBlocTestImportsSnippet />\n\nNext, we need to create our `main` as well as our test group.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nGroups are for organizing individual tests as well as for creating a context in\nwhich you can share a common `setUp` and `tearDown` across all of the individual\ntests.\n\n:::\n\nLet's start by creating an instance of our `CounterBloc` which will be used\nacross all of our tests.\n\n<CounterBlocTestSetupSnippet />\n\nNow we can start writing our individual tests.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nWe can run all of our tests with the `dart test` command.\n\n:::\n\nAt this point we should have our first passing test! Now let's write a more\ncomplex test using the [bloc_test](https://pub.dev/packages/bloc_test) package.\n\n<CounterBlocTestBlocTestSnippet />\n\nWe should be able to run the tests and see that all are passing.\n\nThat's all there is to it, testing should be a breeze and we should feel\nconfident when making changes and refactoring our code.\n\nYou can refer to the\n[Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nfor an example of a fully tested application.\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Flutter Counter\ndescription: An in-depth guide on how to build a Flutter counter app with bloc.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn the following tutorial, we're going to build a Counter in Flutter using the\nBloc library.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## Key Topics\n\n- Observe state changes with [BlocObserver](/bloc-concepts#blocobserver).\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles\n  building the widget in response to new states.\n- Using Cubit instead of Bloc.\n  [What's the difference?](/bloc-concepts#cubit-vs-bloc)\n- Adding events with [context.read](/flutter-bloc-concepts#contextread).\n\n## Setup\n\nWe'll start off by creating a brand new Flutter project\n\n<FlutterCreateSnippet />\n\nWe can then go ahead and replace the contents of `pubspec.yaml` with\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nand then install all of our dependencies\n\n<FlutterPubGetSnippet />\n\n## Project Structure\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nThe application uses a feature-driven directory structure. This project\nstructure enables us to scale the project by having self-contained features. In\nthis example we will only have a single feature (the counter itself) but in more\ncomplex applications we can have hundreds of different features.\n\n## BlocObserver\n\nThe first thing we're going to take a look at is how to create a `BlocObserver`\nwhich will help us observe all state changes in the application.\n\nLet's create `lib/counter_observer.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\nIn this case, we're only overriding `onChange` to see all state changes that\noccur.\n\n:::note\n\n`onChange` works the same way for both `Bloc` and `Cubit` instances.\n\n:::\n\n## main.dart\n\nNext, let's replace the contents of `lib/main.dart` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nWe're initializing the `CounterObserver` we just created and calling `runApp`\nwith the `CounterApp` widget which we'll look at next.\n\n## Counter App\n\nLet's create `lib/app.dart`:\n\n`CounterApp` will be a `MaterialApp` and is specifying the `home` as\n`CounterPage`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nWe are extending `MaterialApp` because `CounterApp` _is_ a `MaterialApp`. In\nmost cases, we're going to be creating `StatelessWidget` or `StatefulWidget`\ninstances and composing widgets in `build` but in this case there are no widgets\nto compose so it's simpler to just extend `MaterialApp`.\n\n:::\n\nLet's take a look at `CounterPage` next!\n\n## Counter Page\n\nLet's create `lib/counter/view/counter_page.dart`:\n\nThe `CounterPage` widget is responsible for creating a `CounterCubit` (which we\nwill look at next) and providing it to the `CounterView`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\nIt's important to separate or decouple the creation of a `Cubit` from the\nconsumption of a `Cubit` in order to have code that is much more testable and\nreusable.\n\n:::\n\n## Counter Cubit\n\nLet's create `lib/counter/cubit/counter_cubit.dart`:\n\nThe `CounterCubit` class will expose two methods:\n\n- `increment`: adds 1 to the current state\n- `decrement`: subtracts 1 from the current state\n\nThe type of state the `CounterCubit` is managing is just an `int` and the\ninitial state is `0`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\nUse the\n[VSCode Extension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nor [IntelliJ Plugin](https://plugins.jetbrains.com/plugin/12129-bloc) to create\nnew cubits automatically.\n\n:::\n\nNext, let's take a look at the `CounterView` which will be responsible for\nconsuming the state and interacting with the `CounterCubit`.\n\n## Counter View\n\nLet's create `lib/counter/view/counter_view.dart`:\n\nThe `CounterView` is responsible for rendering the current count and rendering\ntwo FloatingActionButtons to increment/decrement the counter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\nA `BlocBuilder` is used to wrap the `Text` widget in order to update the text\nany time the `CounterCubit` state changes. In addition,\n`context.read<CounterCubit>()` is used to look-up the closest `CounterCubit`\ninstance.\n\n:::note\n\nOnly the `Text` widget is wrapped in a `BlocBuilder` because that is the only\nwidget that needs to be rebuilt in response to state changes in the\n`CounterCubit`. Avoid unnecessarily wrapping widgets that don't need to be\nrebuilt when a state changes.\n\n:::\n\n## Barrel\n\nCreate `lib/counter/view/view.dart`:\n\nAdd `view.dart` to export all public facing parts of counter view.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\nLet's create `lib/counter/counter.dart`:\n\nAdd `counter.dart` to export all the public facing parts of the counter feature.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\nThat's it! We've separated the presentation layer from the business logic layer.\nThe `CounterView` has no idea what happens when a user presses a button; it just\nnotifies the `CounterCubit`. Furthermore, the `CounterCubit` has no idea what is\nhappening with the state (counter value); it's simply emitting new states in\nresponse to the methods being called.\n\nWe can run our app with `flutter run` and can view it on our device or\nsimulator/emulator.\n\nThe full source (including unit and widget tests) for this example can be found\n[here](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: Flutter Firebase Login\ndescription:\n  An in-depth guide on how to build a Flutter login flow with bloc and Firebase.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn the following tutorial, we're going to build a Firebase Login Flow in Flutter\nusing the Bloc library.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## Key Topics\n\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), a Flutter widget which\n  provides a bloc to its children.\n- Using Cubit instead of Bloc.\n  [What's the difference?](/bloc-concepts#cubit-vs-bloc)\n- Adding events with [context.read](/flutter-bloc-concepts#contextread).\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- [RepositoryProvider](/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget which provides a repository to its children.\n- [BlocListener](/flutter-bloc-concepts#bloclistener), a Flutter widget which\n  invokes the listener code in response to state changes in the bloc.\n- Adding events with [context.read](/flutter-bloc-concepts#contextselect).\n\n## Setup\n\nWe'll start off by creating a brand new Flutter project.\n\n<FlutterCreateSnippet />\n\nJust like in the [login tutorial](/tutorials/flutter-login), we're going to\ncreate internal packages to better layer our application architecture and\nmaintain clear boundaries and to maximize both reusability as well as improve\ntestability.\n\nIn this case, the [firebase_auth](https://pub.dev/packages/firebase_auth) and\n[google_sign_in](https://pub.dev/packages/google_sign_in) packages are going to\nbe our data layer so we're only going to be creating an\n`AuthenticationRepository` to compose data from the two API clients.\n\n## Authentication Repository\n\nThe `AuthenticationRepository` will be responsible for abstracting the internal\nimplementation details of how we authenticate and fetch user information. In\nthis case, it will be integrating with Firebase but we can always change the\ninternal implementation later on and our application will be unaffected.\n\n### Setup\n\nWe'll start by creating `packages/authentication_repository` and a\n`pubspec.yaml` at the root of the project.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\nNext, we can install the dependencies by running:\n\n<FlutterPubGetSnippet />\n\nin the `authentication_repository` directory.\n\nJust like most packages, the `authentication_repository` will define it's API\nsurface via\n`packages/authentication_repository/lib/authentication_repository.dart`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\nThe `authentication_repository` package will be exposing an\n`AuthenticationRepository` as well as models.\n\n:::\n\nNext, let's take a look at the models.\n\n### User\n\nThe `User` model will describe a user in the context of the authentication\ndomain. For the purposes of this example, a user will consist of an `email`,\n`id`, `name`, and `photo`.\n\n:::note\n\nIt's completely up to you to define what a user needs to look like in the\ncontext of your domain.\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\nThe `User` class is extending [equatable](https://pub.dev/packages/equatable) in\norder to override equality comparisons so that we can compare different\ninstances of `User` by value.\n\n:::\n\n:::tip\n\nIt's useful to define a `static` empty `User` so that we don't have to handle\n`null` Users and can always work with a concrete `User` object.\n\n:::\n\n### Repository\n\nThe `AuthenticationRepository` is responsible for abstracting the underlying\nimplementation of how a user is authenticated, as well as how a user is fetched.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nThe `AuthenticationRepository` exposes a `Stream<User>` which we can subscribe\nto in order to be notified of when a `User` changes. In addition, it exposes\nmethods to `signUp`, `logInWithGoogle`, `logInWithEmailAndPassword`, and\n`logOut`.\n\n:::note\n\nThe `AuthenticationRepository` is also responsible for handling low-level errors\nthat can occur in the data layer and exposes a clean, simple set of errors that\nalign with the domain.\n\n:::\n\nThat's it for the `AuthenticationRepository`. Next, let's take a look at how to\nintegrate it into the Flutter project we created.\n\n## Firebase Setup\n\nWe need to follow the\n[firebase_auth usage instructions](https://pub.dev/packages/firebase_auth#usage)\nin order to hook up our application to Firebase and enable\n[google_sign_in](https://pub.dev/packages/google_sign_in).\n\n:::caution\n\nRemember to update the `google-services.json` on Android and the\n`GoogleService-Info.plist` & `Info.plist` on iOS, otherwise the application will\ncrash.\n\n:::\n\n## Project Dependencies\n\nWe can replace the generated `pubspec.yaml` at the root of the project with the\nfollowing:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nNotice that we are specifying an assets directory for all of our applications\nlocal assets. Create an `assets` directory in the root of your project and add\nthe\n[bloc logo](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\nasset (which we'll use later).\n\nThen install all of the dependencies:\n\n<FlutterPubGetSnippet />\n\n:::note\n\nWe are depending on the `authentication_repository` package via path which will\nallow us to iterate quickly while still maintaining a clear separation.\n\n:::\n\n## main.dart\n\nThe `main.dart` file can be replaced with the following:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nIt's simply setting up some global configuration for the application and calling\n`runApp` with an instance of `App`.\n\n:::note\n\nWe're injecting a single instance of `AuthenticationRepository` into the `App`\nand it is an explicit constructor dependency.\n\n:::\n\n## App\n\nJust like in the [login tutorial](/tutorials/flutter-login), our `app.dart` will\nprovide an instance of the `AuthenticationRepository` to the application via\n`RepositoryProvider` and also creates and provides an instance of\n`AuthenticationBloc`. Then `AppView` consumes the `AuthenticationBloc` and\nhandles updating the current route based on the `AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\nThe `AppBloc` is responsible for managing the global state of the application.\nIt has a dependency on the `AuthenticationRepository` and subscribes to the\n`user` Stream in order to emit new states in response to changes in the current\nuser.\n\n### State\n\nThe `AppState` consists of an `AppStatus` and a `User`. The default constructor\naccepts an optional `User` and redirects to the private constructor with the\nappropriate authentication status.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### Event\n\nThe `AppEvent` has two subclasses:\n\n- `AppUserSubscriptionRequested` which notifies the bloc to subscribe to the\n  user stream.\n- `AppLogoutPressed` which notifies the bloc of a user logout action.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\nIn the constructor body, `AppEvent` subclasses are mapped to their corresponding\nevent handlers.\n\nIn the `_onUserSubscriptionRequested` event handler, the `AppBloc` uses\n`emit.onEach` to subscribe to the user stream of the `AuthenticationRepository`\nand emit a state in response to each `User`.\n\n`emit.onEach` creates a stream subscription internally and takes care of\ncanceling it when either `AppBloc` or the user stream is closed.\n\nIf the user stream emits an error, `addError` forwards the error and stack trace\nto any `BlocObserver` listening.\n\n:::caution\n\nIf `onError` is omitted, any errors on the user stream are considered unhandled,\nand will be thrown by `onEach`. As a result, the subscription to the user stream\nwill be canceled.\n\n:::\n\n:::tip\n\nA [`BlocObserver`](/bloc-concepts/#blocobserver-1) is great for logging Bloc\nevents, errors, and state changes especially in the context analytics and crash\nreporting.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## Models\n\nAn `Email` and `Password` input model are useful for encapsulating the\nvalidation logic and will be used in both the `LoginForm` and `SignUpForm`\n(later in the tutorial).\n\nBoth input models are made using the [formz](https://pub.dev/packages/formz)\npackage and allow us to work with a validated object rather than a primitive\ntype like a `String`.\n\n### Email\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## Login Page\n\nThe `LoginPage` is responsible for creating and providing an instance of\n`LoginCubit` to the `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nIt's very important to keep the creation of blocs/cubits separate from where\nthey are consumed. This will allow you to easily inject mock instances and test\nyour view in isolation.\n\n:::\n\n## Login Cubit\n\nThe `LoginCubit` is responsible for managing the `LoginState` of the form. It\nexposes APIs to `logInWithCredentials`, `logInWithGoogle`, as well as gets\nnotified when the email/password are updated.\n\n### State\n\nThe `LoginState` consists of an `Email`, `Password`, and `FormzStatus`. The\n`Email` and `Password` models extend `FormzInput` from the\n[formz](https://pub.dev/packages/formz) package.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\nThe `LoginCubit` has a dependency on the `AuthenticationRepository` in order to\nsign the user in either via credentials or via google sign in.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\nWe used a `Cubit` instead of a `Bloc` here because the `LoginState` is fairly\nsimple and localized. Even without events, we can still have a fairly good sense\nof what happened just by looking at the changes from one state to another and\nour code is a lot simpler and more concise.\n\n:::\n\n## Login Form\n\nThe `LoginForm` is responsible for rendering the form in response to the\n`LoginState` and invokes methods on the `LoginCubit` in response to user\ninteractions.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\nThe `LoginForm` also renders a \"Create Account\" button which navigates to the\n`SignUpPage` where a user can create a brand new account.\n\n## Sign Up Page\n\nThe `SignUp` structure mirrors the `Login` structure and consists of a\n`SignUpPage`, `SignUpView`, and `SignUpCubit`.\n\nThe `SignUpPage` is just responsible for creating and providing an instance of\nthe `SignUpCubit` to the `SignUpForm` (exactly like in `LoginPage`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\nJust as in the `LoginCubit`, the `SignUpCubit` has a dependency on the\n`AuthenticationRepository` in order to create new user accounts.\n\n:::\n\n## Sign Up Cubit\n\nThe `SignUpCubit` manages the state of the `SignUpForm` and communicates with\nthe `AuthenticationRepository` in order to create new user accounts.\n\n### State\n\nThe `SignUpState` reuses the same `Email` and `Password` form input models\nbecause the validation logic is the same.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\nThe `SignUpCubit` is extremely similar to the `LoginCubit` with the main\nexception being it exposes an API to submit the form as opposed to login.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## Sign Up Form\n\nThe `SignUpForm` is responsible for rendering the form in response to the\n`SignUpState` and invokes methods on the `SignUpCubit` in response to user\ninteractions.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## Home Page\n\nAfter a user either successfully logs in or signs up, the `user` stream will be\nupdated which will trigger a state change in the `AuthenticationBloc` and will\nresult in the `AppView` pushing the `HomePage` route onto the navigation stack.\n\nFrom the `HomePage`, the user can view their profile information and log out by\ntapping the exit icon in the `AppBar`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\nA `widgets` directory was created alongside the `view` directory within the\n`home` feature for reusable components that are specific to that particular\nfeature. In this case a simple `Avatar` widget is exported and used within the\n`HomePage`.\n\n:::\n\n:::note\n\nWhen the logout `IconButton` is tapped, an `AuthenticationLogoutRequested` event\nis added to the `AuthenticationBloc` which signs the user out and navigates them\nback to the `LoginPage`.\n\n:::\n\nAt this point we have a pretty solid login implementation using Firebase and we\nhave decoupled our presentation layer from the business logic layer by using the\nBloc Library.\n\nThe full source for this example can be found\n[here](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: Flutter Infinite List\ndescription:\n  An in-depth guide on how to build a Flutter infinite list with bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nIn this tutorial, we're going to be implementing an app which fetches data over\nthe network and loads it as a user scrolls using Flutter and the bloc library.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## Key Topics\n\n- Observe state changes with [BlocObserver](/bloc-concepts#blocobserver).\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles\n  building the widget in response to new states.\n- Adding events with [context.read](/flutter-bloc-concepts#contextread).\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- Use the `transformEvents` method with Rx.\n\n## Setup\n\nWe'll start off by creating a brand new Flutter project\n\n<FlutterCreateSnippet />\n\nWe can then go ahead and replace the contents of pubspec.yaml with\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nand then install all of our dependencies\n\n<FlutterPubGetSnippet />\n\n## Project Structure\n\n```\n├── lib\n|   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n|   |   |   └── post_event.dart\n|   |   |   └── post_state.dart\n|   |   └── models\n|   |   |   └── models.dart*\n|   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n|   |   |   └── view.dart*\n|   |   └── widgets\n|   |   |   └── bottom_loader.dart\n|   |   |   └── post_list_item.dart\n|   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nThe application uses a feature-driven directory structure. This project\nstructure enables us to scale the project by having self-contained features. In\nthis example we will only have a single feature (the post feature) and it's\nsplit up into respective folders with barrel files, indicated by the asterisk\n(\\*).\n\n## REST API\n\nFor this demo application, we'll be using\n[jsonplaceholder](http://jsonplaceholder.typicode.com) as our data source.\n\n:::note\n\njsonplaceholder is an online REST API which serves fake data; it's very useful\nfor building prototypes.\n\n:::\n\nOpen a new tab in your browser and visit\nhttps://jsonplaceholder.typicode.com/posts?_start=0&_limit=2 to see what the API\nreturns.\n\n<PostsJsonSnippet />\n\n:::note\n\nIn our url we specified the start and limit as query parameters to the GET\nrequest.\n\n:::\n\nGreat, now that we know what our data is going to look like, let's create the\nmodel.\n\n## Data Model\n\nCreate `post.dart` and let's get to work creating the model of our Post object.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post` is just a class with an `id`, `title`, and `body`.\n\n:::note\n\nWe extend [`Equatable`](https://pub.dev/packages/equatable) so that we can\ncompare `Posts`. Without this, we would need to manually change our class to\noverride equality and hashCode so that we could tell the difference between two\n`Posts` objects. See [the package](https://pub.dev/packages/equatable) for more\ndetails.\n\n:::\n\nNow that we have our `Post` object model, let's start working on the Business\nLogic Component (bloc).\n\n## Post Events\n\nBefore we dive into the implementation, we need to define what our `PostBloc` is\ngoing to be doing.\n\nAt a high level, it will be responding to user input (scrolling) and fetching\nmore posts in order for the presentation layer to display them. Let's start by\ncreating our `Event`.\n\nOur `PostBloc` will only be responding to a single event; `PostFetched` which\nwill be added by the presentation layer whenever it needs more Posts to present.\nSince our `PostFetched` event is a type of `PostEvent` we can create\n`bloc/post_event.dart` and implement the event like so.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\nTo recap, our `PostBloc` will be receiving `PostEvents` and converting them to\n`PostStates`. We have defined all of our `PostEvents` (PostFetched) so next\nlet's define our `PostState`.\n\n## Post States\n\nOur presentation layer will need to have several pieces of information in order\nto properly lay itself out:\n\n- `PostInitial`- will tell the presentation layer it needs to render a loading\n  indicator while the initial batch of posts are loaded\n- `PostSuccess`- will tell the presentation layer it has content to render\n  - `posts`- will be the `List<Post>` which will be displayed\n  - `hasReachedMax`- will tell the presentation layer whether or not it has\n    reached the maximum number of posts\n- `PostFailure`- will tell the presentation layer that an error has occurred\n  while fetching posts\n\nWe can now create `bloc/post_state.dart` and implement it like so.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\nWe implemented `copyWith` so that we can copy an instance of `PostSuccess` and\nupdate zero or more properties conveniently (this will come in handy later).\n\n:::\n\nNow that we have our `Events` and `States` implemented, we can create our\n`PostBloc`.\n\n## Post Bloc\n\nFor simplicity, our `PostBloc` will have a direct dependency on an\n`http client`; however, in a production application we suggest instead you\ninject an api client and use the repository pattern [docs](/architecture).\n\nLet's create `post_bloc.dart` and create our empty `PostBloc`.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\nJust from the class declaration we can tell that our PostBloc will be taking\nPostEvents as input and outputting PostStates.\n\n:::\n\nNext, we need to register an event handler to handle incoming `PostFetched`\nevents. In response to a `PostFetched` event, we will call `_fetchPosts` to\nfetch posts from the API.\n\n<PostBlocOnPostFetchedSnippet />\n\nOur `PostBloc` will `emit` new states via the `Emitter<PostState>` provided in\nthe event handler. Check out [core concepts](/bloc-concepts#streams) for more\ninformation.\n\nNow every time a `PostEvent` is added, if it is a `PostFetched` event and there\nare more posts to fetch, our `PostBloc` will fetch the next 20 posts.\n\nThe API will return an empty array if we try to fetch beyond the maximum number\nof posts (100), so if we get back an empty array, our bloc will `emit` the\ncurrentState except we will set `hasReachedMax` to true.\n\nIf we cannot retrieve the posts, we emit `PostStatus.failure`.\n\nIf we can retrieve the posts, we emit `PostStatus.success` and the entire list\nof posts.\n\nOne optimization we can make is to `throttle` the `PostFetched` event in order\nto prevent spamming our API unnecessarily. We can do this by using the\n`transform` parameter when we register the `_onFetched` event handler.\n\n:::note\n\nPassing a `transformer` to `on<PostFetched>` allows us to customize how events\nare processed.\n\n:::\n\n:::note\n\nMake sure to import\n[`package:stream_transform`](https://pub.dev/packages/stream_transform) to use\nthe `throttle` api.\n\n:::\n\n<PostBlocTransformerSnippet />\n\nOur finished `PostBloc` should now look like this:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\nGreat! Now that we've finished implementing the business logic all that's left\nto do is implement the presentation layer.\n\n## Presentation Layer\n\nIn our `main.dart` we can start by implementing our main function and calling\n`runApp` to render our root widget. Here, we can also include our bloc observer\nto log transitions and any errors.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nIn our `App` widget, the root of our project, we can then set the home to\n`PostsPage`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nIn our `PostsPage` widget, we use `BlocProvider` to create and provide an\ninstance of `PostBloc` to the subtree. Also, we add a `PostFetched` event so\nthat when the app loads, it requests the initial batch of Posts.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\nNext, we need to implement our `PostsList` view which will present our posts and\nhook up to our `PostBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList` is a `StatefulWidget` because it will need to maintain a\n`ScrollController`. In `initState`, we add a listener to our `ScrollController`\nso that we can respond to scroll events. We also access our `PostBloc` instance\nvia `context.read<PostBloc>()`.\n\n:::\n\nMoving along, our build method returns a `BlocBuilder`. `BlocBuilder` is a\nFlutter widget from the\n[flutter_bloc package](https://pub.dev/packages/flutter_bloc) which handles\nbuilding a widget in response to new bloc states. Any time our `PostBloc` state\nchanges, our builder function will be called with the new `PostState`.\n\n:::caution\n\nWe need to remember to clean up after ourselves and dispose of our\n`ScrollController` when the StatefulWidget is disposed.\n\n:::\n\nWhenever the user scrolls, we calculate how far you have scrolled down the page\nand if our distance is ≥ 90% of our `maxScrollextent` we add a `PostFetched`\nevent in order to load more posts.\n\nNext, we need to implement our `BottomLoader` widget which will indicate to the\nuser that we are loading more posts.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\nLastly, we need to implement our `PostListItem` which will render an individual\n`Post`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\nAt this point, we should be able to run our app and everything should work;\nhowever, there's one more thing we can do.\n\nOne added bonus of using the bloc library is that we can have access to all\n`Transitions` in one place.\n\nThe change from one state to another is called a `Transition`.\n\n:::note\n\nA `Transition` consists of the current state, the event, and the next state.\n\n:::\n\nEven though in this application we only have one bloc, it's fairly common in\nlarger applications to have many blocs managing different parts of the\napplication's state.\n\nIf we want to be able to do something in response to all `Transitions` we can\nsimply create our own `BlocObserver`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\nAll we need to do is extend `BlocObserver` and override the `onTransition`\nmethod.\n\n:::\n\nNow every time a Bloc `Transition` occurs we can see the transition printed to\nthe console.\n\n:::note\n\nIn practice, you can create different `BlocObservers` and because every state\nchange is recorded, we are able to very easily instrument our applications and\ntrack all user interactions and state changes in one place!\n\n:::\n\nThat's all there is to it! We've now successfully implemented an infinite list\nin flutter using the [bloc](https://pub.dev/packages/bloc) and\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) packages and we've\nsuccessfully separated our presentation layer from our business logic.\n\nOur `PostsPage` has no idea where the `Posts` are coming from or how they are\nbeing retrieved. Conversely, our `PostBloc` has no idea how the `State` is being\nrendered, it simply converts events into states.\n\nThe full source for this example can be found\n[here](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-login.mdx",
    "content": "---\ntitle: Flutter Login\ndescription: An in-depth guide on how to build a Flutter login flow with bloc.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nIn the following tutorial, we're going to build a Login Flow in Flutter using\nthe Bloc library.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## Key Topics\n\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- Adding events with [context.read](/flutter-bloc-concepts#contextread).\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- [RepositoryProvider](/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget which provides a repository to its children.\n- [BlocListener](/flutter-bloc-concepts#bloclistener), a Flutter widget which\n  invokes the listener code in response to state changes in the bloc.\n- Updating the UI based on a part of a bloc state with\n  [context.select](/flutter-bloc-concepts#contextselect).\n\n## Project Setup\n\nWe'll start off by creating a brand new Flutter project\n\n<FlutterCreateSnippet />\n\nNext, we can install all of our dependencies\n\n<FlutterPubGetSnippet />\n\n## Authentication Repository\n\nThe first thing we're going to do is create an `authentication_repository`\npackage which will be responsible for managing the authentication domain.\n\nWe'll start by creating a `packages/authentication_repository` directory at the\nroot of the project which will contain all internal packages.\n\nAt a high level, the directory structure should look like this:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\nNext, we can create a `pubspec.yaml` for the `authentication_repository`\npackage:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\n`package:authentication_repository` will be a pure Dart package without any\nexternal dependencies.\n\n:::\n\nNext up, we need to implement the `AuthenticationRepository` class itself which\nwill be in\n`packages/authentication_repository/lib/src/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\nThe `AuthenticationRepository` exposes a `Stream` of `AuthenticationStatus`\nupdates which will be used to notify the application when a user signs in or\nout.\n\nIn addition, there are `logIn` and `logOut` methods which are stubbed for\nsimplicity but can easily be extended to authenticate with `FirebaseAuth` for\nexample or some other authentication provider.\n\n:::note\n\nSince we are maintaining a `StreamController` internally, a `dispose` method is\nexposed so that the controller can be closed when it is no longer needed.\n\n:::\n\nLastly, we need to create\n`packages/authentication_repository/lib/authentication_repository.dart` which\nwill contain the public exports:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\nThat's it for the `AuthenticationRepository`, next we'll work on the\n`UserRepository`.\n\n## User Repository\n\nJust like with the `AuthenticationRepository`, we will create a\n`user_repository` package inside the `packages` directory.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\nNext, we'll create the `pubspec.yaml` for the `user_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\nThe `user_repository` will be responsible for the user domain and will expose\nAPIs to interact with the current user.\n\nThe first thing we will define is the user model in\n`packages/user_repository/lib/src/models/user.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\nFor simplicity, a user just has an `id` property but in practice we might have\nadditional properties like `firstName`, `lastName`, `avatarUrl`, etc...\n\n:::note\n\n[`package:equatable`](https://pub.dev/packages/equatable) is used to enable\nvalue comparisons of the `User` object.\n\n:::\n\nNext, we can create a `models.dart` in `packages/user_repository/lib/src/models`\nwhich will export all models so that we can use a single import state to import\nmultiple models.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\nNow that the models have been defined, we can implement the `UserRepository`\nclass in `packages/user_repository/lib/src/user_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\nFor this simple example, the `UserRepository` exposes a single method `getUser`\nwhich will retrieve the current user. We are stubbing this but in practice this\nis where we would query the current user from the backend.\n\nAlmost done with the `user_repository` package -- the only thing left to do is\nto create the `user_repository.dart` file in `packages/user_repository/lib`\nwhich defines the public exports:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\nNow that we have the `authentication_repository` and `user_repository` packages\ncomplete, we can focus on the Flutter application.\n\n## Installing Dependencies\n\nLet's start by updating the generated `pubspec.yaml` at the root of our project:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nWe can install the dependencies by running:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\nThe `AuthenticationBloc` will be responsible for reacting to changes in the\nauthentication state (exposed by the `AuthenticationRepository`) and will emit\nstates we can react to in the presentation layer.\n\nThe implementation for the `AuthenticationBloc` is inside of\n`lib/authentication` because we treat authentication as a feature in our\napplication layer.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\nUse the\n[VSCode Extension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nor [IntelliJ Plugin](https://plugins.jetbrains.com/plugin/12129-bloc) to create\nblocs automatically.\n\n:::\n\n### authentication_event.dart\n\n`AuthenticationEvent` instances will be the input to the `AuthenticationBloc`\nand will be processed and used to emit new `AuthenticationState` instances.\n\nIn this application, the `AuthenticationBloc` will be reacting to two different\nevents:\n\n- `AuthenticationSubscriptionRequested`: initial event that notifies the bloc to\n  subscribe to the `AuthenticationStatus` stream\n- `AuthenticationLogoutPressed`: notifies the bloc of a user logout action\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\nNext, let's take a look at the `AuthenticationState`.\n\n### authentication_state.dart\n\n`AuthenticationState` instances will be the output of the `AuthenticationBloc`\nand will be consumed by the presentation layer.\n\nThe `AuthenticationState` class has three named constructors:\n\n- `AuthenticationState.unknown()`: the default state which indicates that the\n  bloc does not yet know whether the current user is authenticated or not.\n\n- `AuthenticationState.authenticated()`: the state which indicates that the user\n  is current authenticated.\n\n- `AuthenticationState.unauthenticated()`: the state which indicates that the\n  user is current not authenticated.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\nNow that we have seen the `AuthenticationEvent` and `AuthenticationState`\nimplementations let's take a look at `AuthenticationBloc`.\n\n### authentication_bloc.dart\n\nThe `AuthenticationBloc` manages the authentication state of the application\nwhich is used to determine things like whether or not to start the user at a\nlogin page or a home page.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\nThe `AuthenticationBloc` has a dependency on both the `AuthenticationRepository`\nand `UserRepository` and defines the initial state as\n`AuthenticationState.unknown()`.\n\nIn the constructor body, `AuthenticationEvent` subclasses are mapped to their\ncorresponding event handlers.\n\nIn the `_onSubscriptionRequested` event handler, the `AuthenticationBloc` uses\n`emit.onEach` to subscribe to the `status` stream of the\n`AuthenticationRepository` and emit a state in response to each\n`AuthenticationStatus`.\n\n`emit.onEach` creates a stream subscription internally and takes care of\ncanceling it when either `AuthenticationBloc` or the `status` stream is closed.\n\nIf the `status` stream emits an error, `addError` forwards the error and\nstackTrace to any `BlocObserver` listening.\n\n:::caution\n\nIf `onError` is omitted, any errors on the `status` stream are considered\nunhandled, and will be thrown by `onEach`. As a result, the subscription to the\n`status` stream will be canceled.\n\n:::\n\n:::tip\n\nA [`BlocObserver`](/bloc-concepts/#blocobserver-1) is great for logging Bloc\nevents, errors, and state changes especially in the context analytics and crash\nreporting.;\n\n:::\n\nWhen the `status` stream emits `AuthenticationStatus.unknown` or\n`unauthenticated`, the corresponding `AuthenticationState` is emitted.\n\nWhen `AuthenticationStatus.authenticated` is emitted, the `AuthentictionBloc`\nqueries the user via the `UserRepository`.\n\n## main.dart\n\nNext, we can replace the default `main.dart` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## App\n\n`app.dart` will contain the root `App` widget for the entire application.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`app.dart` is split into two parts `App` and `AppView`. `App` is responsible for\ncreating/providing the `AuthenticationBloc` which will be consumed by the\n`AppView`. This decoupling will enable us to easily test both the `App` and\n`AppView` widgets later on.\n\n:::\n\n:::note\n\n`RepositoryProvider` is used to provide the single instance of\n`AuthenticationRepository` to the entire application which will come in handy\nlater on.\n\n:::\n\nBy default, `BlocProvider` is lazy and does not call `create` until the first\ntime the Bloc is accessed. Since `AuthenticationBloc` should always subscribe to\nthe `AuthenticationStatus` stream immediately (via the\n`AuthenticationSubscriptionRequested` event), we can explicitly opt out of this\nbehavior by setting `lazy: false`.\n\n`AppView` is a `StatefulWidget` because it maintains a `GlobalKey` which is used\nto access the `NavigatorState`. By default, `AppView` will render the\n`SplashPage` (which we will see later) and it uses `BlocListener` to navigate to\ndifferent pages based on changes in the `AuthenticationState`.\n\n## Splash\n\nThe splash feature will just contain a simple view which will be rendered right\nwhen the app is launched while the app determines whether the user is\nauthenticated.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage` exposes a static `Route` which makes it very easy to navigate to\nvia `Navigator.of(context).push(SplashPage.route())`;\n\n:::\n\n## Login\n\nThe login feature contains a `LoginPage`, `LoginForm` and `LoginBloc` and allows\nusers to enter a username and password to log into the application.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### Login Models\n\nWe are using [`package:formz`](https://pub.dev/packages/formz) to create\nreusable and standard models for the `username` and `password`.\n\n#### Username\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\nFor simplicity, we are just validating the username to ensure that it is not\nempty but in practice you can enforce special character usage, length, etc...\n\n#### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\nAgain, we are just performing a simple check to ensure the password is not\nempty.\n\n#### Models Barrel\n\nJust like before, there is a `models.dart` barrel to make it easy to import the\n`Username` and `Password` models with a single import.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\nThe `LoginBloc` manages the state of the `LoginForm` and takes care validating\nthe username and password input as well as the state of the form.\n\n#### login_event.dart\n\nIn this application there are three different `LoginEvent` types:\n\n- `LoginUsernameChanged`: notifies the bloc that the username has been modified.\n- `LoginPasswordChanged`: notifies the bloc that the password has been modified.\n- `LoginSubmitted`: notifies the bloc that the form has been submitted.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\nThe `LoginState` will contain the status of the form as well as the username and\npassword input states.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\nThe `Username` and `Password` models are used as part of the `LoginState` and\nthe status is also part of [package:formz](https://pub.dev/packages/formz).\n\n:::\n\n#### login_bloc.dart\n\nThe `LoginBloc` is responsible for reacting to user interactions in the\n`LoginForm` and handling the validation and submission of the form.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\nThe `LoginBloc` has a dependency on the `AuthenticationRepository` because when\nthe form is submitted, it invokes `logIn`. The initial state of the bloc is\n`pure` meaning neither the inputs nor the form has been touched or interacted\nwith.\n\nWhenever either the `username` or `password` change, the bloc will create a\ndirty variant of the `Username`/`Password` model and update the form status via\nthe `Formz.validate` API.\n\nWhen the `LoginSubmitted` event is added, if the current status of the form is\nvalid, the bloc makes a call to `logIn` and updates the status based on the\noutcome of the request.\n\nNext let's take a look at the `LoginPage` and `LoginForm`.\n\n### Login Page\n\nThe `LoginPage` is responsible for exposing the `Route` as well as creating and\nproviding the `LoginBloc` to the `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\n`context.read<AuthenticationRepository>()` is used to lookup the instance of\n`AuthenticationRepository` via the `BuildContext`.\n\n:::\n\n### Login Form\n\nThe `LoginForm` handles notifying the `LoginBloc` of user events and also\nresponds to state changes using `BlocBuilder` and `BlocListener`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`BlocListener` is used to show a `SnackBar` if the login submission fails. In\naddition, `context.select` is used to efficiently access specific parts of the\n`LoginState` for each widget, preventing unnecessary rebuilds. The `onChanged`\ncallback is used to notify the `LoginBloc` of changes to the username/password.\n\nThe `_LoginButton` widget is only enabled if the status of the form is valid and\na `CircularProgressIndicator` is shown in its place while the form is being\nsubmitted.\n\n## Home\n\nUpon a successful `logIn` request, the state of the `AuthenticationBloc` will\nchange to `authenticated` and the user will be navigated to the `HomePage` where\nwe display the user's `id` as well as a button to log out.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### Home Page\n\nThe `HomePage` can access the current user id via\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` and displays\nit via a `Text` widget. In addition, when the logout button is tapped, an\n`AuthenticationLogoutPressed` event is added to the `AuthenticationBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` will trigger\nupdates if the user id changes.\n\n:::\n\nAt this point we have a pretty solid login implementation and we have decoupled\nour presentation layer from the business logic layer by using Bloc.\n\nThe full source for this example (including unit and widget tests) can be found\n[here](https://github.com/felangel/Bloc/tree/master/examples/flutter_login).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: Flutter Timer\ndescription: An in-depth guide on how to build a Flutter timer app with bloc.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn the following tutorial we're going to cover how to build a timer application\nusing the bloc library. The finished application should look like this:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## Key Topics\n\n- Observe state changes with [BlocObserver](/bloc-concepts#blocobserver).\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles\n  building the widget in response to new states.\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- Learn to use `StreamSubscription` in a Bloc.\n- Prevent unnecessary rebuilds with `buildWhen`.\n\n## Setup\n\nWe'll start off by creating a brand new Flutter project:\n\n<FlutterCreateSnippet />\n\nWe can then replace the contents of pubspec.yaml with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nWe'll be using the [flutter_bloc](https://pub.dev/packages/flutter_bloc) and\n[equatable](https://pub.dev/packages/equatable) packages in this app.\n\n:::\n\nNext, run `flutter pub get` to install all the dependencies.\n\n## Project Structure\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## Ticker\n\nThe ticker will be our data source for the timer application. It will expose a\nstream of ticks which we can subscribe and react to.\n\nStart off by creating `ticker.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\nAll our `Ticker` class does is expose a tick function which takes the number of\nticks (seconds) we want and returns a stream which emits the remaining seconds\nevery second.\n\nNext up, we need to create our `TimerBloc` which will consume the `Ticker`.\n\n## Timer Bloc\n\n### TimerState\n\nWe'll start off by defining the `TimerStates` which our `TimerBloc` can be in.\n\nOur `TimerBloc` state can be one of the following:\n\n- `TimerInitial`: ready to start counting down from the specified duration.\n- `TimerRunInProgress`: actively counting down from the specified duration.\n- `TimerRunPause`: paused at some remaining duration.\n- `TimerRunComplete`: completed with a remaining duration of 0.\n\nEach of these states will have an implication on the user interface and actions\nthat the user can perform. For example:\n\n- if the state is `TimerInitial` the user will be able to start the timer.\n- if the state is `TimerRunInProgress` the user will be able to pause and reset\n  the timer as well as see the remaining duration.\n- if the state is `TimerRunPause` the user will be able to resume the timer and\n  reset the timer.\n- if the state is `TimerRunComplete` the user will be able to reset the timer.\n\nIn order to keep all of our bloc files together, let's create a bloc directory\nwith `bloc/timer_state.dart`.\n\n:::tip\n\nYou can use the\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) or\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nextensions to autogenerate the following bloc files for you.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\nNote that all of the `TimerStates` extend the abstract base class `TimerState`\nwhich has a duration property. This is because no matter what state our\n`TimerBloc` is in, we want to know how much time is remaining. Additionally,\n`TimerState` extends `Equatable` to optimize our code by ensuring that our app\ndoes not trigger rebuilds if the same state occurs.\n\nNext up, let's define and implement the `TimerEvents` which our `TimerBloc` will\nbe processing.\n\n### TimerEvent\n\nOur `TimerBloc` will need to know how to process the following events:\n\n- `TimerStarted`: informs the TimerBloc that the timer should be started.\n- `TimerPaused`: informs the TimerBloc that the timer should be paused.\n- `TimerResumed`: informs the TimerBloc that the timer should be resumed.\n- `TimerReset`: informs the TimerBloc that the timer should be reset to the\n  original state.\n- `_TimerTicked`: informs the TimerBloc that a tick has occurred and that it\n  needs to update its state accordingly.\n\nIf you didn't use the\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) or\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nextensions, then create `bloc/timer_event.dart` and let's implement those\nevents.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\nNext up, let's implement the `TimerBloc`!\n\n### TimerBloc\n\nIf you haven't already, create `bloc/timer_bloc.dart` and create an empty\n`TimerBloc`.\n\n<TimerBlocEmptySnippet />\n\nThe first thing we need to do is define the initial state of our `TimerBloc`. In\nthis case, we want the `TimerBloc` to start off in the `TimerInitial` state with\na preset duration of 1 minute (60 seconds).\n\n<TimerBlocInitialStateSnippet />\n\nNext, we need to define the dependency on our `Ticker`.\n\n<TimerBlocTickerSnippet />\n\nWe are also defining a `StreamSubscription` for our `Ticker` which we will get\nto in a bit.\n\nAt this point, all that's left to do is implement the event handlers. For\nimproved readability, I like to break out each event handler into its own helper\nfunction. We'll start with the `TimerStarted` event.\n\n<TimerBlocOnStartedSnippet />\n\nIf the `TimerBloc` receives a `TimerStarted` event, it pushes a\n`TimerRunInProgress` state with the start duration. In addition, if there was\nalready an open `_tickerSubscription` we need to cancel it to deallocate the\nmemory. We also need to override the `close` method on our `TimerBloc` so that\nwe can cancel the `_tickerSubscription` when the `TimerBloc` is closed. Lastly,\nwe listen to the `_ticker.tick` stream and on every tick we add a `_TimerTicked`\nevent with the remaining duration.\n\nNext, let's implement the `_TimerTicked` event handler.\n\n<TimerBlocOnTickedSnippet />\n\nEvery time a `_TimerTicked` event is received, if the tick's duration is greater\nthan 0, we need to push an updated `TimerRunInProgress` state with the new\nduration. Otherwise, if the tick's duration is 0, our timer has ended and we\nneed to push a `TimerRunComplete` state.\n\nNow let's implement the `TimerPaused` event handler.\n\n<TimerBlocOnPausedSnippet />\n\nIn `_onPaused` if the `state` of our `TimerBloc` is `TimerRunInProgress`, then\nwe can pause the `_tickerSubscription` and push a `TimerRunPause` state with the\ncurrent timer duration.\n\nNext, let's implement the `TimerResumed` event handler so that we can unpause\nthe timer.\n\n<TimerBlocOnResumedSnippet />\n\nThe `TimerResumed` event handler is very similar to the `TimerPaused` event\nhandler. If the `TimerBloc` has a `state` of `TimerRunPause` and it receives a\n`TimerResumed` event, then it resumes the `_tickerSubscription` and pushes a\n`TimerRunInProgress` state with the current duration.\n\nLastly, we need to implement the `TimerReset` event handler.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\nIf the `TimerBloc` receives a `TimerReset` event, it needs to cancel the current\n`_tickerSubscription` so that it isn't notified of any additional ticks and\npushes a `TimerInitial` state with the original duration.\n\nThat's all there is to the `TimerBloc`. Now all that's left is implement the UI\nfor our Timer Application.\n\n## Application UI\n\n### MyApp\n\nWe can start off by deleting the contents of `main.dart` and replacing it with\nthe following.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nNext, let's create our 'App' widget in `app.dart`, which will be the root of our\napplication.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nNext, we need to implement our `Timer` widget.\n\n### Timer\n\nOur `Timer` widget (`lib/timer/view/timer_page.dart`) will be responsible for\ndisplaying the remaining time along with the proper buttons which will enable\nusers to start, pause, and reset the timer.\n\n<TimerPageSnippet />\n\nSo far, we're just using `BlocProvider` to access the instance of our\n`TimerBloc`.\n\nNext, we're going to implement our `Actions` widget which will have the proper\nactions (start, pause, and reset).\n\n### Barrel\n\nIn order to clean up our imports from the `Timer` section, we need to create a\nbarrel file `timer/timer.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### Actions\n\n<ActionsSnippet />\n\nThe `Actions` widget is just another `StatelessWidget` which uses a\n`BlocBuilder` to rebuild the UI every time we get a new `TimerState`. `Actions`\nuses `context.read<TimerBloc>()` to access the `TimerBloc` instance and returns\ndifferent `FloatingActionButtons` based on the current state of the `TimerBloc`.\nEach of the `FloatingActionButtons` adds an event in its `onPressed` callback to\nnotify the `TimerBloc`.\n\nIf you want fine-grained control over when the `builder` function is called you\ncan provide an optional `buildWhen` to `BlocBuilder`. The `buildWhen` takes the\nprevious bloc state and current bloc state and returns a `boolean`. If\n`buildWhen` returns `true`, `builder` will be called with `state` and the widget\nwill rebuild. If `buildWhen` returns `false`, `builder` will not be called with\n`state` and no rebuild will occur.\n\nIn this case, we don't want the `Actions` widget to be rebuilt on every tick\nbecause that would be inefficient. Instead, we only want `Actions` to rebuild if\nthe `runtimeType` of the `TimerState` changes (TimerInitial =>\nTimerRunInProgress, TimerRunInProgress => TimerRunPause, etc...).\n\nAs a result, if we randomly colored the widgets on every rebuild, it would look\nlike:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\nEven though the `Text` widget is rebuilt on every tick, we only rebuild the\n`Actions` if they need to be rebuilt.\n\n:::\n\n### Background\n\nLastly, add the background widget as follows:\n\n<BackgroundSnippet />\n\n### Putting it all together\n\nThat's all there is to it! At this point we have a pretty solid timer\napplication which efficiently rebuilds only widgets that need to be rebuilt.\n\nThe full source for this example can be found\n[here](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Flutter Todos\ndescription: An in-depth guide on how to build a Flutter todos app with bloc.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn the following tutorial, we're going to build a todos app in Flutter using the\nBloc library.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## Key Topics\n\n- [Bloc and Cubit](/bloc-concepts#cubit-vs-bloc) to manage the various feature\n  states.\n- [Layered Architecture](/architecture) for separation of concerns and to\n  facilitate reusability.\n- [BlocObserver](/bloc-concepts#blocobserver) to observe state changes.\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), a Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), a Flutter widget that\n  handles building the widget in response to new states.\n- [BlocListener](/flutter-bloc-concepts#bloclistener), a Flutter widget that\n  handles performing side effects in response to state changes.\n- [RepositoryProvider](/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget to provide a repository to its children.\n- [Equatable](/faqs#when-to-use-equatable) to prevent unnecessary rebuilds.\n- [MultiBlocListener](/flutter-bloc-concepts#multibloclistener), a Flutter\n  widget that reduces nesting when using multiple BlocListeners.\n\n## Setup\n\nWe'll start off by creating a brand new Flutter project using the\n[very_good_cli](https://pub.dev/packages/very_good_cli).\n\n<FlutterCreateSnippet />\n\n:::note\n\nInstall `very_good_cli` using the following command\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\nNext we'll create the `todos_api`, `local_storage_todos_api`, and\n`todos_repository` packages using `very_good_cli`:\n\n<FlutterCreatePackagesSnippet />\n\nWe can then replace the contents of `pubspec.yaml` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nFinally, we can install all the dependencies:\n\n<VeryGoodPackagesGetSnippet />\n\n## Project Structure\n\nOur application project structure should look like:\n\n<ProjectStructureSnippet />\n\nWe split the project into multiple packages in order to maintain explicit\ndependencies for each package with clear boundaries that enforce the\n[single responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle).\nModularizing our project like this has many benefits including but not limited\nto:\n\n- easy to reuse packages across multiple projects\n- CI/CD improvements in terms of efficiency (run checks on only the code that\n  has changed)\n- easy to maintain the packages in isolation with their dedicated test suites,\n  semantic versioning, and release cycle/cadence\n\n## Architecture\n\n![Todos Architecture Diagram](~/assets/tutorials/todos-architecture.png)\n\nLayering our code is incredibly important and helps us iterate quickly and with\nconfidence. Each layer has a single responsibility and can be used and tested in\nisolation. This allows us to keep changes contained to a specific layer in order\nto minimize the impact on the entire application. In addition, layering our\napplication allows us to easily reuse libraries across multiple projects\n(especially with respect to the data layer).\n\nOur application consists of three main layers:\n\n- data layer\n- domain layer\n- feature layer\n  - presentation/UI (widgets)\n  - business logic (blocs/cubits)\n\n**Data Layer**\n\nThis layer is the lowest layer and is responsible for retrieving raw data from\nexternal sources such as a databases, APIs, and more. Packages in the data layer\ngenerally should not depend on any UI and can be reused and even published on\n[pub.dev](https://pub.dev) as a standalone package. In this example, our data\nlayer consists of the `todos_api` and `local_storage_todos_api` packages.\n\n**Domain Layer**\n\nThis layer combines one or more data providers and applies \"business rules\" to\nthe data. Each component in this layer is called a repository and each\nrepository generally manages a single domain. Packages in the repository layer\nshould generally only interact with the data layer. In this example, our\nrepository layer consists of the `todos_repository` package.\n\n**Feature Layer**\n\nThis layer contains all of the application-specific features and use cases. Each\nfeature generally consists of some UI and business logic. Features should\ngenerally be independent of other features so that they can easily be\nadded/removed without impacting the rest of the codebase. Within each feature,\nthe state of the feature along with any business logic is managed by blocs.\nBlocs interact with zero or more repositories. Blocs react to events and emit\nstates which trigger changes in the UI. Widgets within each feature should\ngenerally only depend on the corresponding bloc and render UI based on the\ncurrent state. The UI can notify the bloc of user input via events. In this\nexample, our application will consist of the `home`, `todos_overview`, `stats`,\nand `edit_todos` features.\n\nNow that we've gone over the layers at a high level, let's start building our\napplication starting with the data layer!\n\n## Data Layer\n\nThe data layer is the lowest layer in our application and consists of raw data\nproviders. Packages in this layer are primarily concerned with where/how data is\ncoming from. In this case our data layer will consist of the `TodosApi`, which\nis an interface, and the `LocalStorageTodosApi`, which is an implementation of\nthe `TodosApi` backed by `shared_preferences`.\n\n### TodosApi\n\nThe `todos_api` package will export a generic interface for interacting/managing\ntodos. Later we'll implement the `TodosApi` using `shared_preferences`. Having\nan abstraction will make it easy to support other implementations without having\nto change any other part of our application. For example, we can later add a\n`FirestoreTodosApi`, which uses `cloud_firestore` instead of\n`shared_preferences`, with minimal code changes to the rest of the application.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### Todo model\n\nNext we'll define our `Todo` model.\n\nThe first thing of note is that the `Todo` model doesn't live in our app — it's\npart of the `todos_api` package. This is because the `TodosApi` defines APIs\nthat return/accept `Todo` objects. The model is a Dart representation of the raw\nTodo object that will be stored/retrieved.\n\nThe `Todo` model uses\n[json_serializable](https://pub.dev/packages/json_serializable) to handle the\njson (de)serialization. If you are following along, you will have to run the\n[code generation step](https://pub.dev/packages/json_serializable#running-the-code-generator)\nto resolve the compiler errors.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\n`json_map.dart` provides a `typedef` for code checking and linting.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\nThe model of the `Todo` is defined in `todos_api/models/todo.dart` and is\nexported by `package:todos_api/todos_api.dart`.\n\n#### Update Exports\n\nOur `Todo` model and the `TodosApi` are exported via barrel files. Notice how we\ndon't import the model directly, but we import it in `lib/src/todos_api.dart`\nwith a reference to the package barrel file:\n`import 'package:todos_api/todos_api.dart';`. Update the barrel files to resolve\nany remaining import errors:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Streams vs Futures\n\nIn a previous version of this tutorial, the `TodosApi` was `Future`-based rather\nthan `Stream`-based.\n\nFor an example of a `Future`-based API see\n[Brian Egan's implementation in his Architecture Samples](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core).\n\nA `Future`-based implementation could consist of two methods: `loadTodos` and\n`saveTodos` (note the plural). This means, a full list of todos must be provided\nto the method each time.\n\n- One limitation of this approach is that the standard CRUD (Create, Read,\n  Update, and Delete) operation requires sending the full list of todos with\n  each call. For example, on an Add Todo screen, one cannot just send the added\n  todo item. Instead, we must keep track of the entire list and provide the\n  entire new list of todos when persisting the updated list.\n- A second limitation is that `loadTodos` is a one-time delivery of data. The\n  app must contain logic to ask for updates periodically.\n\nIn the current implementation, the `TodosApi` exposes a `Stream<List<Todo>>` via\n`getTodos()` which will report real-time updates to all subscribers when the\nlist of todos has changed.\n\nIn addition, todos can be created, deleted, or updated individually. For\nexample, both deleting and saving a todo are done with only the `todo` as the\nargument. It's not necessary to provide the newly updated list of todos each\ntime.\n\n### LocalStorageTodosApi\n\nThis package implements the `todos_api` using the\n[`shared_preferences`](https://pub.dev/packages/shared_preferences) package.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## Repository Layer\n\nA [repository](/architecture#repository) is part of the business layer. A\nrepository depends on one or more data providers that have no business value,\nand combines their public API into APIs that provide business value. In\naddition, having a repository layer helps abstract data acquisition from the\nrest of the application, allowing us to change where/how data is being stored\nwithout affecting other parts of the app.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\nInstantiating the repository requires specifying a `TodosApi`, which we\ndiscussed earlier in this tutorial, so we added it as a dependency in our\n`pubspec.yaml`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### Library Exports\n\nIn addition to exporting the `TodosRepository` class, we also export the `Todo`\nmodel from the `todos_api` package. This step prevents tight coupling between\nthe application and the data providers.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\nWe decided to re-export the same `Todo` model from the `todos_api`, rather than\nredefining a separate model in the `todos_repository`, because in this case we\nare in complete control of the data model. In many cases, the data provider will\nnot be something that you can control. In those cases, it becomes increasingly\nimportant to maintain your own model definitions in the repository layer to\nmaintain full control of the interface and API contract.\n\n## Feature Layer\n\n### Entrypoint\n\nOur app's entrypoint is `main.dart`. In this case, there are three versions:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\nThe most notable thing is the concrete implementation of the\n`local_storage_todos_api` is instantiated within each entrypoint.\n\n### Bootstrapping\n\n`bootstrap.dart` loads our `BlocObserver` and creates the instance of\n`TodosRepository`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### App\n\n`App` wraps a `RepositoryProvider` widget that provides the repository to all\nchildren. Since both the `EditTodoPage` and `HomePage` subtrees are descendents,\nall the blocs and cubits can access the repository.\n\n`AppView` creates the `MaterialApp` and configures the theme and localizations.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### Theme\n\nThis provides theme definition for light and dark mode.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### Home\n\nThe home feature is responsible for managing the state of the currently-selected\ntab and displays the correct subtree.\n\n#### HomeState\n\nThere are only two states associated with the two screens: `todos` and `stats`.\n\n:::note\n\n`EditTodo` is a separate route therefore it isn't part of the `HomeState`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### HomeCubit\n\nA cubit is appropriate in this case due to the simplicity of the business logic.\nWe have one method `setTab` to change the tab.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### HomeView\n\n`view.dart` is a barrel file that exports all relevant UI components for the\nhome feature.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\n`home_page.dart` contains the UI for the root page that the user will see when\nthe app is launched.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `HomePage` is:\n\n<HomePageTreeSnippet />\n\nThe `HomePage` provides an instance of `HomeCubit` to `HomeView`. `HomeView`\nuses `context.select` to selectively rebuild whenever the tab changes. This\nallows us to easily widget test `HomeView` by providing a mock `HomeCubit` and\nstubbing the state.\n\nThe `BottomAppBar` contains `HomeTabButton` widgets which call `setTab` on the\n`HomeCubit`. The instance of the cubit is looked up via `context.read` and the\nappropriate method is invoked on the cubit instance.\n\n:::caution\n\n`context.read` doesn't listen for changes, it is just used to access to\n`HomeCubit` and call `setTab`.\n\n:::\n\n### TodosOverview\n\nThe todos overview feature allows users to manage their todos by creating,\nediting, deleting, and filtering todos.\n\n#### TodosOverviewEvent\n\nLet's create `todos_overview/bloc/todos_overview_event.dart` and define the\nevents.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: This is the startup event. In response,\n  the bloc subscribes to the stream of todos from the `TodosRepository`.\n- `TodosOverviewTodoDeleted`: This deletes a Todo.\n- `TodosOverviewTodoCompletionToggled`: This toggles a todo's completed status.\n- `TodosOverviewToggleAllRequested`: This toggles completion for all todos.\n- `TodosOverviewClearCompletedRequested`: This deletes all completed todos.\n- `TodosOverviewUndoDeletionRequested`: This undoes a todo deletion, e.g. an\n  accidental deletion.\n- `TodosOverviewFilterChanged`: This takes a `TodosViewFilter` as an argument\n  and changes the view by applying a filter.\n\n#### TodosOverviewState\n\nLet's create `todos_overview/bloc/todos_overview_state.dart` and define the\nstate.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState` will keep track of a list of todos, the active filter, the\n`lastDeletedTodo`, and the status.\n\n:::note\n\nIn addition to the default getters and setters, we have a custom getter called\n`filteredTodos`. The UI uses `BlocBuilder` to access either\n`state.filteredTodos` or `state.todos`.\n\n:::\n\n#### TodosOverviewBloc\n\nLet's create `todos_overview/bloc/todos_overview_bloc.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nThe bloc does not create an instance of the `TodosRepository` internally.\nInstead, it relies on an instance of the repository to be injected via\nconstructor.\n\n:::\n\n##### onSubscriptionRequested\n\nWhen `TodosOverviewSubscriptionRequested` is added, the bloc starts by emitting\na `loading` state. In response, the UI can then render a loading indicator.\n\nNext, we use `emit.forEach<List<Todo>>( ... )` which creates a subscription on\nthe todos stream from the `TodosRepository`.\n\n:::caution\n\n`emit.forEach()` is not the same `forEach()` used by lists. This `forEach`\nenables the bloc to subscribe to a `Stream` and emit a new state for each update\nfrom the stream.\n\n:::\n\n:::note\n\n`stream.listen` is never called directly in this tutorial. Using\n`await emit.forEach()` is a newer pattern for subscribing to a stream which\nallows the bloc to manage the subscription internally.\n\n:::\n\nNow that the subscription is handled, we will handle the other events, like\nadding, modifying, and deleting todos.\n\n##### onTodoSaved\n\n`_onTodoSaved` simply calls `_todosRepository.saveTodo(event.todo)`.\n\n:::note\n\n`emit` is never called from within `onTodoSaved` and many other event handlers.\nInstead, they notify the repository which emits an updated list via the todos\nstream. See the [data flow](#data-flow) section for more information.\n\n:::\n\n##### Undo\n\nThe undo feature allows users to restore the last deleted item.\n\n`_onTodoDeleted` does two things. First, it emits a new state with the `Todo` to\nbe deleted. Then, it deletes the `Todo` via a call to the repository.\n\n`_onUndoDeletionRequested` runs when the undo deletion request event comes from\nthe UI.\n\n`_onUndoDeletionRequested` does the following:\n\n- Temporarily saves a copy of the last deleted todo.\n- Updates the state by removing the `lastDeletedTodo`.\n- Reverts the deletion.\n\n##### Filtering\n\n`_onFilterChanged` emits a new state with the new event filter.\n\n#### Models\n\nThere is one model file that deals with the view filtering.\n\n`todos_view_filter.dart` is an enum that represents the three view filters and\nthe methods to apply the filter.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart` is the barrel file for exports.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\nNext, let's take a look at the `TodosOverviewPage`.\n\n#### TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `TodosOverviewPage` is:\n\n<TodosOverviewPageTreeSnippet />\n\nJust as with the `Home` feature, the `TodosOverviewPage` provides an instance of\nthe `TodosOverviewBloc` to the subtree via `BlocProvider<TodosOverviewBloc>`.\nThis scopes the `TodosOverviewBloc` to just the widgets below\n`TodosOverviewPage`.\n\nThere are three widgets that are listening for changes in the\n`TodosOverviewBloc`.\n\n1. The first is a `BlocListener` that listens for errors. The `listener` will\n   only be called when `listenWhen` returns `true`. If the status is\n   `TodosOverviewStatus.failure`, a `SnackBar` is displayed.\n\n2. We created a second `BlocListener` that listens for deletions. When a todo\n   has been deleted, a `SnackBar` is displayed with an undo button. If the user\n   taps undo, the `TodosOverviewUndoDeletionRequested` event will be added to\n   the bloc.\n\n3. Finally, we use a `BlocBuilder` to builds the ListView that displays the\n   todos.\n\nThe `AppBar`contains two actions which are dropdowns for filtering and\nmanipulating the todos.\n\n:::note\n\n`TodosOverviewTodoCompletionToggled` and `TodosOverviewTodoDeleted` are added to\nthe bloc via `context.read`.\n\n:::\n\n`view.dart` is the barrel file that exports `todos_overview_page.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Widgets\n\n`widgets.dart` is another barrel file that exports all the components used\nwithin the `todos_overview` feature.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart` is the `ListTile` for each todo item.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart` exposes two options for manipulating todos:\n\n- `toggleAll`\n- `clearCompleted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart` exposes three filter options:\n\n- `all`\n- `activeOnly`\n- `completedOnly`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### Stats\n\nThe stats feature displays statistics about the active and completed todos.\n\n#### StatsState\n\n`StatsState` keeps track of summary information and the current `StatsStatus`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### StatsEvent\n\n`StatsEvent` has only one event called `StatsSubscriptionRequested`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### StatsBloc\n\n`StatsBloc` depends on the `TodosRepository` just like `TodosOverviewBloc`. It\nsubscribes to the todos stream via `_todosRepository.getTodos`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### Stats View\n\n`view.dart` is the barrel file for the `stats_page`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart` contains the UI for the page that displays the todos\nstatistics.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\nA simplified representation of the widget tree for the `StatsPage` is:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\nThe `TodosOverviewBloc` and `StatsBloc` both communicate with the\n`TodosRepository`, but it is important to note there is no direct communication\nbetween the blocs. See the [data flow](#data-flow) section for more information.\n\n:::\n\n### EditTodo\n\nThe `EditTodo` feature allows users to edit an existing todo item and save the\nchanges.\n\n#### EditTodoState\n\n`EditTodoState` keeps track of the information needed when editing a todo.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### EditTodoEvent\n\nThe different events the bloc will react to are:\n\n- `EditTodoTitleChanged`\n- `EditTodoDescriptionChanged`\n- `EditTodoSubmitted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc` depends on the `TodosRepository`, just like `TodosOverviewBloc`\nand `StatsBloc`.\n\n:::caution\n\nUnlike the other Blocs, `EditTodoBloc` does not subscribe to\n`_todosRepository.getTodos`. It is a \"write-only\" bloc meaning it doesn't need\nto read any information from the repository.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### Data Flow\n\nEven though there are many features that depend on the same list of todos, there\nis no bloc-to-bloc communication. Instead, all features are independent of each\nother and rely on the `TodosRepository` to listen for changes in the list of\ntodos, as well as perform updates to the list.\n\nFor example, the `EditTodos` doesn't know anything about the `TodosOverview` or\n`Stats` features.\n\nWhen the UI submits a `EditTodoSubmitted` event:\n\n- `EditTodoBloc` handles the business logic to update the `TodosRepository`.\n- `TodosRepository` notifies `TodosOverviewBloc` and `StatsBloc`.\n- `TodosOverviewBloc` and `StatsBloc` notify the UI which update with the new\n  state.\n\n#### EditTodoPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\nJust like with the previous features, the `EditTodosPage` provides an instance\nof the `EditTodosBloc` via `BlocProvider`. Unlike the other features, the\n`EditTodosPage` is a separate route which is why it exposes a `static` `route`\nmethod. This makes it easy to push the `EditTodosPage` onto the navigation stack\nvia `Navigator.of(context).push(...)`.\n\nA simplified representation of the widget tree for the `EditTodosPage` is:\n\n<EditTodosPageTreeSnippet />\n\n## Summary\n\nThat's it, we have completed the tutorial! 🎉\n\nThe full source code for this example, including unit and widget tests, can be\nfound\n[here](https://github.com/felangel/bloc/tree/master/examples/flutter_todos).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: Flutter Weather\ndescription: An in-depth guide on how to build a Flutter weather app with bloc.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn this tutorial, we're going to build a Weather app in Flutter which\ndemonstrates how to manage multiple cubits to implement dynamic theming,\npull-to-refresh, and much more. Our weather app will pull live weather data from\nthe public OpenMeteo API and demonstrate how to separate our application into\nlayers (data, repository, business logic, and presentation).\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## Project Requirements\n\nOur app should let users\n\n- Search for a city on a dedicated search page\n- See a pleasant depiction of the weather data returned by\n  [Open Meteo API](https://open-meteo.com)\n- Change the units displayed (metric vs imperial)\n\nAdditionally,\n\n- The theme of the application should reflect the weather for the chosen city\n- Application state should persist across sessions: i.e., the app should\n  remember its state after closing and reopening it (using\n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc))\n\n## Key Concepts\n\n- Observe state changes with [BlocObserver](/bloc-concepts#blocobserver).\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles\n  building the widget in response to new states.\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- [RepositoryProvider](/flutter-bloc-concepts#repositoryprovider), a Flutter\n  widget which provides a repository to its children.\n- [BlocListener](/flutter-bloc-concepts#bloclistener), a Flutter widget which\n  invokes the listener code in response to state changes in the bloc.\n- [MultiBlocProvider](/flutter-bloc-concepts#multiblocprovider), a Flutter\n  widget that merges multiple BlocProvider widgets into one\n- [BlocConsumer](/flutter-bloc-concepts#blocconsumer), a Flutter widget that\n  exposes a builder and listener in order to react to new states\n- [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  to manage and persist state\n\n## Setup\n\nTo begin, create a new flutter project\n\n<FlutterCreateSnippet />\n\n### Project Structure\n\nOur app will consist of isolated features in corresponding directories. This\nenables us to scale as the number of features increases and allows developers to\nwork on different features in parallel.\n\nOur app can be broken down into four main features: **search, settings, theme,\nweather**. Let's create those directories.\n\n<FeatureTreeSnippet />\n\n### Architecture\n\nFollowing the [bloc architecture](/architecture) guidelines, our application\nwill consist of several layers.\n\nIn this tutorial, here's what these layers will do:\n\n- **Data**: retrieve raw weather data from the API\n- **Repository**: abstract the data layer and expose domain models for the\n  application to consume\n- **Business Logic**: manage the state of each feature (unit information, city\n  details, themes, etc.)\n- **Presentation**: display weather information and collect input from users\n  (settings page, search page etc.)\n\n## Data Layer\n\nFor this application we'll be hitting the\n[Open Meteo API](https://open-meteo.com).\n\nWe'll be focusing on two endpoints:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` to get a\n  location for a given city name\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true`\n  to get the weather for a given location\n\nOpen\n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)\nin your browser to see the response for the city of Chicago. We will use the\n`latitude` and `longitude` in the response to hit the weather endpoint.\n\nThe `latitude`/`longitutde` for Chicago is `41.85003`/`-87.65005`. Navigate to\n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)\nin your browser and you'll see the response for weather in Chicago which\ncontains all the data we will need for our app.\n\n### OpenMeteo API Client\n\nThe OpenMeteo API Client is independent of our application. As a result, we will\ncreate it as an internal package (and could even publish it on\n[pub.dev](https://pub.dev)). We can then use the package by adding it to the\n`pubspec.yaml` for the repository layer, which will handle data requests for our\nmain weather application.\n\nCreate a new directory on the project level called `packages`. This directory\nwill store all of our internal packages.\n\nWithin this directory, run the built-in `flutter create` command to create a new\npackage called `open_meteo_api` for our API client.\n\n<FlutterCreateApiClientSnippet />\n\n### Weather Data Model\n\nNext, let's create `location.dart` and `weather.dart` which will contain the\nmodels for the `location` and `weather` API endpoint responses.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### Location Model\n\nThe `location.dart` model should store data returned by the location API, which\nlooks like the following:\n\n<LocationJsonSnippet />\n\nHere's the in-progress `location.dart` file which stores the above response:\n\n<LocationDartSnippet />\n\n#### Weather Model\n\nNext, let's work on `weather.dart`. Our weather model should store data returned\nby the weather API, which looks like the following:\n\n<WeatherJsonSnippet />\n\nHere's the in-progress `weather.dart` file which stores the above response:\n\n<WeatherDartSnippet />\n\n### Barrel Files\n\nWhile we're here, let's quickly create a\n[barrel file](https://adrianfaciu.dev/posts/barrel-files/) to clean up some of\nour imports down the road.\n\nCreate a `models.dart` barrel file and export the two models:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\nLet's also create a package level barrel file, `open_meteo_api.dart`\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\nIn the top level, `open_meteo_api.dart` let's export the models:\n\n<OpenMeteoLibrarySnippet />\n\n### Setup\n\nWe need to be able to\n[serialize and deserialize](https://en.wikipedia.org/wiki/Serialization) our\nmodels in order to work with the API data. To do this, we will add `toJson` and\n`fromJson` methods to our models.\n\nAdditionally, we need a way to\n[make HTTP network requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)\nto fetch data from an API. Fortunately, there are a number of popular packages\nfor doing just that.\n\nWe will be using the\n[json_annotation](https://pub.dev/packages/json_annotation),\n[json_serializable](https://pub.dev/packages/json_serializable), and\n[build_runner](https://pub.dev/packages/build_runner) packages to generate the\n`toJson` and `fromJson` implementations for us.\n\nIn a later step, we will also use the [http](https://pub.dev/packages/http)\npackage to send network requests to the weather API so our application can\ndisplay the current weather data.\n\nLet's add these dependencies to the `pubspec.yaml`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\nRemember to run `flutter pub get` after adding the dependencies.\n\n:::\n\n### (De)Serialization\n\nIn order for code generation to work, we need to annotate our code using the\nfollowing:\n\n- `@JsonSerializable` to label classes which can be serialized\n- `@JsonKey` to provide string representations of field names\n- `@JsonValue` to provide string representations of field values\n- Implement `JSONConverter` to convert object representations into JSON\n  representations\n\nFor each file we also need to:\n\n- Import `json_annotation`\n- Include the generated code using the\n  [part](https://dart.dev/tools/pub/create-packages#organizing-a-package)\n  keyword\n- Include `fromJson` methods for deserialization\n\n#### Location Model\n\nHere is our complete `location.dart` model file:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### Weather Model\n\nHere is our complete `weather.dart` model file:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### Create Build File\n\nIn the `open_meteo_api` folder, create a `build.yaml` file. The purpose of this\nfile is to handle discrepancies between naming conventions in the\n`json_serializable` field names.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### Code Generation\n\nLet's use `build_runner` to generate the code.\n\n<BuildRunnerBuildSnippet />\n\n`build_runner` should generate the `location.g.dart` and `weather.g.dart` files.\n\n### OpenMeteo API Client\n\nLet's create our API client in `open_meteo_api_client.dart` within the `src`\ndirectory. Our project structure should now look like this:\n\n<OpenMeteoApiClientTreeSnippet />\n\nWe can now use the [http](https://pub.dev/packages/http) package we added\nearlier to the `pubspec.yaml` file to make HTTP requests to the weather API and\nuse this information in our application.\n\nOur API client will expose two methods:\n\n- `locationSearch` which returns a `Future<Location>`\n- `getWeather` which returns a `Future<Weather>`\n\n#### Location Search\n\nThe `locationSearch` method hits the location API and throws\n`LocationRequestFailure` errors as applicable. The completed method looks as\nfollows:\n\n<LocationSearchMethodSnippet />\n\n#### Get Weather\n\nSimilarly, the `getWeather` method hits the weather API and throws\n`WeatherRequestFailure` errors as applicable. The completed method looks as\nfollows:\n\n<GetWeatherMethodSnippet />\n\nThe completed file looks like this:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### Barrel File Updates\n\nLet's wrap up this package by adding our API client to the barrel file.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### Unit Tests\n\nIt's especially important to write unit tests for the data layer since it's the\nfoundation of our application. Unit tests will give us confidence that the\npackage behaves as expected.\n\n#### Setup\n\nEarlier, we added the [test](https://pub.dev/packages/test) package to our\npubspec.yaml which allows to easily write unit tests.\n\nWe will be creating a test file for the api client as well as the two models.\n\n#### Location Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### Weather Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### API Client Tests\n\nNext, let's test our API client. We should test to ensure that our API client\nhandles both API calls correctly, including edge cases.\n\n:::note\n\nWe don't want our tests to make real API calls since our goal is to test the API\nclient logic (including all edge cases) and not the API itself. In order to have\na consistent, controlled test environment, we will use\n[mocktail](https://github.com/felangel/mocktail) (which we added to the\npubspec.yaml file earlier) to mock the `http` client.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### Test Coverage\n\nFinally, let's gather test coverage to verify that we've covered each line of\ncode with at least one test case.\n\n<FlutterTestCoverageSnippet />\n\n## Repository Layer\n\nThe goal of our repository layer is to abstract our data layer and facilitate\ncommunication with the bloc layer. In doing this, the rest of our code base\ndepends only on functions exposed by our repository layer instead of specific\ndata provider implementations. This allows us to change data providers without\ndisrupting any of the application-level code. For example, if we decide to\nmigrate away from this particular weather API, we should be able to create a new\nAPI client and swap it out without having to make changes to the public API of\nthe repository or application layers.\n\n### Setup\n\nInside the packages directory, run the following command:\n\n<FlutterCreateRepositorySnippet />\n\nWe will use the same packages as in the `open_meteo_api` package including the\n`open_meteo_api` package from the last step. Update your `pubspec.yaml` and run\n`flutter pub get`.\n\n:::note\n\nWe're using a `path` to specify the location of the `open_meteo_api` which\nallows us to treat it just like an external package from `pub.dev`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### Weather Repository Models\n\nWe will be creating a new `weather.dart` file to expose a domain-specific\nweather model. This model will contain only data relevant to our business cases\n-- in other words it should be completely decoupled from the API client and raw\ndata format. As usual, we will also create a `models.dart` barrel file.\n\n<RepositoryModelsBarrelTreeSnippet />\n\nThis time, our weather model will only store the\n`location, temperature, condition` properties. We will also continue to annotate\nour code to allow for serialization and deserialization.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\nUpdate the barrel file we created previously to include the models.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### Create Build File\n\nAs before, we need to create a `build.yaml` file with the following contents:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### Code Generation\n\nAs we have done previously, run the following command to generate the\n(de)serialization implementation.\n\n<BuildRunnerBuildSnippet />\n\n#### Barrel File\n\nLet's also create a package-level barrel file named\n`packages/weather_repository/lib/weather_repository.dart` to export our models:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\nThe main goal of the `WeatherRepository` is to provide an interface which\nabstracts the data provider. In this case, the `WeatherRepository` will have a\ndependency on the `WeatherApiClient` and expose a single public method,\n`getWeather(String city)`.\n\n:::note\n\nConsumers of the `WeatherRepository` are not privy to the underlying\nimplementation details such as the fact that two network requests are made to\nthe weather API. The goal of the `WeatherRepository` is to separate the \"what\"\nfrom the \"how\" -- in other words, we want to have a way to fetch weather for a\ngiven city, but don't care about how or where that data is coming from.\n\n:::\n\n#### Setup\n\nLet's create the `weather_repository.dart` file within the `src` directory of\nour package and work on the repository implementation.\n\nThe main method we will focus on is `getWeather(String city)`. We can implement\nit using two calls to the API client as follows:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### Barrel File\n\nUpdate the barrel file we created previously.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### Unit Tests\n\nJust as with the data layer, it's critical to test the repository layer in order\nto make sure the domain level logic is correct. To test our `WeatherRepository`,\nwe will use the [mocktail](https://github.com/felangel/mocktail) library. We\nwill mock the underlying api client in order to unit test the\n`WeatherRepository` logic in an isolated, controlled environment.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## Business Logic Layer\n\nIn the business logic layer, we will be consuming the weather domain model from\nthe `WeatherRepository` and exposing a feature-level model which will be\nsurfaced to the user via the UI.\n\n:::note\n\nThis is the third different type of weather model we're implementing. In the API\nclient, our weather model contained all the info returned by the API. In the\nrepository layer, our weather model contained only the abstracted model based on\nour business case. In this layer, our weather model will contain relevant\ninformation needed specifically for the current feature set.\n\n:::\n\n### Setup\n\nBecause our business logic layer resides in our main app, we need to edit the\n`pubspec.yaml` for the entire `flutter_weather` project and include all the\npackages we'll be using.\n\n- Using [equatable](https://pub.dev/packages/equatable) enables our app's state\n  class instances to be compared using the equals `==` operator. Under the hood,\n  bloc will compare our states to see if they're equal, and if they're not, it\n  will trigger a rebuild. This guarantees that our widget tree will only rebuild\n  when necessary to keep performance fast and responsive.\n- We can spice up our user interface with\n  [google_fonts](https://pub.dev/packages/google_fonts).\n- [HydratedBloc](https://pub.dev/packages/hydrated_bloc) allows us to persist\n  application state when the app is closed and reopened.\n- We'll include the `weather_repository` package we just created to allow us to\n  fetch the current weather data!\n\nFor testing, we'll want to include the usual `test` package, along with\n`mocktail` for mocking dependencies and\n[bloc_test](https://pub.dev/packages/bloc_test), to enable easy testing of\nbusiness logic units, or blocs!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nNext, we will be working on the application layer within the `weather` feature\ndirectory.\n\n### Weather Model\n\nThe goal of our weather model is to keep track of weather data displayed by our\napp, as well as temperature settings (Celsius or Fahrenheit).\n\nCreate `flutter_weather/lib/weather/models/weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### Create Build File\n\nCreate a `build.yaml` file for the business logic layer.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### Code Generation\n\nRun `build_runner` to generate the (de)serialization implementations.\n\n<BuildRunnerBuildSnippet />\n\n### Barrel File\n\nLet's export our models from the barrel file\n(`flutter_weather/lib/weather/models/models.dart`):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\nThen, let's create a top-level weather barrel file\n(`flutter_weather/lib/weather/weather.dart`);\n\n<WeatherBarrelDartSnippet />\n\n### Weather\n\nWe will use `HydratedCubit` to enable our app to remember its application state,\neven after it's been closed and reopened.\n\n:::note\n\n`HydratedCubit` is an extension of `Cubit` which handles persisting and\nrestoring state across sessions.\n\n:::\n\n#### Weather State\n\nUsing the\n[Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nor [Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) extension,\nright click on the `weather` directory and create a new cubit called `Weather`.\nThe project structure should look like this:\n\n<WeatherCubitTreeSnippet />\n\nThere are four states our weather app can be in:\n\n- `initial` before anything loads\n- `loading` during the API call\n- `success` if the API call is successful\n- `failure` if the API call is unsuccessful\n\nThe `WeatherStatus` enum will represent the above.\n\nThe complete weather state should look like this:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\nNow that we've defined the `WeatherState`, let's write the `WeatherCubit` which\nwill expose the following methods:\n\n- `fetchWeather(String? city)` uses our weather repository to try and retrieve a\n  weather object for the given city\n- `refreshWeather()` retrieves a new weather object using the weather repository\n  given the current weather state\n- `toggleUnits()` toggles the state between Celsius and Fahrenheit\n- `fromJson(Map<String, dynamic> json)`, `toJson(WeatherState state)` used for\n  persistence\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\nRemember to generate the (de)serialization code via:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### Unit Tests\n\nSimilar to the data and repository layers, it's critical to unit test the\nbusiness logic layer to ensure that the feature-level logic behaves as we\nexpect. We will be relying on the\n[bloc_test](https://pub.dev/packages/bloc_test) in addition to `mocktail` and\n`test`.\n\nLet's add the `test`, `bloc_test`, and `mocktail` packages to the\n`dev_dependencies`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nThe [bloc_test](https://pub.dev/packages/bloc_test) package allows us to easily\nprepare our blocs for testing, handle state changes, and check results in a\nconsistent way.\n\n:::\n\n#### Weather Cubit Tests\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## Presentation Layer\n\n### Weather Page\n\nWe will start with the `WeatherPage` which uses `BlocProvider` in order to\nprovide an instance of the `WeatherCubit` to the widget tree.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\nYou'll notice that page depends on `SettingsPage` and `SearchPage` widgets,\nwhich we will create next.\n\n### SettingsPage\n\nThe settings page allows users to update their preferences for the temperature\nunits.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### SearchPage\n\nThe search page allows users to enter the name of their desired city and\nprovides the search result to the previous route via\n`Navigator.of(context).pop`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Weather Widgets\n\nThe app will display different screens depending on the four possible states of\nthe `WeatherCubit`.\n\n#### WeatherEmpty\n\nThis screen will show when there is no data to display because the user has not\nyet selected a city.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### WeatherError\n\nThis screen will display if there is an error.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### WeatherLoading\n\nThis screen will display as the application fetches the data.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### WeatherPopulated\n\nThis screen will display after the user has selected a city and we have\nretrieved the data.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### Barrel File\n\nLet's add these states to a barrel file to clean up our imports.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### Entrypoint\n\nOur `main.dart` file should initialize our `WeatherApp` and `BlocObserver` (for\ndebugging purposes), as well as setup our `HydratedStorage` to persist state\nacross sessions.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nOur `app.dart` widget will handle building the `WeatherPage` view we previously\ncreated and use `BlocProvider` to inject our `WeatherCubit`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### Widget Tests\n\nThe [`bloc_test`](https://pub.dev/packages/bloc_test) library also exposes\n`MockBlocs` and `MockCubits` which make it easy to test UI. We can mock the\nstates of the various cubits and ensure that the UI reacts correctly.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\nWe're using a `MockWeatherCubit` together with the `when` API from `mocktail` in\norder to stub the state of the cubit in each of the test cases. This allows us\nto simulate all states and verify the UI behaves correctly under all\ncircumstances.\n\n:::\n\n## Summary\n\nThat's it, we have completed the tutorial! 🎉\n\nWe can run the final app using the `flutter run` command.\n\nThe full source code for this example, including unit and widget tests, can be\nfound\n[here](https://github.com/felangel/bloc/tree/master/examples/flutter_weather).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/github-search.mdx",
    "content": "---\ntitle: GitHub Search\ndescription:\n  An in-depth guide on how to build GitHub Search app in Flutter and AngularDart\n  with bloc.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nIn the following tutorial, we're going to build a GitHub Search app in Flutter\nand AngularDart to demonstrate how we can share the data and business logic\nlayers between the two projects.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## Key Topics\n\n- [BlocProvider](/flutter-bloc-concepts#blocprovider), Flutter widget which\n  provides a bloc to its children.\n- [BlocBuilder](/flutter-bloc-concepts#blocbuilder), Flutter widget that handles\n  building the widget in response to new states.\n- Using Cubit instead of Bloc.\n  [What's the difference?](/bloc-concepts#cubit-vs-bloc)\n- Prevent unnecessary rebuilds with [Equatable](/faqs#when-to-use-equatable).\n- Use a custom `EventTransformer` with\n  [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency).\n- Making network requests using the `http` package.\n\n## Common GitHub Search Library\n\nThe Common GitHub Search library will contain models, the data provider, the\nrepository, as well as the bloc that will be shared between AngularDart and\nFlutter.\n\n### Setup\n\nWe'll start off by creating a new directory for our application.\n\n<SetupSnippet />\n\n:::note\n\nThe `common_github_search` directory will contain the shared library.\n\n:::\n\nWe need to create a `pubspec.yaml` with the required dependencies.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\nLastly, we need to install our dependencies.\n\n<DartPubGetSnippet />\n\nThat's it for the project setup! Now we can get to work on building out the\n`common_github_search` package.\n\n### Github Client\n\nThe `GithubClient` which will be providing raw data from the\n[GitHub API](https://developer.github.com/v3/).\n\n:::note\n\nYou can see a sample of what the data we get back will look like\n[here](https://api.github.com/search/repositories?q=dartlang).\n\n:::\n\nLet's create `github_client.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\nOur `GithubClient` is simply making a network request to Github's Repository\nSearch API and converting the result into either a `SearchResult` or\n`SearchResultError` as a `Future`.\n\n:::\n\n:::note\n\nThe `GithubClient` implementation depends on `SearchResult.fromJson`, which we\nhave not yet implemented.\n\n:::\n\nNext we need to define our `SearchResult` and `SearchResultError` models.\n\n#### Search Result Model\n\nCreate `search_result.dart`, which represents a list of `SearchResultItems`\nbased on the user's query:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\nThe `SearchResult` implementation depends on `SearchResultItem.fromJson`, which\nwe have not yet implemented.\n\n:::\n\n:::note\n\nWe aren't including properties that aren't going to be used in our model.\n\n:::\n\n#### Search Result Item Model\n\nNext, we'll create `search_result_item.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\nAgain, the `SearchResultItem` implementation dependes on `GithubUser.fromJson`,\nwhich we have not yet implemented.\n\n:::\n\n#### GitHub User Model\n\nNext, we'll create `github_user.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\nAt this point, we have finished implementing `SearchResult` and its\ndependencies. Now we'll move onto `SearchResultError`.\n\n#### Search Result Error Model\n\nCreate `search_result_error.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\nOur `GithubClient` is finished so next we'll move onto the `GithubCache`, which\nwill be responsible for [memoizing](https://en.wikipedia.org/wiki/Memoization)\nas a performance optimization.\n\n### GitHub Cache\n\nOur `GithubCache` will be responsible for remembering all past queries so that\nwe can avoid making unnecessary network requests to the GitHub API. This will\nalso help improve our application's performance.\n\nCreate `github_cache.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\nNow we're ready to create our `GithubRepository`!\n\n### GitHub Repository\n\nThe Github Repository is responsible for creating an abstraction between the\ndata layer (`GithubClient`) and the Business Logic Layer (`Bloc`). This is also\nwhere we're going to put our `GithubCache` to use.\n\nCreate `github_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\nThe `GithubRepository` has a dependency on the `GithubCache` and the\n`GithubClient` and abstracts the underlying implementation. Our application\nnever has to know about how the data is being retrieved or where it's coming\nfrom since it shouldn't care. We can change how the repository works at any time\nand as long as we don't change the interface we shouldn't need to change any\nclient code.\n\n:::\n\nAt this point, we've completed the data provider layer and the repository layer\nso we're ready to move on to the business logic layer.\n\n### GitHub Search Event\n\nOur Bloc will be notified when a user has typed the name of a repository which\nwe will represent as a `TextChanged` `GithubSearchEvent`.\n\nCreate `github_search_event.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\nWe extend [`Equatable`](https://pub.dev/packages/equatable) so that we can\ncompare instances of `GithubSearchEvent`. By default, the equality operator\nreturns true if and only if this and other are the same instance.\n\n:::\n\n### Github Search State\n\nOur presentation layer will need to have several pieces of information in order\nto properly lay itself out:\n\n- `SearchStateEmpty`- will tell the presentation layer that no input has been\n  given by the user.\n\n- `SearchStateLoading`- will tell the presentation layer it has to display some\n  sort of loading indicator.\n\n- `SearchStateSuccess`- will tell the presentation layer that it has data to\n  present.\n\n  - `items`- will be the `List<SearchResultItem>` which will be displayed.\n\n- `SearchStateError`- will tell the presentation layer that an error has\n  occurred while fetching repositories.\n\n  - `error`- will be the exact error that occurred.\n\nWe can now create `github_search_state.dart` and implement it like so.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\nWe extend [`Equatable`](https://pub.dev/packages/equatable) so that we can\ncompare instances of `GithubSearchState`. By default, the equality operator\nreturns true if and only if this and other are the same instance.\n\n:::\n\nNow that we have our Events and States implemented, we can create our\n`GithubSearchBloc`.\n\n### GitHub Search Bloc\n\nCreate `github_search_bloc.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\nOur `GithubSearchBloc` converts `GithubSearchEvent` to `GithubSearchState` and\nhas a dependency on the `GithubRepository`.\n\n:::\n\n:::note\n\nWe create a custom `EventTransformer` to\n[debounce](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)\nthe `GithubSearchEvents`. One of the reasons why we created a `Bloc` instead of\na `Cubit` was to take advantage of stream transformers.\n\n:::\n\nAwesome! We're all done with our `common_github_search` package. The finished\nproduct should look like\n[this](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search).\n\nNext, we'll work on the Flutter implementation.\n\n## Flutter GitHub Search\n\nFlutter Github Search will be a Flutter application which reuses the models,\ndata providers, repositories, and blocs from `common_github_search` to implement\nGithub Search.\n\n### Setup\n\nWe need to start by creating a new Flutter project in our `github_search`\ndirectory at the same level as `common_github_search`.\n\n<FlutterCreateSnippet />\n\nNext, we need to update our `pubspec.yaml` to include all the necessary\ndependencies.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\nWe are including our newly created `common_github_search` library as a\ndependency.\n\n:::\n\nNow, we need to install the dependencies.\n\n<FlutterPubGetSnippet />\n\nThat's it for project setup. Since the `common_github_search` package contains\nour data layer as well as our business logic layer, all we need to build is the\npresentation layer.\n\n### Search Form\n\nWe're going to need to create a form with a `_SearchBar` and `_SearchBody`\nwidget.\n\n- `_SearchBar` will be responsible for taking user input.\n- `_SearchBody` will be responsible for displaying search results, loading\n  indicators, and errors.\n\nLet's create `search_form.dart`.\n\nOur `SearchForm` will be a `StatelessWidget` which renders the `_SearchBar` and\n`_SearchBody` widgets.\n\n`_SearchBar` is also going to be a `StatefulWidget` because it will need to\nmaintain its own `TextEditingController` so that we can keep track of what a\nuser has entered as input.\n\n`_SearchBody` is a `StatelessWidget` which will be responsible for displaying\nsearch results, errors, and loading indicators. It will be the consumer of the\n`GithubSearchBloc`.\n\nIf our state is `SearchStateSuccess`, we render `_SearchResults` which we will\nimplement next.\n\n`_SearchResults` is a `StatelessWidget` which takes a `List<SearchResultItem>`\nand displays them as a list of `_SearchResultItems`.\n\n`_SearchResultItem` is a `StatelessWidget` and is responsible for rendering the\ninformation for a single search result. It is also responsible for handling user\ninteraction and navigating to the repository url on a user tap.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar` accesses `GitHubSearchBloc` via `context.read<GithubSearchBloc>()`\nand notifies the bloc of `TextChanged` events.\n\n:::\n\n:::note\n\n`_SearchBody` uses `BlocBuilder` in order to rebuild in response to state\nchanges. Since the bloc parameter of the `BlocBuilder` object was omitted,\n`BlocBuilder` will automatically perform a lookup using `BlocProvider` and the\ncurrent `BuildContext`. Read more [here.](/flutter-bloc-concepts#blocbuilder)\n:::\n\n:::note\n\nWe use `ListView.builder` in order to construct a scrollable list of\n`_SearchResultItem`.\n\n:::\n\n:::note\n\nWe use the [url_launcher](https://pub.dev/packages/url_launcher) package to open\nexternal urls.\n\n:::\n\n### Putting it all together\n\nNow all that's left to do is implement our main app in `main.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\nOur `GithubRepository` is created in `main` and injected into our `App`. Our\n`SearchForm` is wrapped in a `BlocProvider` which is responsible for\ninitializing, closing, and making the instance of `GithubSearchBloc` available\nto the `SearchForm` widget and its children.\n\n:::\n\nThat's all there is to it! We've now successfully implemented a GitHub search\napp in Flutter using the [bloc](https://pub.dev/packages/bloc) and\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) packages and we've\nsuccessfully separated our presentation layer from our business logic.\n\nThe full source can be found\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search).\n\nFinally, we're going to build our AngularDart GitHub Search app.\n\n## AngularDart GitHub Search\n\nAngularDart GitHub Search will be an AngularDart application which reuses the\nmodels, data providers, repositories, and blocs from `common_github_search` to\nimplement Github Search.\n\n### Setup\n\nWe need to start by creating a new AngularDart project in our github_search\ndirectory at the same level as `common_github_search`.\n\n<StagehandSnippet />\n\n:::note\n\nYou can install `stagehand` via:\n\n<ActivateStagehandSnippet />\n:::\n\nWe can then go ahead and replace the contents of `pubspec.yaml` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### Search Form\n\nJust like in our Flutter app, we're going to need to create a `SearchForm` with\na `SearchBar` and `SearchBody` component.\n\nOur `SearchForm` component will implement `OnInit` and `OnDestroy` because it\nwill need to create and close a `GithubSearchBloc`.\n\n- `SearchBar` will be responsible for taking user input.\n- `SearchBody` will be responsible for displaying search results, loading\n  indicators, and errors.\n\nLet's create `search_form_component.dart.`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\nThe `GithubRepository` is injected into the `SearchFormComponent`.\n\n:::\n\n:::note\n\nThe `GithubSearchBloc` is created and closed by the `SearchFormComponent`.\n\n:::\n\nOur template (`search_form_component.html`) will look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\nNext, we'll implement the `SearchBar` component.\n\n### Search Bar\n\n`SearchBar` is a component which will be responsible for taking in user input\nand notifying the `GithubSearchBloc` of text changes.\n\nCreate `search_bar_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent` has a dependency on `GitHubSearchBloc` because it is\nresponsible for notifying the bloc of `TextChanged` events.\n\n:::\n\nNext, we can create `search_bar_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\nWe're done with `SearchBar`, now onto `SearchBody`.\n\n### Search Body\n\n`SearchBody` is a component which will be responsible for displaying search\nresults, errors, and loading indicators. It will be the consumer of the\n`GithubSearchBloc`.\n\nCreate `search_body_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent` has a dependency on `GithubSearchState` which is provided\nby the `GithubSearchBloc` using the `angular_bloc` bloc pipe.\n\n:::\n\nCreate `search_body_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\nIf our state `isSuccess`, we render `SearchResults`. We will implement it next.\n\n### Search Results\n\n`SearchResults` is a component which takes a `List<SearchResultItem>` and\ndisplays them as a list of `SearchResultItems`.\n\nCreate `search_results_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\nNext up we'll create `search_results_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\nWe use `ngFor` in order to construct a list of `SearchResultItem` components.\n:::\n\nIt's time to implement `SearchResultItem`.\n\n### Search Result Item\n\n`SearchResultItem` is a component that is responsible for rendering the\ninformation for a single search result. It is also responsible for handling user\ninteraction and navigating to the repository url on a user tap.\n\nCreate `search_result_item_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\nand the corresponding template in `search_result_item_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### Putting it all together\n\nWe have all of our components and now it's time to put them all together in our\n`app_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\nWe're creating the `GithubRepository` in the `AppComponent` and injecting it\ninto the `SearchForm` component.\n\n:::\n\nThat's all there is to it! We've now successfully implemented a GitHub search\napp in AngularDart using the `bloc` and `angular_bloc` packages and we've\nsuccessfully separated our presentation layer from our business logic.\n\nThe full source can be found\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search).\n\n## Summary\n\nIn this tutorial we created a Flutter and AngularDart app while sharing all of\nthe models, data providers, and blocs between the two.\n\nThe only thing we actually had to write twice was the presentation layer (UI)\nwhich is awesome in terms of efficiency and development speed. In addition, it's\nfairly common for web apps and mobile apps to have different user experiences\nand styles and this approach really demonstrates how easy it is to build two\napps that look totally different but share the same data and business logic\nlayers.\n\nThe full source can be found\n[here](https://github.com/felangel/bloc/tree/master/examples/github_search).\n"
  },
  {
    "path": "docs/src/content/docs/tutorials/ngdart-counter.mdx",
    "content": "---\ntitle: AngularDart Counter\ndescription:\n  An in-depth guide on how to build an AngularDart counter app with bloc.\nsidebar:\n  order: 8\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/ngdart-counter/StagehandSnippet.astro';\nimport InstallDependenciesSnippet from '~/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nIn the following tutorial, we're going to build a Counter in AngularDart using\nthe Bloc library.\n\n![demo](~/assets/tutorials/ngdart-counter.gif)\n\n## Setup\n\nWe'll start off by creating a brand new AngularDart project with\n[stagehand](https://github.com/dart-lang/stagehand).\n\nIf you don't have stagehand installed, activate it via:\n\n<ActivateStagehandSnippet />\n\nThen generate a new project via:\n\n<StagehandSnippet />\n\nWe can then go ahead and replace the contents of `pubspec.yaml` with:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nand then install all of our dependencies\n\n<InstallDependenciesSnippet />\n\nOur counter app is just going to have two buttons to increment/decrement the\ncounter value and an element to display the current value. Let's get started\ndesigning the `CounterEvents`.\n\n## Counter Bloc\n\nSince our counter's state can be represented by an integer we don't need to\ncreate a custom class and we can co-locate the events and bloc.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_bloc.dart\"\n\ttitle=\"lib/src/counter_page/counter_bloc.dart\"\n/>\n\n:::note\n\nJust from the class declaration we can tell that our `CounterBloc` will be\ntaking `CounterEvents` as input and outputting integers.\n\n:::\n\n## Counter App\n\nNow that we have our `CounterBloc` fully implemented, we can get started\ncreating our AngularDart App Component.\n\nOur `app.component.dart` should look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.dart\"\n\ttitle=\"lib/app_component.dart\"\n/>\n\nand our `app.component.html` should look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.html\"\n\ttitle=\"lib/app_component.html\"\n/>\n\n## Counter Page\n\nFinally, all that's left is to build our Counter Page Component.\n\nOur `counter_page_component.dart` should look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.dart\"\n\ttitle=\"lib/src/counter_page/counter_page_component.dart\"\n/>\n\n:::note\n\nWe are able to access the `CounterBloc` instance using AngularDart's dependency\ninjection system. Because we have registered it as a `Provider`, AngularDart can\nproperly resolve `CounterBloc`.\n\n:::\n\n:::note\n\nWe are closing the `CounterBloc` in `ngOnDestroy`.\n\n:::\n\n:::note\n\nWe are importing the `BlocPipe` so that we can use it in our template.\n\n:::\n\nLastly, our `counter_page_component.html` should look like:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.html\"\n\ttitle=\"lib/src/counter_page/counter_page_component.html\"\n/>\n\n:::note\n\nWe are using the `BlocPipe` so that we can display our `CounterBloc` state as it\nis updated.\n\n:::\n\nThat's it! We've separated our presentation layer from our business logic layer.\nOur `CounterPageComponent` has no idea what happens when a user presses a\nbutton; it just adds an event to notify the `CounterBloc`. Furthermore, our\n`CounterBloc` has no idea what is happening with the state (counter value); it's\nsimply converting the `CounterEvents` into integers.\n\nWe can run our app with `webdev serve` and can view it locally.\n\nThe full source for this example can be found\n[here](https://github.com/felangel/bloc/tree/master/examples/angular_counter).\n"
  },
  {
    "path": "docs/src/content/docs/uk/architecture.mdx",
    "content": "---\ntitle: Архітектура\ndescription: Огляд рекомендованих архітектурних шаблонів при використанні bloc.\n---\n\nimport DataProviderSnippet from '~/components/architecture/DataProviderSnippet.astro';\nimport RepositorySnippet from '~/components/architecture/RepositorySnippet.astro';\nimport BusinessLogicComponentSnippet from '~/components/architecture/BusinessLogicComponentSnippet.astro';\nimport BlocTightCouplingSnippet from '~/components/architecture/BlocTightCouplingSnippet.astro';\nimport BlocLooseCouplingPresentationSnippet from '~/components/architecture/BlocLooseCouplingPresentationSnippet.astro';\nimport AppIdeasRepositorySnippet from '~/components/architecture/AppIdeasRepositorySnippet.astro';\nimport AppIdeaRankingBlocSnippet from '~/components/architecture/AppIdeaRankingBlocSnippet.astro';\nimport PresentationComponentSnippet from '~/components/architecture/PresentationComponentSnippet.astro';\n\n![Архітектура Bloc](~/assets/concepts/bloc_architecture_full.png)\n\nВикористання бібліотеки bloc дозволяє нам розділити наш додаток на три шари:\n\n- Представлення\n- Бізнес-логіка\n- Дані\n  - Сховище\n  - Постачальник даних\n\nМи почнемо з найнижчого шару (найбільш віддаленого від користувацького\nінтерфейсу) та рухатимемося вгору до шару представлення.\n\n## Шар даних\n\nВідповідальність шару даних полягає в отриманні/маніпулюванні даними з одного\nабо кількох джерел.\n\nШар даних можна розділити на дві частини:\n\n- Сховище\n- Постачальник даних\n\nЦей шар є найнижчим рівнем додатку та взаємодіє з базами даних, мережевими\nзапитами та іншими асинхронними джерелами даних.\n\n### Постачальник даних\n\nВідповідальність постачальника даних полягає в наданні необроблених даних.\nПостачальник даних повинен бути універсальним та багатофункціональним.\n\nПостачальник даних зазвичай надає прості API для виконання\n[CRUD](https://uk.wikipedia.org/wiki/CRUD) операцій. Ми можемо мати методи\n`createData`, `readData`, `updateData` та `deleteData` як частину нашого шару\nданих.\n\n<DataProviderSnippet />\n\n### Сховище\n\nШар сховища — це обгортка навколо одного або кількох постачальників даних, з\nякими спілкується шар Bloc.\n\n<RepositorySnippet />\n\nЯк ви можете бачити, наш шар сховища може взаємодіяти з кількома постачальниками\nданих та виконувати перетворення даних перед передачею результату на шар\nбізнес-логіки.\n\n## Шар бізнес-логіки\n\nВідповідальність шару бізнес-логіки полягає у відповіді на введення з шару\nпредставлення новими станами. Цей шар може залежати від одного або кількох\nсховищ для отримання даних, необхідних для побудови стану додатку.\n\nДумайте про шар бізнес-логіки як про міст між користувацьким інтерфейсом (шар\nпредставлення) та шаром даних. Шар бізнес-логіки сповіщається про події/дії з\nшару представлення, а потім взаємодіє зі сховищем, щоб побудувати новий стан для\nвикористання шаром представлення.\n\n<BusinessLogicComponentSnippet />\n\n### Взаємодія між блоками\n\nОскільки блоки надають потоки, може виникнути спокуса створити блок, який\nпрослуховує інший блок. Ви **не повинні** робити цього. Існують кращі\nальтернативи, ніж вдаватися до коду нижче:\n\n<BlocTightCouplingSnippet />\n\nХоча наведений вище код не містить помилок (і навіть очищується за собою), він\nмає більш серйозну проблему: він створює залежність між двома блоками.\n\nЯк правило, залежностей між двома сутностями на одному архітектурному шарі слід\nуникати за будь-яку ціну, оскільки це створює тісний зв'язок, який важко\nпідтримувати. Оскільки блоки знаходяться на архітектурному шарі бізнес-логіки,\nжоден блок не повинен знати про будь-який інший блок.\n\n![Шари архітектури додатку](~/assets/architecture/architecture.png)\n\nБлок повинен отримувати інформацію лише через події та з впроваджених сховищ\n(тобто сховищ, переданих блоку в його конструкторі).\n\nЯкщо ви перебуваєте в ситуації, коли блок повинен реагувати на інший блок, у вас\nє два інших варіанти. Ви можете перемістити проблему на шар вище (у шар\nпредставлення) або на шар нижче (у шар домену).\n\n#### З'єднання блоків через представлення\n\nВи можете використовувати `BlocListener` для прослуховування одного блоку та\nдодавання події до іншого блоку щоразу, коли перший блок змінюється.\n\n<BlocLooseCouplingPresentationSnippet />\n\nНаведений вище код запобігає необхідності `SecondBloc` знати про `FirstBloc`,\nзаохочуючи слабкий зв'язок. Додаток\n[flutter_weather](/uk/tutorials/flutter-weather)\n[використовує цю техніку](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42)\nдля зміни теми додатку на основі отриманої інформації про погоду.\n\nУ деяких ситуаціях ви можете не захотіти зв'язувати два блоки в шарі\nпредставлення. Замість цього часто має сенс, щоб два блоки використовували одне\nй те саме джерело даних та оновлювалися при зміні даних.\n\n#### З'єднання блоків через домен\n\nДва блоки можуть прослуховувати потік зі сховища та оновлювати свої стани\nнезалежно один від одного щоразу, коли змінюються дані сховища. Використання\nреактивних сховищ для синхронізації стану є поширеним у великомасштабних\nкорпоративних додатках.\n\nСпочатку створіть або використовуйте сховище, яке надає `Stream` даних.\nНаприклад, наступне сховище надає нескінченний потік тих самих кількох ідей\nдодатків:\n\n<AppIdeasRepositorySnippet />\n\nТе саме сховище може бути впроваджене в кожний блок, який повинен реагувати на\nнові ідеї додатків. Нижче наведено `AppIdeaRankingBloc`, який видає стан для\nкожної вхідної ідеї додатку зі сховища вище:\n\n<AppIdeaRankingBlocSnippet />\n\nДокладніше про використання потоків з Bloc див. у статті\n[Як використовувати Bloc з потоками та конкурентністю](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency).\n\n## Шар представлення\n\nВідповідальність шару представлення полягає у визначенні того, як відмалювати\nсебе на основі одного або кількох станів блоків. Крім того, він повинен\nобробляти введення користувача та події життєвого циклу додатку.\n\nБільшість потоків додатків починаються з події `AppStart`, яка запускає додаток\nдля отримання деяких даних для представлення користувачеві.\n\nУ цьому сценарії шар представлення додасть подію `AppStart`.\n\nКрім того, шар представлення повинен буде визначити, що відмалювати на екрані на\nоснові стану з шару bloc.\n\n<PresentationComponentSnippet />\n\nДо цього моменту, хоча у нас були деякі фрагменти коду, все це було досить\nвисокорівневим. У розділі посібників ми об'єднаємо все це разом, коли будемо\nстворювати кілька різних прикладів додатків.\n"
  },
  {
    "path": "docs/src/content/docs/uk/bloc-concepts.mdx",
    "content": "---\ntitle: Концепції Bloc\ndescription: Огляд основних концепцій для package:bloc.\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\nБудь ласка, уважно прочитайте наступні розділи перед початком роботи з\n[`package:bloc`](https://pub.dev/packages/bloc).\n\n:::\n\nІснує кілька ключових концепцій, які є критично важливими для розуміння того, як\nвикористовувати пакет bloc.\n\nУ наступних розділах ми детально обговоримо кожну з них, а також розглянемо, як\nвони застосовуються на прикладі додатку-лічильника.\n\n## Потоки (Streams)\n\n:::note\n\nОзнайомтеся з офіційною\n[документацією Dart](https://dart.dev/tutorials/language/streams) для отримання\nдодаткової інформації про `Streams`.\n\n:::\n\nПотік (stream) — це послідовність асинхронних даних.\n\nДля використання бібліотеки bloc критично важливо мати базове розуміння\n`Streams` та того, як вони працюють.\n\nЯкщо ви не знайомі з `Streams`, просто уявіть трубу з водою, що тече через неї.\nТруба — це `Stream`, а вода — це асинхронні дані.\n\nМи можемо створити `Stream` у Dart, написавши функцію `async*` (асинхронний\nгенератор).\n\n<CountStreamSnippet />\n\nПозначаючи функцію як `async*`, ми отримуємо можливість використовувати ключове\nслово `yield` та повертати `Stream` даних. У наведеному вище прикладі ми\nповертаємо `Stream` цілих чисел до значення параметра `max`.\n\nЩоразу, коли ми використовуємо `yield` у функції `async*`, ми проштовхуємо цей\nфрагмент даних через `Stream`.\n\nМи можемо використовувати вищевказаний `Stream` кількома способами. Якби ми\nхотіли написати функцію для повернення суми `Stream` цілих чисел, вона могла б\nвиглядати так:\n\n<SumStreamSnippet />\n\nПозначаючи вищевказану функцію як `async`, ми отримуємо можливість\nвикористовувати ключове слово `await` та повертати `Future` цілих чисел. У цьому\nприкладі ми очікуємо кожне значення в потоці та повертаємо суму всіх цілих чисел\nу потоці.\n\nМи можемо зібрати все це разом наступним чином:\n\n<StreamsMainSnippet />\n\nТепер, коли ми маємо базове розуміння того, як працюють `Streams` у Dart, ми\nготові дізнатися про основний компонент пакету bloc: `Cubit`.\n\n## Cubit\n\n`Cubit` — це клас, який розширює `BlocBase` та може бути розширений для\nкерування будь-яким типом стану.\n\n![Cubit Architecture](~/assets/concepts/cubit_architecture_full.png)\n\n`Cubit` може надавати функції, які можна викликати для ініціювання змін стану.\n\nСтани — це вихідні дані `Cubit` і представляють частину стану вашого додатку.\nКомпоненти UI можуть бути сповіщені про стани та перемальовувати частини себе на\nоснові поточного стану.\n\n:::note\n\nДля отримання додаткової інформації про походження `Cubit` ознайомтеся з\n[цим issue](https://github.com/felangel/cubit/issues/69).\n\n:::\n\n### Створення Cubit\n\nМи можемо створити `CounterCubit` наступним чином:\n\n<CounterCubitSnippet />\n\nПри створенні `Cubit` нам необхідно визначити тип стану, яким буде керувати\n`Cubit`. У випадку `CounterCubit` вище стан може бути представлений через `int`,\nале в більш складних випадках може бути необхідно використовувати `class`\nзамість примітивного типу.\n\nДруге, що нам потрібно зробити при створенні `Cubit`, — це вказати початковий\nстан. Ми можемо зробити це, викликавши `super` зі значенням початкового стану. У\nнаведеному вище фрагменті ми встановлюємо початковий стан у `0` внутрішньо, але\nми також можемо дозволити `Cubit` бути більш гнучким, приймаючи зовнішнє\nзначення:\n\n<CounterCubitInitialStateSnippet />\n\nЦе дозволило б нам створювати екземпляри `CounterCubit` з різними початковими\nстанами, наприклад:\n\n<CounterCubitInstantiationSnippet />\n\n### Зміни стану Cubit\n\nКожний `Cubit` має можливість видавати новий стан через `emit`.\n\n<CounterCubitIncrementSnippet />\n\nУ наведеному вище фрагменті `CounterCubit` надає публічний метод `increment`,\nякий може бути викликаний ззовні для сповіщення `CounterCubit` про збільшення\nйого стану. Коли викликається `increment`, ми можемо отримати доступ до\nпоточного стану `Cubit` через геттер `state` та викликати `emit` нового стану,\nдодавши 1 до поточного стану.\n\n:::caution\n\nМетод `emit` є захищеним, що означає, що він повинен використовуватися лише\nвсередині `Cubit`.\n\n:::\n\n### Використання Cubit\n\nТепер ми можемо взяти реалізований `CounterCubit` та використати його!\n\n#### Базове використання\n\n<CounterCubitBasicUsageSnippet />\n\nУ наведеному вище фрагменті ми починаємо зі створення екземпляра `CounterCubit`.\nПотім ми виводимо поточний стан cubit, який є початковим станом (оскільки нові\nстани ще не були видані). Далі ми викликаємо функцію `increment` для ініціювання\nзміни стану. Нарешті, ми знову виводимо стан `Cubit`, який змінився з `0` на\n`1`, та викликаємо `close` на `Cubit` для закриття внутрішнього потоку станів.\n\n#### Використання Stream\n\n`Cubit` надає `Stream`, який дозволяє нам отримувати оновлення стану в реальному\nчасі:\n\n<CounterCubitStreamUsageSnippet />\n\nУ наведеному вище фрагменті ми підписуємося на `CounterCubit` та викликаємо\nprint при кожній зміні стану. Потім ми викликаємо функцію `increment`, яка\nвидасть новий стан. Нарешті, ми викликаємо `cancel` на `subscription`, коли\nбільше не хочемо отримувати оновлення, та закриваємо `Cubit`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` додано для цього прикладу, щоб уникнути\nнегайного скасування підписки.\n\n:::\n\n:::caution\n\nЛише наступні зміни стану будуть отримані при виклику `listen` на `Cubit`.\n\n:::\n\n### Спостереження за Cubit\n\nКоли `Cubit` видає новий стан, відбувається `Change`. Ми можемо спостерігати за\nвсіма змінами для даного `Cubit`, перевизначивши `onChange`.\n\n<CounterCubitOnChangeSnippet />\n\nПотім ми можемо взаємодіяти з `Cubit` та спостерігати за всіма змінами, що\nвиводяться в консоль.\n\n<CounterCubitOnChangeUsageSnippet />\n\nНаведений вище приклад виведе:\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change` відбувається безпосередньо перед оновленням стану `Cubit`. `Change`\nскладається з `currentState` та `nextState`.\n\n:::\n\n#### BlocObserver\n\nОдним з додаткових переваг використання бібліотеки bloc є те, що ми можемо мати\nдоступ до всіх `Changes` в одному місці. Хоча в цьому додатку у нас є лише один\n`Cubit`, у більших додатках досить часто зустрічається багато `Cubits`, що\nкерують різними частинами стану додатку.\n\nЯкщо ми хочемо мати можливість щось робити у відповідь на всі `Changes`, ми\nможемо просто створити власний `BlocObserver`.\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\nВсе, що нам потрібно зробити, — це розширити `BlocObserver` та перевизначити\nметод `onChange`.\n\n:::\n\nЩоб використовувати `SimpleBlocObserver`, нам просто потрібно змінити функцію\n`main`:\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\nНаведений вище фрагмент потім виведе:\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\nВнутрішнє перевизначення `onChange` викликається першим, яке викликає\n`super.onChange`, сповіщаючи `onChange` у `BlocObserver`.\n\n:::\n\n:::tip\n\nУ `BlocObserver` ми маємо доступ до екземпляра `Cubit` на додаток до самого\n`Change`.\n\n:::\n\n### Обробка помилок у Cubit\n\nКожний `Cubit` має метод `addError`, який можна використовувати для позначення\nтого, що сталася помилка.\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n`onError` може бути перевизначений всередині `Cubit` для обробки всіх помилок\nдля конкретного `Cubit`.\n\n:::\n\n`onError` також може бути перевизначений у `BlocObserver` для глобальної обробки\nвсіх повідомлених помилок.\n\n<SimpleBlocObserverOnErrorSnippet />\n\nЯкщо ми знову запустимо ту ж програму, ми повинні побачити наступний вивід:\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n`Bloc` — це більш просунутий клас, який покладається на `події` для ініціювання\nзмін `стану`, а не на функції. `Bloc` також розширює `BlocBase`, що означає, що\nвін має аналогічний публічний API, як `Cubit`. Однак замість виклику `функції`\nна `Bloc` та безпосереднього видавання нового `стану`, `Bloc`-и отримують\n`події` та перетворюють вхідні `події` у вихідні `стани`.\n\n![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png)\n\n### Створення Bloc\n\nСтворення `Bloc` аналогічне створенню `Cubit`, за винятком того, що на додаток\nдо визначення стану, яким ми будемо керувати, ми також повинні визначити подію,\nяку `Bloc` зможе обробляти.\n\nПодії — це вхідні дані для Bloc. Вони зазвичай додаються у відповідь на\nвзаємодії користувача, такі як натискання кнопок, або події життєвого циклу,\nтакі як завантаження сторінки.\n\n<CounterBlocSnippet />\n\nТак само, як при створенні `CounterCubit`, ми повинні вказати початковий стан,\nпередавши його в суперклас через `super`.\n\n### Зміни стану Bloc\n\n`Bloc` вимагає, щоб ми реєстрували обробники подій через API `on<Event>`, на\nвідміну від функцій у `Cubit`. Обробник подій відповідає за перетворення\nбудь-яких вхідних подій у нуль або більше вихідних станів.\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\n`EventHandler` має доступ до доданої події, а також до `Emitter`, який може\nвикористовуватися для видавання нуля або більше станів у відповідь на вхідну\nподію.\n\n:::\n\nПотім ми можемо оновити `EventHandler` для обробки події\n`CounterIncrementPressed`:\n\n<CounterBlocIncrementSnippet />\n\nУ наведеному вище фрагменті ми зареєстрували `EventHandler` для керування всіма\nподіями `CounterIncrementPressed`. Для кожної вхідної події\n`CounterIncrementPressed` ми можемо отримати доступ до поточного стану bloc\nчерез геттер `state` та викликати `emit(state + 1)`.\n\n:::note\n\nОскільки клас `Bloc` розширює `BlocBase`, ми маємо доступ до поточного стану\nbloc у будь-який момент часу через геттер `state`, так само як у `Cubit`.\n\n:::\n\n:::caution\n\nBloc-и ніколи не повинні безпосередньо викликати `emit` для нових станів.\nЗамість цього кожна зміна стану повинна бути виведена у відповідь на вхідну\nподію всередині `EventHandler`.\n\n:::\n\n:::caution\n\nІ bloc-и, і cubit-и будуть ігнорувати дублікати станів. Якщо ми видамо\n`State nextState`, де `state == nextState`, то зміна стану не відбудеться.\n\n:::\n\n### Використання Bloc\n\nНа цьому етапі ми можемо створити екземпляр нашого `CounterBloc` та використати\nйого!\n\n#### Базове використання\n\n<CounterBlocUsageSnippet />\n\nУ наведеному вище фрагменті ми починаємо зі створення екземпляра `CounterBloc`.\nПотім ми виводимо поточний стан `Bloc`, який є початковим станом (оскільки нові\nстани ще не були видані). Далі ми додаємо подію `CounterIncrementPressed` для\nініціювання зміни стану. Нарешті, ми знову виводимо стан `Bloc`, який змінився з\n`0` на `1`, та викликаємо `close` на `Bloc` для закриття внутрішнього потоку\nстанів.\n\n:::note\n\n`await Future.delayed(Duration.zero)` додано, щоб переконатися, що ми чекаємо\nнаступної ітерації циклу подій (дозволяючи `EventHandler` обробити подію).\n\n:::\n\n#### Використання Stream\n\nТак само, як з `Cubit`, `Bloc` — це спеціальний тип `Stream`, що означає, що ми\nтакож можемо підписатися на `Bloc` для отримання оновлень його стану в реальному\nчасі:\n\n<CounterBlocStreamUsageSnippet />\n\nУ наведеному вище фрагменті ми підписуємося на `CounterBloc` та викликаємо print\nпри кожній зміні стану. Потім ми додаємо подію `CounterIncrementPressed`, яка\nзапускає `EventHandler` `on<CounterIncrementPressed>` та видає новий стан.\nНарешті, ми викликаємо `cancel` на підписці, коли більше не хочемо отримувати\nоновлення, та закриваємо `Bloc`.\n\n:::note\n\n`await Future.delayed(Duration.zero)` додано для цього прикладу, щоб уникнути\nнегайного скасування підписки.\n\n:::\n\n### Спостереження за Bloc\n\nОскільки `Bloc` розширює `BlocBase`, ми можемо спостерігати за всіма змінами\nстану для `Bloc` за допомогою `onChange`.\n\n<CounterBlocOnChangeSnippet />\n\nПотім ми можемо оновити `main.dart` до:\n\n<CounterBlocOnChangeUsageSnippet />\n\nТепер, якщо ми запустимо наведений вище фрагмент, вивід буде:\n\n<CounterBlocOnChangeOutputSnippet />\n\nОднією з ключових відмінностей між `Bloc` та `Cubit` є те, що оскільки `Bloc`\nкерується подіями, ми також можемо захопити інформацію про те, що спричинило\nзміну стану.\n\nМи можемо зробити це, перевизначивши `onTransition`.\n\nЗміна від одного стану до іншого називається `Transition`. `Transition`\nскладається з поточного стану, події та наступного стану.\n\n<CounterBlocOnTransitionSnippet />\n\nЯкщо ми потім повторно запустимо той самий фрагмент `main.dart` як раніше, ми\nповинні побачити наступний вивід:\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` викликається перед `onChange` та містить подію, яка спричинила\nзміну від `currentState` до `nextState`.\n\n:::\n\n#### BlocObserver\n\nТак само, як і раніше, ми можемо перевизначити `onTransition` у користувацькому\n`BlocObserver` для спостереження за всіма переходами, що відбуваються з одного\nмісця.\n\n<SimpleBlocObserverOnTransitionSnippet />\n\nМи можемо ініціалізувати `SimpleBlocObserver` так само, як і раніше:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\nТепер, якщо ми запустимо наведений вище фрагмент, вивід повинен виглядати так:\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` викликається першим (локальний перед глобальним), а потім\n`onChange`.\n\n:::\n\nЩе однією унікальною особливістю екземплярів `Bloc` є те, що вони дозволяють нам\nперевизначити `onEvent`, який викликається щоразу, коли нова подія додається до\n`Bloc`. Так само, як з `onChange` та `onTransition`, `onEvent` може бути\nперевизначений локально, а також глобально.\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\nМи можемо запустити той самий `main.dart`, як і раніше, та повинні побачити\nнаступний вивід:\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n`onEvent` викликається, як тільки подію додано. Локальний `onEvent` викликається\nперед глобальним `onEvent` у `BlocObserver`.\n\n:::\n\n### Обробка помилок у Bloc\n\nТак само, як з `Cubit`, кожний `Bloc` має методи `addError` та `onError`. Ми\nможемо вказати, що сталася помилка, викликавши `addError` з будь-якого місця\nвсередині нашого `Bloc`. Потім ми можемо реагувати на всі помилки,\nперевизначивши `onError`, так само як з `Cubit`.\n\n<CounterBlocOnErrorSnippet />\n\nЯкщо ми повторно запустимо той самий `main.dart`, як раніше, ми можемо побачити,\nяк це виглядає, коли про помилку повідомляється:\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\nЛокальний `onError` викликається першим, а потім глобальний `onError` у\n`BlocObserver`.\n\n:::\n\n:::note\n\n`onError` та `onChange` працюють абсолютно однаково для екземплярів як `Bloc`,\nтак і `Cubit`.\n\n:::\n\n:::caution\n\nБудь-які необроблені винятки, що виникають всередині `EventHandler`, також\nповідомляються до `onError`.\n\n:::\n\n## Cubit проти Bloc\n\nТепер, коли ми розглянули основи класів `Cubit` та `Bloc`, вам може бути цікаво,\nколи слід використовувати `Cubit`, а коли — `Bloc`.\n\n### Переваги Cubit\n\n#### Простота\n\nОднією з найбільших переваг використання `Cubit` є простота. При створенні\n`Cubit` нам потрібно визначити лише стан, а також функції, які ми хочемо надати\nдля зміни стану. Для порівняння, при створенні `Bloc` ми повинні визначити\nстани, події та реалізацію `EventHandler`. Це робить `Cubit` більш зрозумілим та\nпотребує менше коду.\n\nТепер давайте розглянемо дві реалізації лічильника:\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\nРеалізація `Cubit` є більш лаконічною, і замість окремого визначення подій\nфункції діють як події. Крім того, при використанні `Cubit` ми можемо просто\nвикликати `emit` з будь-якого місця, щоб ініціювати зміну стану.\n\n### Переваги Bloc\n\n#### Відстежуваність\n\nОднією з найбільших переваг використання `Bloc` є знання послідовності змін\nстану, а також того, що саме спричинило ці зміни. Для стану, який є критично\nважливим для функціональності додатку, може бути дуже корисно використовувати\nбільш подієво-орієнтований підхід, щоб захопити всі події на додаток до змін\nстану.\n\nПоширеним випадком використання може бути керування `AuthenticationState`. Для\nпростоти припустимо, що ми можемо представити `AuthenticationState` через\n`enum`:\n\n<AuthenticationStateSnippet />\n\nМоже бути багато причин, через які стан додатку міг змінитися з `authenticated`\nна `unauthenticated`. Наприклад, користувач міг натиснути кнопку виходу та\nзапросити вихід з додатку. З іншого боку, можливо, токен доступу користувача\nбуло відкликано, і його було примусово розлогінено. При використанні `Bloc` ми\nможемо чітко відстежити, як стан додатку потрапив у певний стан.\n\n<AuthenticationTransitionSnippet />\n\nНаведений вище `Transition` надає нам всю інформацію, необхідну для розуміння\nтого, чому змінився стан. Якби ми використовували `Cubit` для керування\n`AuthenticationState`, наші логи виглядали б так:\n\n<AuthenticationChangeSnippet />\n\nЦе повідомляє нам, що користувача було розлогінено, але не пояснює чому, що може\nбути критично важливим для налагодження та розуміння того, як стан додатку\nзмінюється з часом.\n\n#### Розширені перетворення подій\n\nЩе одна область, в якій `Bloc` перевершує `Cubit`, — це коли нам потрібно\nскористатися реактивними операторами, такими як `buffer`, `debounceTime`,\n`throttle` тощо.\n\n:::tip\n\nДив. [`package:stream_transform`](https://pub.dev/packages/stream_transform) та\n[`package:rxdart`](https://pub.dev/packages/rxdart) для перетворювачів потоків.\n\n:::\n\n`Bloc` має приймач подій, який дозволяє нам контролювати та перетворювати\nвхідний потік подій.\n\nНаприклад, якби ми створювали пошук у реальному часі, ми, ймовірно, хотіли б\nвідкласти запити до бекенду, щоб уникнути обмеження швидкості, а також зменшити\nвитрати/навантаження на бекенд.\n\nЗ `Bloc` ми можемо надати користувацький `EventTransformer` для зміни способу\nобробки вхідних подій `Bloc`.\n\n<DebounceEventTransformerSnippet />\n\nЗ наведеним вище кодом ми можемо легко відкласти вхідні події з дуже невеликою\nкількістю додаткового коду.\n\n:::tip\n\nОзнайомтеся з\n[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) для\nнабору перетворювачів подій з визначеною думкою.\n\n:::\n\nЯкщо ви не впевнені, що використовувати, починайте з `Cubit`, і ви зможете\nпізніше відрефакторити або масштабуватися до `Bloc` за потреби.\n"
  },
  {
    "path": "docs/src/content/docs/uk/faqs.mdx",
    "content": "---\ntitle: Часті запитання\ndescription: Відповіді на часті запитання щодо бібліотеки bloc.\n---\n\nimport StateNotUpdatingGood1Snippet from '~/components/faqs/StateNotUpdatingGood1Snippet.astro';\nimport StateNotUpdatingGood2Snippet from '~/components/faqs/StateNotUpdatingGood2Snippet.astro';\nimport StateNotUpdatingGood3Snippet from '~/components/faqs/StateNotUpdatingGood3Snippet.astro';\nimport StateNotUpdatingBad1Snippet from '~/components/faqs/StateNotUpdatingBad1Snippet.astro';\nimport StateNotUpdatingBad2Snippet from '~/components/faqs/StateNotUpdatingBad2Snippet.astro';\nimport StateNotUpdatingBad3Snippet from '~/components/faqs/StateNotUpdatingBad3Snippet.astro';\nimport EquatableEmitSnippet from '~/components/faqs/EquatableEmitSnippet.astro';\nimport EquatableBlocTestSnippet from '~/components/faqs/EquatableBlocTestSnippet.astro';\nimport NoEquatableBlocTestSnippet from '~/components/faqs/NoEquatableBlocTestSnippet.astro';\nimport SingleStateSnippet from '~/components/faqs/SingleStateSnippet.astro';\nimport SingleStateUsageSnippet from '~/components/faqs/SingleStateUsageSnippet.astro';\nimport BlocProviderGood1Snippet from '~/components/faqs/BlocProviderGood1Snippet.astro';\nimport BlocProviderGood2Snippet from '~/components/faqs/BlocProviderGood2Snippet.astro';\nimport BlocProviderBad1Snippet from '~/components/faqs/BlocProviderBad1Snippet.astro';\nimport BlocInternalAddEventSnippet from '~/components/faqs/BlocInternalAddEventSnippet.astro';\nimport BlocInternalEventSnippet from '~/components/faqs/BlocInternalEventSnippet.astro';\nimport BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSnippet.astro';\n\n## Стан не оновлюється\n\n❔ **Запитання**: Я випускаю стан у моєму блоці, але користувацький інтерфейс не\nоновлюється. Що я роблю неправильно?\n\n💡 **Відповідь**: Якщо ви використовуєте Equatable, переконайтеся, що ви\nпередаєте всі властивості у геттер props.\n\n✅ **ДОБРЕ**\n\n<StateNotUpdatingGood1Snippet />\n\n❌ **ПОГАНО**\n\n<StateNotUpdatingBad1Snippet />\n\n<StateNotUpdatingBad2Snippet />\n\nКрім того, переконайтеся, що ви випускаєте новий екземпляр стану у вашому блоці.\n\n✅ **ДОБРЕ**\n\n<StateNotUpdatingGood2Snippet />\n\n<StateNotUpdatingGood3Snippet />\n\n❌ **ПОГАНО**\n\n<StateNotUpdatingBad3Snippet />\n\n:::caution\n\nВластивості `Equatable` завжди повинні копіюватися, а не змінюватися. Якщо клас\n`Equatable` містить `List` або `Map` як властивості, обов'язково використовуйте\n`List.of` або `Map.of` відповідно, щоб гарантувати, що рівність оцінюється на\nоснові значень властивостей, а не посилання.\n\n:::\n\n## Коли використовувати Equatable\n\n❔**Запитання**: Коли мені слід використовувати Equatable?\n\n💡**Відповідь**:\n\n<EquatableEmitSnippet />\n\nУ наведеному вище сценарії, якщо `StateA` розширює `Equatable`, відбудеться лише\nодна зміна стану (другий emit буде проігноровано). Загалом, ви повинні\nвикористовувати `Equatable`, якщо хочете оптимізувати свій код для зменшення\nкількості перебудов. Ви не повинні використовувати `Equatable`, якщо хочете, щоб\nоднаковий стан підряд викликав кілька переходів.\n\nКрім того, використання `Equatable` значно спрощує тестування блоків, оскільки\nми можемо очікувати конкретні екземпляри станів блоку, а не використовувати\n`Matchers` або `Predicates`.\n\n<EquatableBlocTestSnippet />\n\nБез `Equatable` наведений вище тест не пройде, і його потрібно буде переписати\nтак:\n\n<NoEquatableBlocTestSnippet />\n\n## Обробка помилок\n\n❔ **Запитання**: Як я можу обробити помилку, зберігаючи при цьому попередні\nдані?\n\n💡 **Відповідь**:\n\nЦе значною мірою залежить від того, як було змодельовано стан блоку. У випадках,\nколи дані повинні зберігатися навіть за наявності помилки, розгляньте\nвикористання одного класу стану.\n\n<SingleStateSnippet />\n\nЦе дозволить віджетам мати доступ до властивостей `data` та `error` одночасно, і\nблок може використовувати `state.copyWith` для збереження старих даних навіть\nколи сталася помилка.\n\n<SingleStateUsageSnippet />\n\n## Bloc vs. Redux\n\n❔ **Запитання**: У чому різниця між Bloc та Redux?\n\n💡 **Відповідь**:\n\nBLoC -- це шаблон проєктування, що визначається наступними правилами:\n\n1. Вхід та вихід BLoC -- це прості потоки та приймачі.\n2. Залежності повинні бути впроваджуваними та незалежними від платформи.\n3. Розгалуження платформи не допускається.\n4. Реалізація може бути будь-якою, якщо ви дотримуєтесь наведених вище правил.\n\nРекомендації щодо користувацького інтерфейсу:\n\n1. Кожен \"достатньо складний\" компонент має відповідний BLoC.\n2. Компоненти повинні надсилати входи \"як є\".\n3. Компоненти повинні показувати виходи якомога ближче до \"як є\".\n4. Усі розгалуження повинні базуватися на простих логічних виходах BLoC.\n\nБібліотека Bloc реалізує шаблон проєктування BLoC і спрямована на абстрагування\nRxDart для спрощення досвіду розробника.\n\nТри принципи Redux:\n\n1. Єдине джерело істини\n2. Стан доступний лише для читання\n3. Зміни виконуються чистими функціями\n\nБібліотека bloc порушує перший принцип; з bloc стан розподілений по кількох\nблоках. Крім того, у bloc немає концепції middleware, і bloc призначений для\nспрощення асинхронних змін стану, дозволяючи вам випускати кілька станів для\nоднієї події.\n\n## Bloc vs. Provider\n\n❔ **Запитання**: У чому різниця між Bloc та Provider?\n\n💡 **Відповідь**: `provider` призначений для впровадження залежностей (він\nобгортає `InheritedWidget`). Вам все одно потрібно з'ясувати, як керувати вашим\nстаном (через `ChangeNotifier`, `Bloc`, `Mobx` тощо...). Бібліотека Bloc\nвикористовує `provider` внутрішньо, щоб спростити надання та доступ до блоків по\nвсьому дереву віджетів.\n\n## BlocProvider.of() не може знайти Bloc\n\n❔ **Запитання**: При використанні `BlocProvider.of(context)` він не може знайти\nблок. Як це виправити?\n\n💡 **Відповідь**: Ви не можете отримати доступ до блоку з того самого контексту,\nв якому він був наданий, тому ви маєте переконатися, що `BlocProvider.of()`\nвикликається всередині дочірнього `BuildContext`.\n\n✅ **ДОБРЕ**\n\n<BlocProviderGood1Snippet />\n\n<BlocProviderGood2Snippet />\n\n❌ **ПОГАНО**\n\n<BlocProviderBad1Snippet />\n\n## Структура проєкту\n\n❔ **Запитання**: Як мені структурувати мій проєкт?\n\n💡 **Відповідь**: Хоча на це запитання дійсно немає правильної/неправильної\nвідповіді, деякі рекомендовані посилання:\n\n- [I/O Photobooth](https://github.com/flutter/photobooth)\n- [I/O Pinball](https://github.com/flutter/pinball)\n- [Flutter News Toolkit](https://github.com/flutter/news_toolkit)\n\nНайважливіше -- мати **послідовну** та **продуману** структуру проєкту.\n\n## Додавання подій всередині блоку\n\n❔ **Запитання**: Чи можна додавати події всередині блоку?\n\n💡 **Відповідь**: У більшості випадків події повинні додаватися ззовні, але в\nдеяких окремих випадках може мати сенс додавати події внутрішньо.\n\nНайпоширеніша ситуація, в якій використовуються внутрішні події, -- це коли\nзміни стану повинні відбуватися у відповідь на оновлення в реальному часі з\nрепозиторію. У цих ситуаціях репозиторій є стимулом для зміни стану замість\nзовнішньої події, такої як натискання кнопки.\n\nУ наступному прикладі стан `MyBloc` залежить від поточного користувача, який\nнадається через `Stream<User>` з `UserRepository`. `MyBloc` прослуховує зміни\nпоточного користувача та додає внутрішню подію `_UserChanged` щоразу, коли\nкористувач випускається з потоку користувачів.\n\n<BlocInternalAddEventSnippet />\n\nДодаючи внутрішню подію, ми також можемо вказати користувацький `transformer`\nдля події, щоб визначити, як будуть оброблятися кілька подій `_UserChanged` --\nза замовчуванням вони будуть оброблятися одночасно.\n\nНаполегливо рекомендується, щоб внутрішні події були приватними. Це явний спосіб\nсигналізувати, що конкретна подія використовується лише всередині самого блоку\nта запобігає обізнаності зовнішніх компонентів про подію.\n\n<BlocInternalEventSnippet />\n\nЯк альтернативу, ми можемо визначити зовнішню подію `Started` та використати API\n`emit.forEach` для обробки реагування на оновлення користувачів у реальному\nчасі:\n\n<BlocExternalForEachSnippet />\n\nПереваги наведеного вище підходу:\n\n- Нам не потрібна внутрішня подія `_UserChanged`\n- Нам не потрібно керувати `StreamSubscription` вручну\n- Ми маємо повний контроль над тим, коли блок підписується на потік оновлень\n  користувачів\n\nНедоліки наведеного вище підходу:\n\n- Ми не можемо легко призупинити `pause` або відновити `resume` підписку\n- Нам потрібно надати публічну подію `Started`, яка повинна бути додана ззовні\n- Ми не можемо використовувати користувацький `transformer` для налаштування\n  того, як ми реагуємо на оновлення користувачів\n\n## Надання публічних методів\n\n❔ **Запитання**: Чи можна надавати публічні методи в моїх екземплярах bloc та\ncubit?\n\n💡 **Відповідь**\n\nПри створенні cubit рекомендується надавати лише публічні методи для цілей\nініціювання змін стану. У результаті, як правило, всі публічні методи в\nекземплярі cubit повинні повертати `void` або `Future<void>`.\n\nПри створенні bloc рекомендується уникати надання будь-яких користувацьких\nпублічних методів і замість цього сповіщати блок про події, викликаючи `add`.\n"
  },
  {
    "path": "docs/src/content/docs/uk/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Концепції Flutter Bloc\ndescription: Огляд основних концепцій для package:flutter_bloc.\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport RepositoryProviderDisposeSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderDisposeSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\nБудь ласка, уважно прочитайте наступні розділи перед роботою з\n[`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc).\n\n:::\n\n:::note\n\nУсі віджети, що експортуються пакетом `flutter_bloc`, інтегруються як з\nекземплярами `Cubit`, так і з екземплярами `Bloc`.\n\n:::\n\n## Віджети Bloc\n\n### BlocBuilder\n\n**BlocBuilder** — це Flutter віджет, якому потрібен `Bloc` та функція `builder`.\n`BlocBuilder` обробляє побудову віджета у відповідь на нові стани. `BlocBuilder`\nдуже схожий на `StreamBuilder`, але має простіший API для зменшення кількості\nшаблонного коду. Функція `builder` може потенційно викликатися багато разів і\nповинна бути [чистою функцією](https://en.wikipedia.org/wiki/Pure_function), яка\nповертає віджет у відповідь на стан.\n\nДивіться `BlocListener`, якщо ви хочете \"робити\" щось у відповідь на зміни\nстану, такі як навігація, показ діалогу тощо.\n\nЯкщо параметр `bloc` опущено, `BlocBuilder` автоматично виконає пошук,\nвикористовуючи `BlocProvider` та поточний `BuildContext`.\n\n<BlocBuilderSnippet />\n\nВказуйте bloc лише в тому випадку, якщо ви хочете надати bloc, який буде\nобмежений одним віджетом і не доступний через батьківський `BlocProvider` та\nпоточний `BuildContext`.\n\n<BlocBuilderExplicitBlocSnippet />\n\nДля точного контролю над тим, коли викликається функція `builder`, можна надати\nнеобов'язковий параметр `buildWhen`. `buildWhen` приймає попередній стан bloc та\nпоточний стан bloc і повертає булеве значення. Якщо `buildWhen` повертає true,\n`builder` буде викликаний з `state` і віджет буде перебудований. Якщо\n`buildWhen` повертає false, `builder` не буде викликаний з `state` і перебудова\nне відбудеться.\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** — це Flutter віджет, який аналогічний `BlocBuilder`, але\nдозволяє розробникам фільтрувати оновлення, обираючи нове значення на основі\nпоточного стану bloc. Непотрібні побудови запобігаються, якщо обране значення не\nзмінюється. Обране значення повинно бути незмінним, щоб `BlocSelector` міг точно\nвизначити, чи повинен `builder` бути викликаний знову.\n\nЯкщо параметр `bloc` опущено, `BlocSelector` автоматично виконає пошук,\nвикористовуючи `BlocProvider` та поточний `BuildContext`.\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** — це Flutter віджет, який надає bloc своїм дочірнім елементам\nчерез `BlocProvider.of<T>(context)`. Він використовується як віджет впровадження\nзалежностей (DI), щоб один екземпляр bloc міг бути наданий кільком віджетам у\nпіддереві.\n\nУ більшості випадків `BlocProvider` повинен використовуватися для створення\nнових bloc, які будуть доступні решті піддерева. У цьому випадку, оскільки\n`BlocProvider` відповідає за створення bloc, він автоматично обробить закриття\nbloc.\n\n<BlocProviderSnippet />\n\nЗа замовчуванням `BlocProvider` створить bloc ліниво, що означає, що `create`\nбуде виконаний, коли bloc буде знайдений через\n`BlocProvider.of<BlocA>(context)`.\n\nЩоб перевизначити цю поведінку та примусово запустити `create` негайно, `lazy`\nможна встановити у `false`.\n\n<BlocProviderEagerSnippet />\n\nУ деяких випадках `BlocProvider` може використовуватися для надання існуючого\nbloc новій частині дерева віджетів. Це найчастіше використовується, коли\nіснуючий bloc потрібно зробити доступним для нового маршруту. У цьому випадку\n`BlocProvider` не буде автоматично закривати bloc, оскільки він його не\nстворював.\n\n<BlocProviderValueSnippet />\n\nпотім з `ChildA` або `ScreenA` ми можемо отримати `BlocA` за допомогою:\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** — це Flutter віджет, який об'єднує кілька віджетів\n`BlocProvider` в один. `MultiBlocProvider` покращує читабельність та усуває\nнеобхідність вкладати кілька `BlocProviders`. Використовуючи\n`MultiBlocProvider`, ми можемо перейти від:\n\n<NestedBlocProviderSnippet />\n\nдо:\n\n<MultiBlocProviderSnippet />\n\n:::caution\n\nКоли `BlocProvider` визначений у контексті `MultiBlocProvider`, будь-який\n`child` буде ігноруватися.\n\n:::\n\n### BlocListener\n\n**BlocListener** — це Flutter віджет, який приймає `BlocWidgetListener` та\nнеобов'язковий `Bloc` і викликає `listener` у відповідь на зміни стану в bloc.\nВін повинен використовуватися для функціональності, яка повинна виконуватися\nодин раз на кожну зміну стану, такої як навігація, показ `SnackBar`, показ\n`Dialog` тощо.\n\n`listener` викликається лише один раз для кожної зміни стану (**НЕ** включаючи\nпочатковий стан), на відміну від `builder` у `BlocBuilder`, і є функцією `void`.\n\nЯкщо параметр `bloc` опущено, `BlocListener` автоматично виконає пошук,\nвикористовуючи `BlocProvider` та поточний `BuildContext`.\n\n<BlocListenerSnippet />\n\nВказуйте bloc лише в тому випадку, якщо ви хочете надати bloc, який інакше не\nдоступний через `BlocProvider` та поточний `BuildContext`.\n\n<BlocListenerExplicitBlocSnippet />\n\nДля точного контролю над тим, коли викликається функція `listener`, можна надати\nнеобов'язковий параметр `listenWhen`. `listenWhen` приймає попередній стан bloc\nта поточний стан bloc і повертає булеве значення. Якщо `listenWhen` повертає\ntrue, `listener` буде викликаний з `state`. Якщо `listenWhen` повертає false,\n`listener` не буде викликаний з `state`.\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** — це Flutter віджет, який об'єднує кілька віджетів\n`BlocListener` в один. `MultiBlocListener` покращує читабельність та усуває\nнеобхідність вкладати кілька `BlocListeners`. Використовуючи\n`MultiBlocListener`, ми можемо перейти від:\n\n<NestedBlocListenerSnippet />\n\nдо:\n\n<MultiBlocListenerSnippet />\n\n:::caution\n\nКоли `BlocListener` визначений у контексті `MultiBlocListener`, будь-який\n`child` буде ігноруватися.\n\n:::\n\n### BlocConsumer\n\n**BlocConsumer** надає `builder` та `listener` для реагування на нові стани.\n`BlocConsumer` аналогічний вкладеним `BlocListener` та `BlocBuilder`, але\nзменшує кількість необхідного шаблонного коду. `BlocConsumer` повинен\nвикористовуватися лише коли необхідно як перебудувати UI, так і виконати інші\nреакції на зміни стану в `bloc`. `BlocConsumer` приймає обов'язкові\n`BlocWidgetBuilder` та `BlocWidgetListener` і необов'язкові `bloc`,\n`BlocBuilderCondition` та `BlocListenerCondition`.\n\nЯкщо параметр `bloc` опущено, `BlocConsumer` автоматично виконає пошук,\nвикористовуючи `BlocProvider` та поточний `BuildContext`.\n\n<BlocConsumerSnippet />\n\nНеобов'язкові `listenWhen` та `buildWhen` можуть бути реалізовані для більш\nдетального контролю над тим, коли викликаються `listener` та `builder`.\n`listenWhen` та `buildWhen` будуть викликані при кожній зміні `state` в `bloc`.\nКожний приймає попередній `state` та поточний `state` і повинен повернути\n`bool`, який визначає, чи буде викликана функція `builder` та/або `listener`.\nПопередній `state` буде ініціалізований станом `state` блоку `bloc` при\nініціалізації `BlocConsumer`. `listenWhen` та `buildWhen` є необов'язковими, і\nякщо вони не реалізовані, за замовчуванням буде `true`.\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**RepositoryProvider** — це Flutter віджет, який надає сховище своїм дочірнім\nелементам через `RepositoryProvider.of<T>(context)`. Він використовується як\nвіджет впровадження залежностей (DI), щоб один екземпляр сховища міг бути\nнаданий кільком віджетам у піддереві. `BlocProvider` повинен використовуватися\nдля надання bloc, тоді як `RepositoryProvider` повинен використовуватися лише\nдля сховищ.\n\n<RepositoryProviderSnippet />\n\nпотім з `ChildA` ми можемо отримати екземпляр `Repository` за допомогою:\n\n<RepositoryProviderLookupSnippet />\n\nСховища, які керують ресурсами, що повинні бути звільнені, можуть зробити це\nчерез зворотний виклик `dispose`:\n\n<RepositoryProviderDisposeSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** — це Flutter віджет, який об'єднує кілька віджетів\n`RepositoryProvider` в один. `MultiRepositoryProvider` покращує читабельність та\nусуває необхідність вкладати кілька `RepositoryProvider`. Використовуючи\n`MultiRepositoryProvider`, ми можемо перейти від:\n\n<NestedRepositoryProviderSnippet />\n\nдо:\n\n<MultiRepositoryProviderSnippet />\n\n:::caution\n\nКоли `RepositoryProvider` визначений у контексті `MultiRepositoryProvider`,\nбудь-який `child` буде ігноруватися.\n\n:::\n\n## Використання BlocProvider\n\nДавайте розглянемо, як використовувати `BlocProvider` для надання `CounterBloc`\nу `CounterPage` та реагувати на зміни стану за допомогою `BlocBuilder`.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\nНа цьому етапі ми успішно відокремили наш шар представлення від шару\nбізнес-логіки. Зверніть увагу, що віджет `CounterPage` нічого не знає про те, що\nвідбувається, коли користувач натискає на кнопки. Віджет просто повідомляє\n`CounterBloc`, що користувач натиснув кнопку збільшення або зменшення.\n\n## Використання RepositoryProvider\n\nМи розглянемо, як використовувати `RepositoryProvider` у контексті прикладу\n[`flutter_weather`][flutter_weather_link].\n\n<WeatherRepositorySnippet />\n\nУ нашому `main.dart` ми викликаємо `runApp` з нашим віджетом `WeatherApp`.\n\n<WeatherMainSnippet />\n\nМи впровадимо наш екземпляр `WeatherRepository` у дерево віджетів через\n`RepositoryProvider`.\n\nПри створенні екземпляра bloc ми можемо отримати доступ до екземпляра сховища\nчерез `context.read` та впровадити сховище в bloc через конструктор.\n\n<WeatherAppSnippet />\n\n:::tip\n\nЯкщо у вас більше одного сховища, ви можете використовувати\n`MultiRepositoryProvider` для надання кількох екземплярів сховищ піддереву.\n\n:::\n\n:::note\n\nВикористовуйте зворотний виклик `dispose` для звільнення будь-яких ресурсів,\nколи `RepositoryProvider` демонтується.\n\n:::\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## Методи розширення\n\n[Методи розширення](https://dart.dev/guides/language/extension-methods),\nпредставлені в Dart 2.7, — це спосіб додати функціональність до існуючих\nбібліотек. У цьому розділі ми розглянемо методи розширення, включені в\n`package:flutter_bloc`, та як їх можна використовувати.\n\n`flutter_bloc` має залежність від\n[package:provider](https://pub.dev/packages/provider), яка спрощує використання\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).\n\nВнутрішньо `package:flutter_bloc` використовує `package:provider` для\nреалізації: `BlocProvider`, `MultiBlocProvider`, `RepositoryProvider` та\nвіджетів `MultiRepositoryProvider`. `package:flutter_bloc` експортує розширення\n`ReadContext`, `WatchContext` та `SelectContext` з `package:provider`.\n\n:::note\n\nДізнайтеся більше про [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` шукає найближчий екземпляр предка типу `T` і функціонально\nеквівалентний `BlocProvider.of<T>(context)`. `context.read` найчастіше\nвикористовується для отримання екземпляра bloc, щоб додати подію у зворотних\nвикликах `onPressed`.\n\n:::note\n\n`context.read<T>()` не прослуховує `T` — якщо наданий `Object` типу `T`\nзмінюється, `context.read` не викличе перебудову віджета.\n\n:::\n\n#### Використання\n\n✅ **ВИКОРИСТОВУЙТЕ** `context.read` для додавання подій у зворотних викликах.\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **УНИКАЙТЕ** використання `context.read` для отримання стану в методі\n`build`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nВищевказане використання є схильним до помилок, тому що віджет `Text` не буде\nперебудований, якщо стан bloc зміниться.\n\n:::caution\n\nВикористовуйте `BlocBuilder` або `context.watch` замість цього, щоб\nперебудовувати у відповідь на зміни стану.\n\n:::\n\n### context.watch\n\nЯк і `context.read<T>()`, `context.watch<T>()` надає найближчий екземпляр предка\nтипу `T`, однак він також прослуховує зміни екземпляра. Це функціонально\nеквівалентно `BlocProvider.of<T>(context, listen: true)`.\n\nЯкщо наданий `Object` типу `T` змінюється, `context.watch` викличе перебудову.\n\n:::caution\n\n`context.watch` доступний лише в методі `build` класу `StatelessWidget` або\n`State`.\n\n:::\n\n#### Використання\n\n✅ **ВИКОРИСТОВУЙТЕ** `BlocBuilder` замість `context.watch` для явного обмеження\nперебудов.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // Коли стан змінюється, перебудовується лише Text.\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\nАльтернативно, використовуйте `Builder` для обмеження перебудов.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Коли стан змінюється, перебудовується лише Text.\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **ВИКОРИСТОВУЙТЕ** `Builder` та `context.watch` як `MultiBlocBuilder`.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // повертає віджет, який залежить від стану BlocA, BlocB та BlocC\n  }\n);\n```\n\n❌ **УНИКАЙТЕ** використання `context.watch`, коли батьківський віджет в методі\n`build` не залежить від стану.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Коли стан змінюється, MaterialApp перебудовується\n  // навіть якщо він використовується лише у віджеті Text.\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\nВикористання `context.watch` в корені методу `build` призведе до перебудови\nвсього віджета при зміні стану bloc.\n\n:::\n\n### context.select\n\nЯк і `context.watch<T>()`, `context.select<T, R>(R function(T value))` надає\nнайближчий екземпляр предка типу `T` та прослуховує зміни `T`. На відміну від\n`context.watch`, `context.select` дозволяє прослуховувати зміни в меншій частині\nстану.\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\nВищевказане буде перебудовувати віджет лише коли властивість `name` стану\n`ProfileBloc` зміниться.\n\n#### Використання\n\n✅ **ВИКОРИСТОВУЙТЕ** `BlocSelector` замість `context.select` для явного\nобмеження перебудов.\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // Коли state.name змінюється, перебудовується лише Text.\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\nАльтернативно, використовуйте `Builder` для обмеження перебудов.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // Коли state.name змінюється, перебудовується лише Text.\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **УНИКАЙТЕ** використання `context.select`, коли батьківський віджет в методі\nbuild не залежить від стану.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // Коли state.value змінюється, MaterialApp перебудовується\n  // навіть якщо він використовується лише у віджеті Text.\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\nВикористання `context.select` в корені методу `build` призведе до перебудови\nвсього віджета при зміні обраного значення.\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/uk/getting-started.mdx",
    "content": "---\ntitle: Початок роботи\ndescription: Все, що потрібно для початку роботи з Bloc.\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Пакети\n\nЕкосистема bloc складається з кількох пакетів, перелічених нижче:\n\n| Пакет                                                                                      | Опис                            | Посилання                                                                                                      |\n| ------------------------------------------------------------------------------------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | Компоненти AngularDart          | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | Основні API Dart                | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | Перетворювачі подій             | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Користувацький лінтер           | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | API для тестування              | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Інструменти командного рядка    | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Віджети Flutter                 | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | Підтримка кешування/збереження  | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | Підтримка скасування/повторення | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## Встановлення\n\n<InstallationTabs />\n\n:::note\n\nДля початку використання bloc у вас має бути встановлений\n[Dart SDK](https://dart.dev/get-dart) на вашому комп'ютері.\n\n:::\n\n## Імпорти\n\nТепер, коли ми успішно встановили bloc, ми можемо створити наш `main.dart` та\nімпортувати відповідний пакет `bloc`.\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/uk/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Бібліотека керування станом Bloc\ndescription:\n  Офіційна документація бібліотеки керування станом bloc. Підтримка Dart,\n  Flutter та AngularDart. Включає приклади та посібники.\nbanner:\n  content: |\n    ✨ Відвідайте\n    <a href=\"https://shop.bloclibrary.dev\">Крамницю Bloc</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: Передбачувана бібліотека керування станом для Dart.\n  image:\n    alt: Логотип Bloc\n    file: ~/assets/bloc.svg\n  actions:\n    - text: Почати\n      link: /uk/getting-started/\n      variant: primary\n      icon: rocket\n    - text: Переглянути на GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"Почати\" icon=\"rocket\">\n\t```sh\n\t# Додайте bloc до вашого проєкту.\n\tdart pub add bloc\n\t```\n\nНаш [посібник для початку роботи](/uk/getting-started) містить покрокові\nінструкції щодо того, як почати використовувати Bloc всього за кілька хвилин.\n\n</SplitCard>\n\n<Card title=\"Пройдіть екскурсію\" icon=\"star\">\n\tПройдіть [офіційні посібники](/uk/tutorials/flutter-counter), щоб вивчити\n\tнайкращі практики та створити різноманітні додатки на основі Bloc.\n</Card>\n\n<Card title=\"Створюйте з Bloc\" icon=\"laptop\">\n\tДосліджуйте високоякісні, повністю протестовані [приклади\n\tдодатків](https://github.com/felangel/bloc/tree/master/examples), такі як\n\tлічильник, таймер, нескінченний список, погода, завдання та багато іншого!\n</Card>\n\n<ListCard title=\"Навчання\" icon=\"open-book\">\n\n    - [Чому Bloc?](/uk/why-bloc)\n    - [Основні концепції](/uk/bloc-concepts)\n    - [Архітектура](/uk/architecture)\n    - [Тестування](/uk/testing)\n    - [Угоди про найменування](/uk/naming-conventions)\n    - [Часті запитання](/uk/faqs)\n\n</ListCard>\n\n  <ListCard title=\"Інтеграції\" icon=\"puzzle\">\n    - [Інтеграція з VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [Інтеграція з IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Інтеграція з Neovim](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Інтеграція з Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [Користувацькі шаблони](https://brickhub.dev/search?q=bloc)\n    - [Інструменти розробника](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint/configuration.mdx",
    "content": "---\ntitle: Конфігурація лінтера\ndescription: Налаштування лінтера bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport BlocLintBasicAnalysisOptionsSnippet from '~/components/lint/BlocLintBasicAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\nimport RunBlocLintInSrcTestSnippet from '~/components/lint/RunBlocLintInSrcTestSnippet.astro';\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\n\nЗа замовчуванням лінтер bloc не видаватиме жодної діагностики, якщо ви явно не\nналаштували параметри аналізу проєкту.\n\nДля початку створіть або змініть наявний файл `analysis_options.yaml` у корені\nвашого проєкту, щоб включити список правил під ключем верхнього рівня bloc:\n\n<BlocLintBasicAnalysisOptionsSnippet />\n\nЗапустіть лінтер за допомогою наступної команди в терміналі:\n\n<RunBlocLintInCurrentDirectorySnippet />\n\nНаведена вище команда проаналізує всі файли в поточному каталозі та його\nпідкаталогах, але ви також можете перевірити конкретні файли та каталоги,\nпередавши їх як аргументи командного рядка:\n\n<RunBlocLintInSrcTestSnippet />\n\nНаведена вище команда проаналізує весь код у каталогах `src` та `test`.\n\nЯкщо правило `avoid_flutter_imports` увімкнено, будь-який файл bloc або cubit,\nщо містить імпорт flutter, буде позначено як попередження:\n\n<AvoidFlutterImportsWarningSnippet />\n\nВи можете побачити попередження, запустивши команду `bloc lint`:\n\n<RunBlocLintCounterCubitSnippet />\n\nВивід має виглядати так:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\n:::note\n\nОсь усі підтримувані правила лінтера:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/packages/bloc_lint/lib/all.yaml\"\n\ttitle=\"package:bloc_lint/all.yaml\"\n/>\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint/customizing-rules.mdx",
    "content": "---\ntitle: Налаштування правил лінтера\ndescription: Налаштування правил лінтера bloc\nsidebar:\n  order: 4\n---\n\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintEnablingRulesSnippet from '~/components/lint/BlocLintEnablingRulesSnippet.astro';\nimport BlocLintDisablingRulesSnippet from '~/components/lint/BlocLintDisablingRulesSnippet.astro';\nimport BlocLintChangingSeveritySnippet from '~/components/lint/BlocLintChangingSeveritySnippet.astro';\nimport ImportFlutterInfoSnippet from '~/components/lint/ImportFlutterInfoSnippet.mdx';\nimport ImportFlutterInfoOutputSnippet from '~/components/lint/ImportFlutterInfoOutputSnippet.astro';\nimport BlocLintExcludingFilesSnippet from '~/components/lint/BlocLintExcludingFilesSnippet.astro';\nimport BlocLintIgnoreForLineSnippet from '~/components/lint/BlocLintIgnoreForLineSnippet.astro';\nimport BlocLintIgnoreForFileSnippet from '~/components/lint/BlocLintIgnoreForFileSnippet.astro';\n\nВи можете налаштувати поведінку лінтера bloc, змінюючи серйозність окремих\nправил, індивідуально вмикаючи або вимикаючи правила, а також виключаючи файли\nзі статичного аналізу.\n\n## Увімкнення та вимкнення правил\n\nЛінтер bloc підтримує зростаючий список правил лінтера. Зверніть увагу, що\nправила лінтера не обов'язково повинні узгоджуватися між собою. Наприклад, деякі\nрозробники можуть надавати перевагу використанню блоків (`prefer_bloc`), тоді як\nінші можуть надавати перевагу використанню кубітів (`prefer_cubit`).\n\n:::note\n\nНа відміну від статичного аналізу, правила лінтера можуть містити хибні\nспрацювання. Не соромтеся повідомляти про будь-які хибні спрацювання або інші\nпроблеми, [створивши issue](https://github.com/felangel/bloc/issues/new/choose).\n\n:::\n\n### Увімкнення рекомендованих правил\n\nБібліотека bloc надає набір рекомендованих правил лінтера у складі пакета\n[`bloc_lint`](https://pub.dev/packages/bloc_lint).\n\nДля увімкнення рекомендованого набору лінтів додайте пакет `bloc_lint` як\ndev-залежність:\n\n<InstallBlocLintSnippet />\n\nПотім відредагуйте ваш файл `analysis_options.yaml`, щоб включити набір правил:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\n:::note\n\nКоли публікується нова версія `bloc_lint`, код, який раніше проходив статичний\nаналіз, може почати не проходити його. Ми рекомендуємо оновити ваш код для\nроботи з новими правилами, або ви також можете за бажанням увімкнути чи вимкнути\nокремі правила.\n\n:::\n\n### Увімкнення окремих правил\n\nДля увімкнення окремих правил додайте `bloc:` до файлу `analysis_options.yaml`\nяк ключ верхнього рівня та `rules:` як ключ другого рівня. У наступних рядках\nвкажіть правила, які ви бажаєте, у вигляді списку YAML (з префіксом у вигляді\nдефісів).\n\nНаприклад:\n\n<BlocLintEnablingRulesSnippet />\n\n### Вимкнення окремих правил\n\nЯкщо ви включаєте наявний набір правил, такий як набір `recommended`, ви можете\nзахотіти вимкнути одне або кілька включених правил лінтера. Вимкнення правил\nаналогічне їх увімкненню, але потребує використання YAML-карти замість списку.\n\nНаприклад, наступне включає рекомендований набір правил лінтера за винятком\n`avoid_public_bloc_methods` та додатково вмикає правило `prefer_bloc`:\n\n<BlocLintDisablingRulesSnippet />\n\n## Налаштування серйозності правил\n\nВи можете налаштувати серйозність будь-якого правила таким чином:\n\n<BlocLintChangingSeveritySnippet />\n\nТепер те саме правило лінтера відображатиметься з серйозністю `info` замість\n`warning`:\n\n<ImportFlutterInfoSnippet />\n\nВивід команди `bloc lint` має виглядати так:\n\n<ImportFlutterInfoOutputSnippet />\n\nПідтримувані варіанти серйозності:\n\n| Серйозність | Опис                                              |\n| ----------- | ------------------------------------------------- |\n| `error`     | Вказує, що шаблон не дозволений.                  |\n| `warning`   | Вказує, що шаблон підозрілий, але дозволений.     |\n| `info`      | Надає інформацію користувачам, але не є проблемою |\n| `hint`      | Пропонує кращий спосіб досягнення результату.     |\n\n## Виключення файлів\n\nІноді допустимо, що статичний аналіз не проходить. Наприклад, ви можете захотіти\nігнорувати попередження або помилки, що відображаються у згенерованому коді,\nякий не був написаний вами та вашою командою. Так само, як і з офіційними\nправилами лінтера Dart, ви можете використовувати опцію аналізатора `exclude:`,\nщоб виключити файли зі статичного аналізу.\n\nВи можете або перелічити окремі файли, або використовувати патерни\n[`glob`](https://pub.dev/packages/glob).\n\n:::note\n\nУсі використання патернів glob мають бути відносно каталогу, що містить\nвідповідний файл `analysis_options.yaml`.\n\n:::\n\nНаприклад, ми можемо виключити весь згенерований код Dart за допомогою наступних\nпараметрів аналізу:\n\n<BlocLintExcludingFilesSnippet />\n\n## Ігнорування правил\n\nТак само, як і з офіційними правилами лінтера Dart, ви можете ігнорувати правила\nлінтера bloc для певного файлу або рядка коду, використовуючи\n`// ignore_for_file` та `// ignore` відповідно.\n\n:::note\n\nЩоб ігнорувати кілька правил для певного рядка або файлу, вкажіть список,\nрозділений комами.\n\n:::\n\n### Ігнорування рядків\n\nМи можемо ігнорувати конкретні випадки порушень правил, додавши коментар\n`ignore` безпосередньо над проблемним рядком або додавши його в кінець\nпроблемного рядка.\n\nНаприклад, ми можемо ігнорувати конкретні випадки\n`prefer_file_naming_conventions` у певному файлі:\n\n<BlocLintIgnoreForLineSnippet />\n\n### Ігнорування файлів\n\nМи можемо ігнорувати всі випадки порушень правил у файлі, додавши коментар\n`ignore_for_file` у будь-якому місці файлу.\n\nНаприклад, ми можемо ігнорувати всі випадки `prefer_file_naming_conventions` у\nпевному файлі:\n\n<BlocLintIgnoreForFileSnippet />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint/index.mdx",
    "content": "---\ntitle: Огляд лінтера\ndescription: Вступ до лінтера bloc.\nsidebar:\n  order: 1\n---\n\nimport AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWarningSnippet.mdx';\nimport AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro';\n\nЛінтинг — це процес статичного аналізу коду для виявлення потенційних помилок, а\nтакож програмних і стилістичних проблем.\n\nBloc має вбудований лінтер, який можна використовувати через вашу IDE або\n[`інструменти командного рядка bloc`](https://pub.dev/packages/bloc_tools) за\nдопомогою команди `bloc lint`.\n\nЗа допомогою лінтера bloc ви можете підвищити якість вашої кодової бази та\nзабезпечити узгодженість без виконання жодного рядка коду.\n\nНаприклад, можливо, ви випадково імпортували залежність Flutter у ваш cubit:\n\n<AvoidFlutterImportsWarningSnippet />\n\nЗа правильного налаштування лінтер bloc вкаже на імпорт і видасть наступне\nпопередження:\n\n<AvoidFlutterImportsWarningOutputSnippet />\n\nУ наступних розділах ми розглянемо, як встановити, налаштувати та кастомізувати\nлінтер bloc, щоб ви могли скористатися перевагами статичного аналізу у вашій\nкодовій базі.\n\n## Швидкий старт\n\nПочніть використовувати лінтер bloc всього за кілька швидких і простих кроків.\n\n:::note\n\nДля початку використання bloc у вас має бути встановлений\n[Dart SDK](https://dart.dev/get-dart) на вашому комп'ютері.\n\n:::\n\n1. Встановіть\n   [інструменти командного рядка bloc](https://pub.dev/packages/bloc_tools)\n\n   <InstallBlocToolsSnippet />\n\n1. Встановіть пакет [bloc_lint](https://pub.dev/packages/bloc_lint)\n\n   <InstallBlocLintSnippet />\n\n1. Додайте файл `analysis_options.yaml` у корінь вашого проєкту з\n   рекомендованими правилами\n\n   <BlocLintRecommendedAnalysisOptionsSnippet />\n\n1. Запустіть лінтер\n\n   <RunBlocLintInCurrentDirectorySnippet />\n\nОсь і все 🎉\n\nПродовжуйте читання для більш детального вивчення налаштування та кастомізації\nлінтера bloc.\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint/installation.mdx",
    "content": "---\ntitle: Встановлення лінтера\ndescription: Встановлення лінтера bloc.\nsidebar:\n  order: 2\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport Card from '~/components/landing/Card.astro';\nimport InstallBlocToolsSnippet from '~/components/lint/InstallBlocToolsSnippet.astro';\nimport BlocToolsLintHelpOutputSnippet from '~/components/lint/BlocToolsLintHelpOutputSnippet.astro';\nimport InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.astro';\nimport BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro';\nimport BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintMultipleRecommendedAnalysisOptionsSnippet.astro';\n\n## Інструменти командного рядка\n\nДля використання лінтера з командного рядка встановіть\n[`package:bloc_tools`](https://pub.dev/packages/bloc_tools) за допомогою\nнаступної команди:\n\n<InstallBlocToolsSnippet />\n\nПісля встановлення інструментів командного рядка bloc ви можете запустити лінтер\nbloc за допомогою команди `bloc lint`:\n\n<BlocToolsLintHelpOutputSnippet />\n\n## Рекомендований набір правил\n\nДля встановлення рекомендованого набору правил лінтера встановіть\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) як dev-залежність за\nдопомогою наступної команди:\n\n<InstallBlocLintSnippet />\n\nПотім додайте файл `analysis_options.yaml` у корінь вашого проєкту з\nрекомендованим набором правил:\n\n<BlocLintRecommendedAnalysisOptionsSnippet />\n\nЗа потреби ви можете включити кілька наборів правил, визначивши їх у вигляді\nсписку:\n\n<BlocLintMultipleRecommendedAnalysisOptionsSnippet />\n\n## Інтеграції з IDE\n\nНаступні IDE офіційно підтримують лінтер bloc та мовний сервер для надання\nмиттєвої діагностики безпосередньо у вашій IDE.\n\n<CardGrid>\n\t<Card title=\"VSCode\" icon=\"vscode\">\n\t\tПідтримка у [Bloc VSCode\n\t\tExtension](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n\t\tдоступна починаючи з версії v6.8.0.\n\t</Card>\n\t<Card title=\"IntelliJ\" icon=\"jetbrains\">\n\t\tПідтримка у [Bloc IntelliJ\n\t\tPlugin](https://plugins.jetbrains.com/plugin/12129-bloc) доступна починаючи\n\t\tз версії v4.1.0.\n\t</Card>\n</CardGrid>\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/avoid_build_context_extensions.mdx",
    "content": "---\ntitle: Уникайте розширень BuildContext\ndescription: Правило avoid_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nУникайте використання розширень `BuildContext` для доступу до екземплярів `Bloc`\nабо `Cubit`.\n\n:::note\n\nЦе правило лінтера було введено у версії `0.3.0` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обґрунтування\n\nДля узгодженості та заради явності краще використовувати безпосередньо базові\nметоди замість розширень `BuildContext`. Це також корисно для тестування,\nоскільки неможливо замокати метод розширення.\n\n| розширення       | явний метод                                                          |\n| ---------------- | -------------------------------------------------------------------- |\n| `context.read`   | `BlocProvider.of<Bloc>(context, listen: false)`                      |\n| `context.watch`  | `BlocBuilder<Bloc, State>(...)` або `BlocProvider.of<Bloc>(context)` |\n| `context.select` | `BlocSelector<Bloc, State>(...)`                                     |\n\n## Приклади\n\n**Уникайте** використання розширень `BuildContext` для взаємодії з екземплярами\n`Bloc` або `Cubit`.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `avoid_build_context_extensions`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/avoid_flutter_imports.mdx",
    "content": "---\ntitle: Уникайте імпортів Flutter\ndescription: Правило лінтера bloc avoid_flutter_imports.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_flutter_imports/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nУникайте введення залежностей від Flutter у компонентах бізнес-логіки\n(екземплярах `Bloc` або `Cubit`).\n\n## Обґрунтування\n\nРозділення застосунку на шари є ключовою частиною побудови підтримуваної кодової\nбази та допомагає розробникам ітерувати швидко й впевнено. Кожен шар повинен\nмати єдину відповідальність і бути здатним функціонувати та тестуватися\nізольовано. Це дозволяє обмежувати зміни конкретними шарами, мінімізуючи вплив\nна весь застосунок.\n\nУ результаті компоненти бізнес-логіки зазвичай повинні керувати станом функцій і\nбути відокремленими від шару користувацького інтерфейсу. Події повинні надходити\nдо компонентів бізнес-логіки з шару UI, а стан повинен витікати з шару\nбізнес-логіки у шар UI.\n\nЗбереження компонентів бізнес-логіки відокремленими від Flutter надає можливість\nповторно використовувати бізнес-логіку на кількох платформах/фреймворках\n(наприклад, Flutter, AngularDart, Jaspr тощо).\n\n## Приклади\n\n**НЕ ІМПОРТУЙТЕ** Flutter у ваших компонентах бізнес-логіки.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `avoid_flutter_imports`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_flutter_imports\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/avoid_public_bloc_methods.mdx",
    "content": "---\ntitle: Уникайте публічних методів Bloc\ndescription: Правило avoid_public_bloc_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_bloc_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nУникайте надання публічних методів в екземплярах `Bloc`.\n\n## Обґрунтування\n\nБлоки реагують на вхідні події та видають вихідні стани. У результаті\nрекомендований спосіб взаємодії з екземпляром bloc — через метод `add`. У\nбільшості випадків немає потреби створювати додаткові абстракції поверх API\n`add`.\n\n![Архітектура Bloc](~/assets/concepts/bloc_architecture_full.png)\n\n## Приклади\n\n**НЕ НАДАВАЙТЕ** публічні методи в екземплярах bloc.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `avoid_public_bloc_methods`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_bloc_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/avoid_public_fields.mdx",
    "content": "---\ntitle: Уникайте публічних полів\ndescription: Правило avoid_public_fields.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/avoid_public_fields/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nУникайте надання публічних полів в екземплярах `Bloc` та `Cubit`.\n\n## Обґрунтування\n\nКомпоненти бізнес-логіки підтримують власний `state` та видають зміни стану\nчерез API `emit`. У результаті весь публічно доступний стан повинен бути наданий\nчерез об'єкт `state`.\n\n## Приклади\n\n**НЕ НАДАВАЙТЕ** публічні поля в екземплярах bloc та cubit.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `avoid_public_fields`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"avoid_public_fields\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/prefer_bloc.mdx",
    "content": "---\ntitle: Надавайте перевагу Bloc\ndescription: Правило prefer_bloc.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_bloc/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nНадавайте перевагу використанню екземплярів `Bloc` замість екземплярів `Cubit`.\n\n## Обґрунтування\n\nЦе правило є суто стилістичним. У деяких випадках команди можуть надавати\nперевагу стандартизації використання лише екземплярів `Bloc` у всьому застосунку\nзаради узгодженості.\n\n:::tip\n\nДізнайтеся більше про переваги `Bloc` у розділі\n[Основні концепції](/uk/bloc-concepts/#переваги-bloc).\n\n:::\n\n## Приклади\n\n**Уникайте** використання екземплярів `Cubit`.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `prefer_bloc`, додайте його до `analysis_options.yaml` у\nрозділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_bloc\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/prefer_build_context_extensions.mdx",
    "content": "---\ntitle: Надавайте перевагу розширенням BuildContext\ndescription: Правило prefer_build_context_extensions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nНадавайте перевагу використанню розширень `BuildContext` для доступу до\nекземпляра `Bloc` або `Repository`.\n\n:::note\n\nЦе правило лінтера було введено у версії `0.3.2` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обґрунтування\n\nДля узгодженості надавайте перевагу використанню розширень `BuildContext`, таких\nяк `context.read`, `context.watch` та `context.select`, замість\n`BlocProvider.of`, `RepositoryProvider.of`, `BlocBuilder` або `BlocSelector`.\n\n| явний метод                                                          | розширення            |\n| -------------------------------------------------------------------- | --------------------- |\n| `BlocProvider.of<Bloc>(context, listen: false)`                      | `context.read<Bloc>`  |\n| `BlocBuilder<Bloc, State>(...)` або `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |\n| `BlocSelector<Bloc, State>(...)`                                     | `context.select`      |\n\n## Приклади\n\n**Уникайте** використання `BlocProvider.of<T>(context)` для доступу до\nекземпляра `Bloc`.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `prefer_build_context_extensions`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_build_context_extensions\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/prefer_cubit.mdx",
    "content": "---\ntitle: Надавайте перевагу Cubit\ndescription: Правило prefer_cubit.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_cubit/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n</div>\n\nНадавайте перевагу використанню екземплярів `Cubit` замість екземплярів `Bloc`.\n\n## Обґрунтування\n\nЦе правило є суто стилістичним. У деяких випадках команди можуть надавати\nперевагу стандартизації використання лише екземплярів `Cubit` у всьому\nзастосунку заради узгодженості.\n\n:::tip\n\nДізнайтеся більше про переваги `Cubit` у розділі\n[Основні концепції](/uk/bloc-concepts/#переваги-cubit).\n\n:::\n\n## Приклади\n\n**Уникайте** використання екземплярів `Bloc`.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `prefer_cubit`, додайте його до `analysis_options.yaml` у\nрозділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_cubit\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/prefer_file_naming_conventions.mdx",
    "content": "---\ntitle: Надавайте перевагу конвенціям іменування файлів\ndescription: Правило prefer_file_naming_conventions.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_file_naming_conventions/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nНадавайте перевагу дотриманню конвенцій іменування файлів.\n\n:::note\n\nЦе правило лінтера було введено у версії `0.3.0` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обґрунтування\n\nДля узгодженості, простоти обслуговування та розділення відповідальності краще\nвизначати екземпляри bloc та cubit у їхніх відповідних файлах Dart замість\nвбудовування їх безпосередньо.\n\n:::tip\n\nРозгляньте можливість використання команди `bloc new <component>` з пакета\n[package:bloc_tools](https://pub.dev/packages/bloc_tools) для швидкого та\nпослідовного генерування нових екземплярів bloc/cubit.\n\n:::\n\n## Приклади\n\n**Надавайте перевагу** оголошенню екземплярів bloc/cubit у їхніх власних\nвідповідних файлах.\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `prefer_file_naming_conventions`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_file_naming_conventions\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/lint-rules/prefer_void_public_cubit_methods.mdx",
    "content": "---\ntitle: Надавайте перевагу void публічним методам Cubit\ndescription: Правило prefer_void_public_cubit_methods.\n---\n\nimport { Badge } from '@astrojs/starlight/components';\nimport EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';\nimport BadSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/BadSnippet.mdx';\nimport GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_methods/GoodSnippet.astro';\n\n<div class=\"badges\">\n\t<Badge text=\"new\" />\n\t<Badge text=\"dart\" variant=\"note\" />\n\t<Badge text=\"recommended\" variant=\"success\" />\n</div>\n\nНадавайте перевагу void публічним методам в екземплярах `Cubit`.\n\n:::note\n\nЦе правило лінтера було введено у версії `0.2.0-dev.2` пакета\n[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)\n\n:::\n\n## Обґрунтування\n\nПублічні методи в екземплярах `Cubit` повинні використовуватися для сповіщення\n`Cubit` та ініціювання змін стану через метод `emit`. Якщо викликаючій стороні\nпотрібен доступ до будь-якої інформації про стан, вона повинна отримувати її з\n`state`.\n\n:::note\n\nНаступні правила є пов'язаними і зазвичай вмикаються в комбінації з\n`prefer_void_public_cubit_methods`.\n\n- [`avoid_public_bloc_methods`](/uk/lint-rules/avoid_public_bloc_methods)\n- [`avoid_public_fields`](/uk/lint-rules/avoid_public_fields)\n\n:::\n\n## Приклади\n\n**Уникайте** не-void публічних методів в екземплярах `Cubit`.\n\n**ПОГАНО**:\n\n<BadSnippet />\n\n**ДОБРЕ**:\n\n<GoodSnippet />\n\n## Увімкнення\n\nЩоб увімкнути правило `prefer_void_public_cubit_methods`, додайте його до\n`analysis_options.yaml` у розділі `bloc` > `rules`:\n\n<EnableRuleSnippet name=\"prefer_void_public_cubit_methods\" />\n"
  },
  {
    "path": "docs/src/content/docs/uk/migration.mdx",
    "content": "---\ntitle: Посібник з міграції\ndescription: Мігруйте на останню стабільну версію Bloc.\n---\n\nimport { Code, Tabs, TabItem } from '@astrojs/starlight/components';\n\n:::tip\n\nБудь ласка, зверніться до\n[журналу релізів](https://github.com/felangel/bloc/releases) для отримання\nдодаткової інформації про те, що змінилося в кожному релізі.\n\n:::\n\n## v10.0.0\n\n### `package:bloc_test`\n\n#### ❗✨ Відокремлення `blocTest` від `BlocBase`\n\n:::note[Що змінилося?]\n\nУ bloc_test v10.0.0 API `blocTest` більше не тісно пов'язаний з `BlocBase`.\n\n:::\n\n##### Обґрунтування\n\n`blocTest` повинен використовувати основні інтерфейси bloc, коли це можливо, для\nпідвищення гнучкості та можливості повторного використання. Раніше це було\nнеможливо, оскільки `BlocBase` реалізовував `StateStreamableSource`, чого було\nнедостатньо для `blocTest` через внутрішню залежність від API `emit`.\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Підтримка WebAssembly\n\n:::note[Що змінилося?]\n\nУ hydrated_bloc v10.0.0 було додано підтримку компіляції у WebAssembly (wasm).\n\n:::\n\n##### Обґрунтування\n\nРаніше було неможливо компілювати додатки у wasm при використанні\n`hydrated_bloc`. У v10.0.0 пакет було перероблено для підтримки компіляції у\nwasm.\n\n**v9.x.x**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n**v10.x.x**\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n```\n\n## v9.0.0\n\n### `package:bloc`\n\n#### ❗🧹 Видалення застарілих API\n\n:::note[Що змінилося?]\n\nУ bloc v9.0.0 всі раніше застарілі API були видалені.\n\n:::\n\n##### Короткий виклад\n\n- `BlocOverrides` видалено на користь `Bloc.observer` та `Bloc.transformer`\n\n#### ❗✨ Введення нового інтерфейсу `EmittableStateStreamableSource`\n\n:::note[Що змінилося?]\n\nУ bloc v9.0.0 було введено новий основний інтерфейс\n`EmittableStateStreamableSource`.\n\n:::\n\n##### Обґрунтування\n\n`package:bloc_test` раніше був тісно пов'язаний з `BlocBase`. Інтерфейс\n`EmittableStateStreamableSource` було введено для того, щоб дозволити `blocTest`\nвідокремитися від конкретної реалізації `BlocBase`.\n\n### `package:hydrated_bloc`\n\n#### ✨ Повернення API `HydratedBloc.storage`\n\n:::note[Що змінилося?]\n\nУ hydrated_bloc v9.0.0 `HydratedBlocOverrides` було видалено на користь API\n`HydratedBloc.storage`.\\*\\*\n\n:::\n\n##### Обґрунтування\n\nЗверніться до\n[обґрунтування повернення перевизначень Bloc.observer та Bloc.transformer](/uk/migration#-повернення-api-blocobserver-та-bloctransformer).\n\n**v8.x.x**\n\n```dart\nFuture<void> main() async {\n  final storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  HydratedBlocOverrides.runZoned(\n    () => runApp(App()),\n    storage: storage,\n  );\n}\n```\n\n**v9.0.0**\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorage.webStorageDirectory\n        : await getTemporaryDirectory(),\n  );\n  runApp(App());\n}\n```\n\n## v8.1.0\n\n### `package:bloc`\n\n#### ✨ Повернення API `Bloc.observer` та `Bloc.transformer`\n\n:::note[Що змінилося?]\n\nУ bloc v8.1.0 `BlocOverrides` було оголошено застарілим на користь API\n`Bloc.observer` та `Bloc.transformer`.\n\n:::\n\n##### Обґрунтування\n\nAPI `BlocOverrides` було введено у v8.0.0 у спробі підтримати обмеження області\nдії конфігурацій, специфічних для bloc, таких як `BlocObserver`,\n`EventTransformer` та `HydratedStorage`. У чистих додатках Dart зміни працювали\nдобре; однак у додатках Flutter новий API спричинив більше проблем, ніж вирішив.\n\nAPI `BlocOverrides` було натхненне подібними API у Flutter/Dart:\n\n- [HttpOverrides](https://api.flutter.dev/flutter/dart-io/HttpOverrides-class.html)\n- [IOOverrides](https://api.flutter.dev/flutter/dart-io/IOOverrides-class.html)\n\n**Проблеми**\n\nХоча це не було основною причиною цих змін, API `BlocOverrides` привніс\nдодаткову складність для розробників. Окрім збільшення кількості вкладеності та\nрядків коду, необхідних для досягнення того ж ефекту, API `BlocOverrides`\nвимагав від розробників глибокого розуміння\n[Zones](https://api.dart.dev/stable/2.17.6/dart-async/Zone-class.html) у Dart.\n`Zones` -- це не зручна для початківців концепція, і нерозуміння того, як\nпрацюють Zones, може призвести до появи помилок (таких як неініціалізовані\nспостерігачі, трансформери, екземпляри сховища).\n\nНаприклад, багато розробників мали б щось на кшталт:\n\n```dart\nvoid main() {\n  WidgetsFlutterBinding.ensureInitialized();\n  BlocOverrides.runZoned(...);\n}\n```\n\nНаведений вище код, хоча й виглядає нешкідливим, насправді може призвести до\nбагатьох важко відстежуваних помилок. Яка б зона не викликала первісно\n`WidgetsFlutterBinding.ensureInitialized`, буде зоною, в якій обробляються події\nжестів (наприклад, колбеки `onTap`, `onPressed`) через\n`GestureBinding.initInstances`. Це лише одна з багатьох проблем, спричинених\nвикористанням `zoneValues`.\n\nКрім того, Flutter робить багато речей за лаштунками, які включають\nрозгалуження/маніпуляцію Zones (особливо при запуску тестів), що може призвести\nдо неочікуваної поведінки (і у багатьох випадках до поведінки, яка знаходиться\nпоза контролем розробника -- див. проблеми нижче).\n\nЧерез використання\n[runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html), перехід на\nAPI `BlocOverrides` призвів до виявлення кількох помилок/обмежень у Flutter\n(зокрема у віджетних та інтеграційних тестах):\n\n- https://github.com/flutter/flutter/issues/96939\n- https://github.com/flutter/flutter/issues/94123\n- https://github.com/flutter/flutter/issues/93676\n\nякі вплинули на багатьох розробників, що використовують бібліотеку bloc:\n\n- https://github.com/felangel/bloc/issues/3394\n- https://github.com/felangel/bloc/issues/3350\n- https://github.com/felangel/bloc/issues/3319\n\n**v8.0.x**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\n**v8.1.0**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n## v8.0.0\n\n### `package:bloc`\n\n#### ❗✨ Введення нового API `BlocOverrides`\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 `Bloc.observer` та `Bloc.transformer` були видалені на користь API\n`BlocOverrides`.\n\n:::\n\n##### Обґрунтування\n\nПопередній API, який використовувався для перевизначення `BlocObserver` та\n`EventTransformer` за замовчуванням, покладався на глобальний сінглтон як для\n`BlocObserver`, так і для `EventTransformer`.\n\nУ результаті було неможливо:\n\n- Мати кілька реалізацій `BlocObserver` або `EventTransformer`, обмежених\n  різними частинами додатку\n- Мати перевизначення `BlocObserver` або `EventTransformer`, обмежені пакетом\n  - Якщо пакет залежав від `package:bloc` і реєстрував свій власний\n    `BlocObserver`, будь-який споживач пакету повинен був би або перезаписати\n    `BlocObserver` пакету, або звітувати перед `BlocObserver` пакету.\n\nТакож було складніше тестувати через спільний глобальний стан у тестах.\n\nBloc v8.0.0 вводить клас `BlocOverrides`, який дозволяє розробникам\nперевизначати `BlocObserver` та/або `EventTransformer` для конкретної `Zone`, а\nне покладатися на глобальний змінний сінглтон.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  Bloc.observer = CustomBlocObserver();\n  Bloc.transformer = customEventTransformer();\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  BlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    blocObserver: CustomBlocObserver(),\n    eventTransformer: customEventTransformer(),\n  );\n}\n```\n\nЕкземпляри `Bloc` будуть використовувати `BlocObserver` та/або\n`EventTransformer` для поточної `Zone` через `BlocOverrides.current`. Якщо для\nзони немає `BlocOverrides`, вони будуть використовувати існуючі внутрішні\nзначення за замовчуванням (жодних змін у поведінці/функціональності).\n\nЦе дозволяє кожній `Zone` функціонувати незалежно зі своїми `BlocOverrides`.\n\n```dart\nBlocOverrides.runZoned(\n  () {\n    // BlocObserverA та eventTransformerA\n    final overrides = BlocOverrides.current;\n\n    // Bloc'и в цій зоні звітують перед BlocObserverA\n    // та використовують eventTransformerA як трансформер за замовчуванням.\n    // ...\n\n    // Пізніше...\n    BlocOverrides.runZoned(\n      () {\n        // BlocObserverB та eventTransformerB\n        final overrides = BlocOverrides.current;\n\n        // Bloc'и в цій зоні звітують перед BlocObserverB\n        // та використовують eventTransformerB як трансформер за замовчуванням.\n        // ...\n      },\n      blocObserver: BlocObserverB(),\n      eventTransformer: eventTransformerB(),\n    );\n  },\n  blocObserver: BlocObserverA(),\n  eventTransformer: eventTransformerA(),\n);\n```\n\n#### ❗✨ Покращення обробки та звітування про помилки\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 `BlocUnhandledErrorException` видалено. Крім того, будь-які\nнеперехоплені виключення завжди повідомляються в `onError` та пробрасуються\nповторно (незалежно від режиму налагодження чи релізу). API `addError`\nповідомляє про помилки в `onError`, але не розглядає повідомлені помилки як\nнеперехоплені виключення.\n\n:::\n\n##### Обґрунтування\n\nМета цих змін:\n\n- зробити внутрішні необроблені виключення надзвичайно очевидними, зберігаючи\n  при цьому функціональність bloc\n- підтримувати `addError` без порушення потоку керування\n\nРаніше обробка та звітування про помилки відрізнялися залежно від того, чи\nпрацював додаток у режимі налагодження чи релізу. Крім того, помилки,\nповідомлені через `addError`, розглядалися як неперехоплені виключення у режимі\nналагодження, що призводило до поганого досвіду розробника при використанні API\n`addError` (особливо при написанні модульних тестів).\n\nУ v8.0.0 `addError` можна безпечно використовувати для повідомлення про помилки,\nа `blocTest` можна використовувати для перевірки того, що помилки\nповідомляються. Всі помилки, як і раніше, повідомляються в `onError`, однак\nповторно пробрасуються лише неперехоплені виключення (незалежно від режиму\nналагодження чи релізу).\n\n#### ❗🧹 Зробити `BlocObserver` абстрактним\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 `BlocObserver` було перетворено на `abstract` клас, що означає, що\nекземпляр `BlocObserver` не може бути створений.\n\n:::\n\n##### Обґрунтування\n\n`BlocObserver` призначався для використання як інтерфейс. Оскільки реалізація\nAPI за замовчуванням -- це no-ops, `BlocObserver` тепер є `abstract` класом, щоб\nчітко показати, що клас призначений для розширення, а не для прямого створення\nекземпляру.\n\n**v7.x.x**\n\n```dart\nvoid main() {\n  // Було можливо створити екземпляр базового класу.\n  final observer = BlocObserver();\n}\n```\n\n**v8.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {...}\n\nvoid main() {\n  // Неможливо створити екземпляр базового класу.\n  final observer = BlocObserver(); // ПОМИЛКА\n\n  // Замість цього розширте `BlocObserver`.\n  final observer = MyBlocObserver(); // OK\n}\n```\n\n#### ❗✨ `add` викидає `StateError`, якщо Bloc закритий\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 виклик `add` на закритому bloc призведе до `StateError`.\n\n:::\n\n##### Обґрунтування\n\nРаніше було можливо викликати `add` на закритому bloc, і внутрішня помилка\nпроковтувалася, що ускладнювало налагодження того, чому додана подія не\nоброблялася. Щоб зробити цей сценарій більш видимим, у v8.0.0 виклик `add` на\nзакритому bloc викине `StateError`, який буде повідомлений як неперехоплене\nвиключення та переданий в `onError`.\n\n#### ❗✨ `emit` викидає `StateError`, якщо Bloc закритий\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 виклик `emit` всередині закритого bloc призведе до `StateError`.\n\n:::\n\n##### Обґрунтування\n\nРаніше було можливо викликати `emit` всередині закритого bloc, і жодної зміни\nстану не відбувалося, але також не було жодної вказівки на те, що пішло не так,\nщо ускладнювало налагодження. Щоб зробити цей сценарій більш видимим, у v8.0.0\nвиклик `emit` всередині закритого bloc викине `StateError`, який буде\nповідомлений як неперехоплене виключення та переданий в `onError`.\n\n#### ❗🧹 Видалення застарілих API\n\n:::note[Що змінилося?]\n\nУ bloc v8.0.0 всі раніше застарілі API були видалені.\n\n:::\n\n##### Короткий виклад\n\n- `mapEventToState` видалено на користь `on<Event>`\n- `transformEvents` видалено на користь API `EventTransformer`\n- typedef `TransitionFunction` видалено на користь API `EventTransformer`\n- `listen` видалено на користь `stream.listen`\n\n### `package:bloc_test`\n\n#### ✨ `MockBloc` та `MockCubit` більше не потребують `registerFallbackValue`\n\n:::note[Що змінилося?]\n\nУ bloc_test v9.0.0 розробникам більше не потрібно явно викликати\n`registerFallbackValue` при використанні `MockBloc` або `MockCubit`.\n\n:::\n\n##### Короткий виклад\n\n`registerFallbackValue` потрібен лише при використанні матчера `any()` з\n`package:mocktail` для користувацького типу. Раніше `registerFallbackValue` був\nнеобхідний для кожного `Event` та `State` при використанні `MockBloc` або\n`MockCubit`.\n\n**v8.x.x**\n\n```dart\nclass FakeMyEvent extends Fake implements MyEvent {}\nclass FakeMyState extends Fake implements MyState {}\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  setUpAll(() {\n    registerFallbackValue(FakeMyEvent());\n    registerFallbackValue(FakeMyState());\n  });\n\n  // Тести...\n}\n```\n\n**v9.0.0**\n\n```dart\nclass MyMockBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\n\nvoid main() {\n  // Тести...\n}\n```\n\n### `package:hydrated_bloc`\n\n#### ❗✨ Введення нового API `HydratedBlocOverrides`\n\n:::note[Що змінилося?]\n\nУ hydrated_bloc v8.0.0 `HydratedBloc.storage` було видалено на користь API\n`HydratedBlocOverrides`.\n\n:::\n\n##### Обґрунтування\n\nРаніше для перевизначення реалізації `Storage` використовувався глобальний\nсінглтон.\n\nУ результаті було неможливо мати кілька реалізацій `Storage`, обмежених різними\nчастинами додатку. Також було складніше тестувати через спільний глобальний стан\nу тестах.\n\n`HydratedBloc` v8.0.0 вводить клас `HydratedBlocOverrides`, який дозволяє\nрозробникам перевизначати `Storage` для конкретної `Zone`, а не покладатися на\nглобальний змінний сінглтон.\n\n**v7.x.x**\n\n```dart\nvoid main() async {\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  // ...\n}\n```\n\n**v8.0.0**\n\n```dart\nvoid main() {\n  final storage = await HydratedStorage.build(\n    storageDirectory: await getApplicationSupportDirectory(),\n  );\n\n  HydratedBlocOverrides.runZoned(\n    () {\n      // ...\n    },\n    storage: storage,\n  );\n}\n```\n\nЕкземпляри `HydratedBloc` будуть використовувати `Storage` для поточної `Zone`\nчерез `HydratedBlocOverrides.current`.\n\nЦе дозволяє кожній `Zone` функціонувати незалежно зі своїми `BlocOverrides`.\n\n## v7.2.0\n\n### `package:bloc`\n\n#### ✨ Введення нового API `on<Event>`\n\n:::note[Що змінилося?]\n\nУ bloc v7.2.0 `mapEventToState` було оголошено застарілим на користь\n`on<Event>`. `mapEventToState` буде видалено у bloc v8.0.0.\n\n:::\n\n##### Обґрунтування\n\nAPI `on<Event>` було введено в рамках\n[[Пропозиція] Замінити mapEventToState на on\\<Event\\> у Bloc](https://github.com/felangel/bloc/issues/2526).\nЧерез [проблему в Dart](https://github.com/dart-lang/sdk/issues/44616) не завжди\nочевидно, яким буде значення `state` при роботі з вкладеними асинхронними\nгенераторами (`async*`). Незважаючи на те, що існують способи обійти цю\nпроблему, одним з основних принципів бібліотеки bloc є передбачуваність. API\n`on<Event>` було створено, щоб зробити бібліотеку максимально безпечною для\nвикористання та усунути будь-яку невизначеність щодо змін стану.\n\n:::tip\n\nДля отримання додаткової інформації\n[прочитайте повну пропозицію](https://github.com/felangel/bloc/issues/2526).\n\n:::\n\n**Короткий виклад**\n\n`on<E>` дозволяє зареєструвати обробник подій для всіх подій типу `E`. За\nзамовчуванням події будуть оброблятися паралельно при використанні `on<E>`, на\nвідміну від `mapEventToState`, який обробляє події `послідовно`.\n\n**v7.1.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Stream<int> mapEventToState(CounterEvent event) async* {\n    if (event is Increment) {\n      yield state + 1;\n    }\n  }\n}\n```\n\n**v7.2.0**\n\n```dart\nabstract class CounterEvent {}\nclass Increment extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n:::note\n\nКожна зареєстрована функція `EventHandler` працює незалежно, тому важливо\nреєструвати обробники подій на основі типу трансформера, який ви хочете\nзастосувати.\n\n:::\n\nЯкщо ви хочете зберегти точно таку ж поведінку, як у v7.1.0, ви можете\nзареєструвати один обробник подій для всіх подій та застосувати трансформер\n`sequential`:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nclass MyBloc extends Bloc<MyEvent, MyState> {\n  MyBloc() : super(MyState()) {\n    on<MyEvent>(_onEvent, transformer: sequential())\n  }\n\n  FutureOr<void> _onEvent(MyEvent event, Emitter<MyState> emit) async {\n    // TODO: логіка тут...\n  }\n}\n```\n\nВи також можете перевизначити `EventTransformer` за замовчуванням для всіх\nbloc'ів у вашому додатку:\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nvoid main() {\n  Bloc.transformer = sequential<dynamic>();\n  ...\n}\n```\n\n#### ✨ Введення нового API `EventTransformer`\n\n:::note[Що змінилося?]\n\nУ bloc v7.2.0 `transformEvents` було оголошено застарілим на користь API\n`EventTransformer`. `transformEvents` буде видалено у bloc v8.0.0.\n\n:::\n\n##### Обґрунтування\n\nAPI `on<Event>` відкрив можливість надання користувацького трансформера подій\nдля кожного обробника подій. Було введено новий typedef `EventTransformer`, який\nдозволяє розробникам трансформувати вхідний потік подій для кожного обробника\nподій, а не вказувати один трансформер подій для всіх подій.\n\n**Короткий виклад**\n\n`EventTransformer` відповідає за прийняття вхідного потоку подій разом з\n`EventMapper` (ваш обробник події) та повернення нового потоку подій.\n\n```dart\ntypedef EventTransformer<Event> = Stream<Event> Function(Stream<Event> events, EventMapper<Event> mapper)\n```\n\n`EventTransformer` за замовчуванням обробляє всі події паралельно і виглядає\nприблизно так:\n\n```dart\nEventTransformer<E> concurrent<E>() {\n  return (events, mapper) => events.flatMap(mapper);\n}\n```\n\n:::tip\n\nОзнайомтеся з\n[package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) для\nотримання набору користувацьких трансформерів подій\n\n:::\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<MyEvent, MyState>> transformEvents(events, transitionFn) {\n  return events\n    .debounceTime(const Duration(milliseconds: 300))\n    .flatMap(transitionFn);\n}\n```\n\n**v7.2.0**\n\n```dart\n/// Визначте користувацький `EventTransformer`\nEventTransformer<MyEvent> debounce<MyEvent>(Duration duration) {\n  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);\n}\n\nMyBloc() : super(MyState()) {\n  /// Застосуйте користувацький `EventTransformer` до `EventHandler`\n  on<MyEvent>(_onEvent, transformer: debounce(const Duration(milliseconds: 300)))\n}\n```\n\n#### ⚠️ Застаріння API `transformTransitions`\n\n:::note[Що змінилося?]\n\nУ bloc v7.2.0 `transformTransitions` було оголошено застарілим на користь\nперевизначення API `stream`. `transformTransitions` буде видалено у bloc v8.0.0.\n\n:::\n\n##### Обґрунтування\n\nГеттер `stream` у `Bloc` спрощує перевизначення вихідного потоку станів, тому\nбільше немає необхідності підтримувати окремий API `transformTransitions`.\n\n**Короткий виклад**\n\n**v7.1.0**\n\n```dart\n@override\nStream<Transition<Event, State>> transformTransitions(\n  Stream<Transition<Event, State>> transitions,\n) {\n  return transitions.debounceTime(const Duration(milliseconds: 42));\n}\n```\n\n**v7.2.0**\n\n```dart\n@override\nStream<State> get stream => super.stream.debounceTime(const Duration(milliseconds: 42));\n```\n\n## v7.0.0\n\n### `package:bloc`\n\n#### ❗ Bloc та Cubit розширюють BlocBase\n\n##### Обґрунтування\n\nЯк розробник, відносини між bloc'ами та cubit'ами були дещо незручними. Коли\ncubit було вперше представлено, він починався як базовий клас для bloc'ів, що\nмало сенс, тому що він мав підмножину функціональності, а bloc'и просто\nрозширювали Cubit та визначали додаткові API. Це призвело до кількох недоліків:\n\n- Усі API повинні були б або бути перейменовані, щоб приймати cubit для\n  точності, або вони повинні були б бути залишені як bloc для послідовності,\n  навіть незважаючи на те, що ієрархічно це неточно\n  ([#1708](https://github.com/felangel/bloc/issues/1708),\n  [#1560](https://github.com/felangel/bloc/issues/1560)).\n\n- Cubit повинен був розширювати Stream та реалізовувати EventSink, щоб мати\n  спільну базу, на основі якої можна реалізувати віджети, такі як BlocBuilder,\n  BlocListener тощо ([#1429](https://github.com/felangel/bloc/issues/1429)).\n\nПізніше ми експериментували з інверсією відносин та зробили bloc базовим класом,\nщо частково вирішило перший пункт вище, але призвело до інших проблем:\n\n- API cubit роздутий через базові API bloc, такі як mapEventToState, add тощо\n  ([#2228](https://github.com/felangel/bloc/issues/2228))\n  - Розробники технічно можуть викликати ці API та ламати речі\n- Ми все ще маємо ту ж проблему з cubit, що розкриває весь API stream, як і\n  раніше ([#1429](https://github.com/felangel/bloc/issues/1429))\n\nЩоб вирішити ці проблеми, ми ввели базовий клас як для `Bloc`, так і для `Cubit`\nпід назвою `BlocBase`, щоб вищестоящі компоненти як і раніше могли взаємодіяти\nяк з екземплярами bloc, так і з cubit, але без розкриття всього API `Stream` та\n`EventSink` напряму.\n\n**Короткий виклад**\n\n**BlocObserver**\n\n**v6.1.x**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(Cubit cubit) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(Cubit cubit, Object event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(Cubit cubit) {...}\n}\n```\n\n**v7.0.0**\n\n```dart\nclass SimpleBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {...}\n\n  @override\n  void onEvent(Bloc bloc, Object event) {...}\n\n  @override\n  void onChange(BlocBase bloc, Object? event) {...}\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {...}\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}\n\n  @override\n  void onClose(BlocBase bloc) {...}\n}\n```\n\n**Bloc/Cubit**\n\n**v6.1.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.listen((state) {...});\n```\n\n**v7.0.0**\n\n```dart\nfinal bloc = MyBloc();\nbloc.stream.listen((state) {...});\n\nfinal cubit = MyCubit();\ncubit.stream.listen((state) {...});\n```\n\n### `package:bloc_test`\n\n#### ❗seed повертає функцію для підтримки динамічних значень\n\n##### Обґрунтування\n\nЩоб підтримувати змінне початкове значення, яке можна динамічно оновлювати в\n`setUp`, `seed` повертає функцію.\n\n**Короткий виклад**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  seed: MyState(),\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  seed: () => MyState(),\n  ...\n);\n```\n\n#### ❗expect повертає функцію для підтримки динамічних значень та включає підтримку матчерів\n\n##### Обґрунтування\n\nЩоб підтримувати змінне очікування, яке можна динамічно оновлювати в `setUp`,\n`expect` повертає функцію. `expect` також підтримує `Matchers`.\n\n**Короткий виклад**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  expect: [MyStateA(), MyStateB()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  expect: () => [MyStateA(), MyStateB()],\n  ...\n);\n\n// Це також може бути `Matcher`\nblocTest(\n  '...',\n  expect: () => contains(MyStateA()),\n  ...\n);\n```\n\n#### ❗errors повертає функцію для підтримки динамічних значень та включає підтримку матчерів\n\n##### Обґрунтування\n\nЩоб підтримувати змінні помилки, які можна динамічно оновлювати в `setUp`,\n`errors` повертає функцію. `errors` також підтримує `Matchers`.\n\n**Короткий виклад**\n\n**v7.x.x**\n\n```dart\nblocTest(\n  '...',\n  errors: [MyError()],\n  ...\n);\n```\n\n**v8.0.0**\n\n```dart\nblocTest(\n  '...',\n  errors: () => [MyError()],\n  ...\n);\n\n// Це також може бути `Matcher`\nblocTest(\n  '...',\n  errors: () => contains(MyError()),\n  ...\n);\n```\n\n#### ❗MockBloc та MockCubit\n\n##### Обґрунтування\n\nДля підтримки заглушок різних основних API `MockBloc` та `MockCubit`\nекспортуються як частина пакету `bloc_test`. Раніше `MockBloc` доводилося\nвикористовувати як для екземплярів `Bloc`, так і для `Cubit`, що було\nнеінтуїтивно.\n\n**Короткий виклад**\n\n**v7.x.x**\n\n```dart\nclass MockMyBloc extends MockBloc<MyState> implements MyBloc {}\nclass MockMyCubit extends MockBloc<MyState> implements MyBloc {}\n```\n\n**v8.0.0**\n\n```dart\nclass MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}\nclass MockMyCubit extends MockCubit<MyState> implements MyCubit {}\n```\n\n#### ❗Інтеграція з Mocktail\n\n##### Обґрунтування\n\nЧерез різні обмеження null-safe\n[package:mockito](https://pub.dev/packages/mockito), описані\n[тут](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing),\n[package:mocktail](https://pub.dev/packages/mocktail) використовується\n`MockBloc` та `MockCubit`. Це дозволяє розробникам продовжувати використовувати\nзнайомий API для моків без необхідності вручну писати заглушки або покладатися\nна генерацію коду.\n\n**Короткий виклад**\n\n**v7.x.x**\n\n```dart\nimport 'package:mockito/mockito.dart';\n\n...\n\nwhen(bloc.state).thenReturn(MyState());\nverify(bloc.add(any)).called(1);\n```\n\n**v8.0.0**\n\n```dart\nimport 'package:mocktail/mocktail.dart';\n\n...\n\nwhen(() => bloc.state).thenReturn(MyState());\nverify(() => bloc.add(any())).called(1);\n```\n\n> Будь ласка, зверніться до\n> [#347](https://github.com/dart-lang/mockito/issues/347), а також до\n> [документації mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail)\n> для отримання додаткової інформації.\n\n### `package:flutter_bloc`\n\n#### ❗ перейменування параметра `cubit` на `bloc`\n\n##### Обґрунтування\n\nУ результаті рефакторингу в `package:bloc` для введення `BlocBase`, який\nрозширюють `Bloc` та `Cubit`, параметри `BlocBuilder`, `BlocConsumer` та\n`BlocListener` були перейменовані з `cubit` на `bloc`, тому що віджети працюють\nз типом `BlocBase`. Це також додатково узгоджується з назвою бібліотеки та,\nсподіваємося, покращує читабельність.\n\n**Короткий виклад**\n\n**v6.1.x**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  ...\n)\n\nBlocListener(\n  cubit: myBloc,\n  ...\n)\n\nBlocConsumer(\n  cubit: myBloc,\n  ...\n)\n```\n\n**v7.0.0**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  ...\n)\n\nBlocListener(\n  bloc: myBloc,\n  ...\n)\n\nBlocConsumer(\n  bloc: myBloc,\n  ...\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗storageDirectory є обов'язковим при виклику HydratedStorage.build\n\n##### Обґрунтування\n\nЩоб зробити `package:hydrated_bloc` чистим пакетом Dart, залежність від\n[package:path_provider](https://pub.dev/packages/path_provider) була видалена, і\nпараметр `storageDirectory` при виклику `HydratedStorage.build` є обов'язковим і\nбільше не використовує `getTemporaryDirectory` за замовчуванням.\n\n**Короткий виклад**\n\n**v6.x.x**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n**v7.0.0**\n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\n...\n\nHydratedBloc.storage = await HydratedStorage.build(\n  storageDirectory: await getTemporaryDirectory(),\n);\n```\n\n## v6.1.0\n\n### `package:flutter_bloc`\n\n#### ❗context.bloc та context.repository застаріли на користь context.read та context.watch\n\n##### Обґрунтування\n\n`context.read`, `context.watch` та `context.select` були додані для узгодження з\nіснуючим API [provider](https://pub.dev/packages/provider), з яким знайомі\nбагато розробників, та для вирішення проблем, піднятих спільнотою. Щоб підвищити\nбезпеку коду та підтримувати узгодженість, `context.bloc` було оголошено\nзастарілим, тому що його можна замінити або `context.read`, або `context.watch`\nзалежно від того, чи використовується він безпосередньо всередині `build`.\n\n**context.watch**\n\n`context.watch` вирішує запит на наявність\n[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538), тому що ми\nможемо спостерігати за кількома bloc'ами в одному `Builder`, щоб відрендерити UI\nна основі кількох станів:\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // повернути Widget, який залежить від стану BlocA, BlocB та BlocC\n  }\n);\n```\n\n**context.select**\n\n`context.select` дозволяє розробникам рендерити/оновлювати UI на основі частини\nстану bloc та вирішує запит на наявність\n[простішого buildWhen](https://github.com/felangel/bloc/issues/1521).\n\n```dart\nfinal name = context.select((UserBloc bloc) => bloc.state.user.name);\n```\n\nНаведений вище фрагмент дозволяє нам отримати доступ та перебудувати віджет лише\nтоді, коли ім'я поточного користувача змінюється.\n\n**context.read**\n\nХоча здається, що `context.read` ідентичний `context.bloc`, є деякі тонкі, але\nзначні відмінності. Обидва дозволяють отримати доступ до bloc за допомогою\n`BuildContext` і не призводять до перебудов; однак `context.read` не може бути\nвикликаний безпосередньо всередині методу `build`. Є дві основні причини\nвикористовувати `context.bloc` всередині `build`:\n\n1. **Щоб отримати доступ до стану bloc**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nНаведене вище використання схильне до помилок, тому що віджет `Text` не буде\nперебудований, якщо стан bloc зміниться. У цьому сценарії слід використовувати\nабо `BlocBuilder`, або `context.watch`.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\nабо\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return BlocBuilder<MyBloc, MyState>(\n    builder: (context, state) => Text('$state'),\n  );\n}\n```\n\n:::note\n\nВикористання `context.watch` в корені методу `build` призведе до того, що весь\nвіджет буде перебудований при зміні стану bloc. Якщо весь віджет не потрібно\nперебудовувати, або використовуйте `BlocBuilder`, щоб обгорнути частини, які\nповинні перебудовуватися, використовуйте `Builder` з `context.watch` для\nобмеження перебудов, або розбийте віджет на менші віджети.\n\n:::\n\n2. **Щоб отримати доступ до bloc для додавання події**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\nНаведене вище використання неефективне, тому що воно призводить до пошуку bloc\nна кожній перебудові, коли bloc потрібен лише тоді, коли користувач натискає\n`ElevatedButton`. У цьому сценарії краще використовувати `context.read` для\nдоступу до bloc безпосередньо там, де він потрібен (у даному випадку в колбеці\n`onPressed`).\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n**Короткий виклад**\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final bloc = context.bloc<MyBloc>();\n  return ElevatedButton(\n    onPressed: () => bloc.add(MyEvent()),\n    ...\n  )\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return ElevatedButton(\n    onPressed: () => context.read<MyBloc>().add(MyEvent()),\n    ...\n  )\n}\n```\n\n?> Якщо доступ до bloc для додавання події, виконуйте доступ до bloc,\nвикористовуючи `context.read` у колбеці, де це необхідно.\n\n**v6.0.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.bloc<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n**v6.1.x**\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.watch<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n?> Використовуйте `context.watch` при доступі до стану bloc, щоб переконатися,\nщо віджет перебудовується при зміні стану.\n\n## v6.0.0\n\n### `package:bloc`\n\n#### ❗BlocObserver onError приймає Cubit\n\n##### Обґрунтування\n\nЧерез інтеграцію `Cubit`, `onError` тепер є спільним як для екземплярів `Bloc`,\nтак і для `Cubit`. Оскільки `Cubit` є базовим, `BlocObserver` буде приймати тип\n`Cubit`, а не тип `Bloc` у перевизначенні `onError`.\n\n**v5.x.x**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Bloc bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n  }\n}\n```\n\n**v6.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onError(Cubit cubit, Object error, StackTrace stackTrace) {\n    super.onError(cubit, error, stackTrace);\n  }\n}\n```\n\n#### ❗Bloc не емітить останній стан при підписці\n\n##### Обґрунтування\n\nЦю зміну було внесено для узгодження `Bloc` та `Cubit` із вбудованою поведінкою\n`Stream` у `Dart`. Крім того, відповідність старій поведінці в контексті `Cubit`\nпризвела до багатьох непередбачених побічних ефектів і в цілому ускладнила\nвнутрішні реалізації інших пакетів, таких як `flutter_bloc` та `bloc_test`, без\nнеобхідності (потребуючи `skip(1)` тощо).\n\n**v5.x.x**\n\n```dart\nfinal bloc = MyBloc();\nbloc.listen(print);\n```\n\nРаніше наведений вище фрагмент виводив початковий стан bloc, за яким слідували\nподальші зміни стану.\n\n**v6.x.x**\n\nУ v6.0.0 наведений вище фрагмент не виводить початковий стан і виводить лише\nподальші зміни стану. Попередню поведінку можна досягти наступним чином:\n\n```dart\nfinal bloc = MyBloc();\nprint(bloc.state);\nbloc.listen(print);\n```\n\n?> **Примітка**: Ця зміна вплине лише на код, який залежить від прямих підписок\nна bloc. При використанні `BlocBuilder`, `BlocListener` або `BlocConsumer` не\nбуде помітної зміни в поведінці.\n\n### `package:bloc_test`\n\n#### ❗MockBloc потребує лише тип State\n\n##### Обґрунтування\n\nЦе не обов'язково і усуває зайвий код, а також робить `MockBloc` сумісним з\n`Cubit`.\n\n**v5.x.x**\n\n```dart\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n```\n\n**v6.0.0**\n\n```dart\nclass MockCounterBloc extends MockBloc<int> implements CounterBloc {}\n```\n\n#### ❗whenListen потребує лише тип State\n\n##### Обґрунтування\n\nЦе не обов'язково і усуває зайвий код, а також робить `whenListen` сумісним з\n`Cubit`.\n\n**v5.x.x**\n\n```dart\nwhenListen<CounterEvent,int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n**v6.0.0**\n\n```dart\nwhenListen<int>(bloc, Stream.fromIterable([0, 1, 2, 3]));\n```\n\n#### ❗blocTest не потребує тип Event\n\n##### Обґрунтування\n\nЦе не обов'язково і усуває зайвий код, а також робить `blocTest` сумісним з\n`Cubit`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [1] when increment is called',\n  build: () async => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [1] when increment is called',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[1],\n);\n```\n\n#### ❗blocTest skip за замовчуванням дорівнює 0\n\n##### Обґрунтування\n\nОскільки екземпляри `bloc` та `cubit` більше не будуть емітити останній стан для\nнових підписок, більше не було необхідності встановлювати `skip` за\nзамовчуванням рівним `1`.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [0] when skip is 0',\n  build: () async => CounterBloc(),\n  skip: 0,\n  expect: const <int>[0],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [] when skip is 0',\n  build: () => CounterBloc(),\n  skip: 0,\n  expect: const <int>[],\n);\n```\n\nПочатковий стан bloc або cubit можна протестувати наступним чином:\n\n```dart\ntest('initial state is correct', () {\n  expect(MyBloc().state, InitialState());\n});\n```\n\n#### ❗blocTest робить build синхронним\n\n##### Обґрунтування\n\nРаніше `build` було зроблено `async`, щоб можна було виконати різні підготовчі\nдії для приведення тестованого bloc у певний стан. Це більше не потрібно і також\nвирішує кілька проблем через додаткову затримку між build та підпискою\nвсередині. Замість того, щоб виконувати асинхронну підготовку для приведення\nbloc у бажаний стан, ми тепер можемо встановити стан bloc, зв'язавши `emit` з\nбажаним станом.\n\n**v5.x.x**\n\n```dart\nblocTest<CounterBloc, CounterEvent, int>(\n  'emits [2] when increment is added',\n  build: () async {\n    final bloc = CounterBloc();\n    bloc.add(CounterEvent.increment);\n    await bloc.take(2);\n    return bloc;\n  }\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n**v6.0.0**\n\n```dart\nblocTest<CounterBloc, int>(\n  'emits [2] when increment is added',\n  build: () => CounterBloc()..emit(1),\n  act: (bloc) => bloc.add(CounterEvent.increment),\n  expect: const <int>[2],\n);\n```\n\n:::note\n\n`emit` видимий лише для тестування і ніколи не повинен використовуватися поза\nтестами.\n\n:::\n\n### `package:flutter_bloc`\n\n#### ❗параметр bloc у BlocBuilder перейменовано на cubit\n\n##### Обґрунтування\n\nЩоб зробити `BlocBuilder` сумісним з екземплярами `bloc` та `cubit`, параметр\n`bloc` було перейменовано на `cubit` (оскільки `Cubit` є базовим класом).\n\n**v5.x.x**\n\n```dart\nBlocBuilder(\n  bloc: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocBuilder(\n  cubit: myBloc,\n  builder: (context, state) {...}\n)\n```\n\n#### ❗параметр bloc у BlocListener перейменовано на cubit\n\n##### Обґрунтування\n\nЩоб зробити `BlocListener` сумісним з екземплярами `bloc` та `cubit`, параметр\n`bloc` було перейменовано на `cubit` (оскільки `Cubit` є базовим класом).\n\n**v5.x.x**\n\n```dart\nBlocListener(\n  bloc: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocListener(\n  cubit: myBloc,\n  listener: (context, state) {...}\n)\n```\n\n#### ❗параметр bloc у BlocConsumer перейменовано на cubit\n\n##### Обґрунтування\n\nЩоб зробити `BlocConsumer` сумісним з екземплярами `bloc` та `cubit`, параметр\n`bloc` було перейменовано на `cubit` (оскільки `Cubit` є базовим класом).\n\n**v5.x.x**\n\n```dart\nBlocConsumer(\n  bloc: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n**v6.0.0**\n\n```dart\nBlocConsumer(\n  cubit: myBloc,\n  listener: (context, state) {...},\n  builder: (context, state) {...}\n)\n```\n\n---\n\n## v5.0.0\n\n### `package:bloc`\n\n#### ❗initialState було видалено\n\n##### Обґрунтування\n\nЯк розробник, необхідність перевизначати `initialState` при створенні bloc\nстановить дві основні проблеми:\n\n- `initialState` bloc може бути динамічним і також може бути вказаний пізніше\n  (навіть поза самим bloc). У деякому сенсі це можна розглядати як витік\n  внутрішньої інформації bloc на рівень UI.\n- Це багатослівно.\n\n**v4.x.x**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  @override\n  int get initialState => 0;\n\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n\n?> Для отримання додаткової інформації ознайомтеся з\n[#1304](https://github.com/felangel/bloc/issues/1304)\n\n#### ❗BlocDelegate перейменовано на BlocObserver\n\n##### Обґрунтування\n\nНазва `BlocDelegate` не була точним описом ролі, яку відігравав клас.\n`BlocDelegate` передбачає, що клас відіграє активну роль, тоді як насправді\nпередбачувана роль `BlocDelegate` полягала у тому, щоб бути пасивним\nкомпонентом, який просто спостерігає за всіма bloc'ами у додатку.\n\n:::note\n\nВ ідеалі не повинно бути жодних користувацьких функцій або можливостей, що\nобробляються всередині `BlocObserver`.\n\n:::\n\n**v4.x.x**\n\n```dart\nclass MyBlocDelegate extends BlocDelegate {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  ...\n}\n```\n\n#### ❗BlocSupervisor було видалено\n\n##### Обґрунтування\n\n`BlocSupervisor` був ще одним компонентом, про який розробникам потрібно було\nзнати та взаємодіяти з ним виключно для вказівки користувацького `BlocDelegate`.\nЗі зміною на `BlocObserver` ми відчули, що це покращило досвід розробника,\nвстановивши спостерігача безпосередньо на самому bloc.\n\n?> Ця зміна також дозволила нам відокремити інші доповнення до bloc, такі як\n`HydratedStorage`, від `BlocObserver`.\n\n**v4.x.x**\n\n```dart\nBlocSupervisor.delegate = MyBlocDelegate();\n```\n\n**v5.0.0**\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\n### `package:flutter_bloc`\n\n#### ❗condition у BlocBuilder перейменовано на buildWhen\n\n##### Обґрунтування\n\nПри використанні `BlocBuilder` ми раніше могли вказати `condition`, щоб\nвизначити, чи повинен `builder` перебудовуватися.\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати builder\n  },\n  builder: (context, state) {...}\n)\n```\n\nНазва `condition` не дуже самоочевидна або зрозуміла, і, що важливіше, при\nвзаємодії з `BlocConsumer` API став неузгодженим, тому що розробники можуть\nнадати дві умови (одну для `builder` і одну для `listener`). У результаті API\n`BlocConsumer` розкрив `buildWhen` та `listenWhen`\n\n```dart\nBlocConsumer<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати listener\n  },\n  listener: (context, state) {...},\n  buildWhen: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати builder\n  },\n  builder: (context, state) {...},\n)\n```\n\nЩоб узгодити API та забезпечити більш послідовний досвід розробника, `condition`\nбуло перейменовано на `buildWhen`.\n\n**v4.x.x**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  condition: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocBuilder<MyBloc, MyState>(\n  buildWhen: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати builder\n  },\n  builder: (context, state) {...}\n)\n```\n\n#### ❗condition у BlocListener перейменовано на listenWhen\n\n##### Обґрунтування\n\nЗ тих самих причин, що описані вище, умову `BlocListener` також було\nперейменовано.\n\n**v4.x.x**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  condition: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n**v5.0.0**\n\n```dart\nBlocListener<MyBloc, MyState>(\n  listenWhen: (previous, current) {\n    // повернути true/false, щоб визначити, чи потрібно викликати listener\n  },\n  listener: (context, state) {...}\n)\n```\n\n### `package:hydrated_bloc`\n\n#### ❗HydratedStorage та HydratedBlocStorage перейменовані\n\n##### Обґрунтування\n\nЩоб покращити повторне використання коду між\n[hydrated_bloc](https://pub.dev/packages/hydrated_bloc) та\n[hydrated_cubit](https://pub.dev/packages/hydrated_cubit), конкретна реалізація\nсховища за замовчуванням була перейменована з `HydratedBlocStorage` на\n`HydratedStorage`. Крім того, інтерфейс `HydratedStorage` було перейменовано з\n`HydratedStorage` на `Storage`.\n\n**v4.0.0**\n\n```dart\nclass MyHydratedStorage implements HydratedStorage {\n  ...\n}\n```\n\n**v5.0.0**\n\n```dart\nclass MyHydratedStorage implements Storage {\n  ...\n}\n```\n\n#### ❗HydratedStorage відокремлено від BlocDelegate\n\n##### Обґрунтування\n\nЯк згадувалося раніше, `BlocDelegate` було перейменовано на `BlocObserver` і\nбуло встановлено безпосередньо як частину `bloc` через:\n\n```dart\nBloc.observer = MyBlocObserver();\n```\n\nНаступну зміну було внесено для:\n\n- Залишатися узгодженим з новим API спостерігача bloc\n- Обмежити область дії сховища лише `HydratedBloc`\n- Відокремити `BlocObserver` від `Storage`\n\n**v4.0.0**\n\n```dart\nBlocSupervisor.delegate = await HydratedBlocDelegate.build();\n```\n\n**v5.0.0**\n\n```dart\nHydratedBloc.storage = await HydratedStorage.build();\n```\n\n#### ❗Спрощена ініціалізація\n\n##### Обґрунтування\n\nРаніше розробникам доводилося вручну викликати\n`super.initialState ?? DefaultInitialState()` для налаштування своїх екземплярів\n`HydratedBloc`. Це незручно і багатослівно, а також несумісно з критичними\nзмінами в `initialState` у `bloc`. У результаті в v5.0.0 ініціалізація\n`HydratedBloc` ідентична звичайній ініціалізації `Bloc`.\n\n**v4.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  @override\n  int get initialState => super.initialState ?? 0;\n}\n```\n\n**v5.0.0**\n\n```dart\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  ...\n}\n```\n"
  },
  {
    "path": "docs/src/content/docs/uk/modeling-state.mdx",
    "content": "---\ntitle: Моделювання стану\ndescription:\n  Огляд кількох способів моделювання станів при використанні package:bloc.\n---\n\nimport ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro';\nimport SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro';\n\nІснує безліч різних підходів до структурування стану додатку. Кожен з них має\nсвої переваги та недоліки. У цьому розділі ми розглянемо кілька підходів, їх\nплюси та мінуси, а також коли використовувати кожен з них.\n\nНаведені нижче підходи є лише рекомендаціями і є повністю необов'язковими.\nВикористовуйте будь-який підхід, який вам подобається. Ви можете виявити, що\nдеякі приклади/документація не дотримуються цих підходів, переважно для\nпростоти/стислості.\n\n:::tip\n\nНаведені нижче фрагменти коду зосереджені на структурі стану. На практиці ви\nтакож можете захотіти:\n\n- Розширити `Equatable` з\n  [`package:equatable`](https://pub.dev/packages/equatable)\n- Анотувати клас за допомогою `@Data()` з\n  [`package:data_class`](https://pub.dev/packages/data_class)\n- Анотувати клас за допомогою **@immutable** з\n  [`package:meta`](https://pub.dev/packages/meta)\n- Реалізувати метод `copyWith`\n- Використовувати ключове слово `const` для конструкторів\n\n:::\n\n## Конкретний клас та enum статусу\n\nЦей підхід складається з **одного конкретного класу** для всіх станів разом з\n`enum`, що представляє різні статуси. Властивості робляться nullable і\nобробляються на основі поточного статусу. Цей підхід найкраще працює для станів,\nякі не є строго ексклюзивними та/або містять багато спільних властивостей.\n\n<ConcreteClassAndStatusEnumSnippet />\n\n#### Плюси\n\n- **Просто**: Легко керувати одним класом та enum статусу, і всі властивості\n  легко доступні.\n- **Стисло**: Зазвичай потребує менше рядків коду порівняно з іншими підходами.\n\n#### Мінуси\n\n- **Не типобезпечно**: Потребує перевірки `status` перед доступом до\n  властивостей. Можливо `emit` неправильно сформований стан, що може призвести\n  до помилок. Властивості для конкретних станів є nullable, що може бути\n  обтяжливим для керування і потребує або примусового розгортання, або виконання\n  перевірок на null. Деякі з цих мінусів можна пом'якшити написанням модульних\n  тестів та написанням спеціалізованих іменованих конструкторів.\n- **Роздутий**: Призводить до одного стану, який може стати роздутим з багатьма\n  властивостями з часом.\n\n#### Вердикт\n\nЦей підхід найкраще працює для простих станів або коли вимоги потребують станів,\nякі не є ексклюзивними (наприклад, показ snackbar при виникненні помилки при\nзбереженні старих даних з останнього успішного стану). Цей підхід забезпечує\nгнучкість та стислість за рахунок типобезпеки.\n\n## Запечатаний клас та підкласи\n\nЦей підхід складається з **запечатаного класу**, який містить будь-які спільні\nвластивості, та кількох підкласів для окремих станів. Цей підхід чудово\nпідходить для окремих, ексклюзивних станів.\n\n<SealedClassAndSubclassesSnippet />\n\n#### Плюси\n\n- **Типобезпечно**: Код безпечний на етапі компіляції, і неможливо випадково\n  отримати доступ до недопустимої властивості. Кожен підклас містить свої власні\n  властивості, що робить зрозумілим, які властивості належать якому стану.\n- **Явно:** Розділяє спільні властивості від специфічних для стану властивостей.\n- **Вичерпно**: Використання оператора `switch` для перевірки вичерпності, щоб\n  гарантувати, що кожен стан явно оброблений.\n  - Якщо ви не хочете\n    [вичерпного перемикання](https://dart.dev/language/branches#exhaustiveness-checking)\n    або хочете мати можливість додавати підтипи пізніше без порушення API,\n    використовуйте модифікатор\n    [final](https://dart.dev/language/class-modifiers#final).\n  - Див.\n    [документацію по запечатаних класах](https://dart.dev/language/class-modifiers#sealed)\n    для отримання детальнішої інформації.\n\n#### Мінуси\n\n- **Багатослівно**: Потребує більше коду (один базовий клас та підклас для\n  кожного стану). Також може потребувати дублювання коду для спільних\n  властивостей у підкласах.\n- **Складно**: Додавання нових властивостей потребує оновлення кожного підкласу\n  та базового класу, що може бути обтяжливим і призвести до збільшення\n  складності стану. Крім того, може потребувати непотрібної/надмірної перевірки\n  типів для доступу до властивостей.\n\n#### Вердикт\n\nЦей підхід найкраще працює для добре визначених ексклюзивних станів з\nунікальними властивостями. Цей підхід забезпечує типобезпеку та вичерпні\nперевірки і наголошує на безпеці над стислістю та простотою.\n"
  },
  {
    "path": "docs/src/content/docs/uk/naming-conventions.mdx",
    "content": "---\ntitle: Угоди про іменування\ndescription: Огляд рекомендованих угод про іменування при використанні bloc.\n---\n\nimport EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';\nimport EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';\nimport StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';\nimport SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';\nimport StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';\n\nНаведені нижче угоди про іменування є лише рекомендаціями і є повністю\nнеобов'язковими. Використовуйте будь-які угоди про іменування, які вам\nподобаються. Ви можете виявити, що деякі приклади/документація не дотримуються\nугод про іменування, переважно для простоти/стислості. Ці угоди настійно\nрекомендуються для великих проєктів з кількома розробниками.\n\n## Угоди про події\n\nПодії повинні бути названі в **минулому часі**, тому що події -- це те, що вже\nвідбулося з точки зору блоку.\n\n### Анатомія\n\n`BlocSubject` + `Іменник (необов'язково)` + `Дієслово (подія)`\n\nПодії початкового завантаження повинні дотримуватися угоди: `BlocSubject` +\n`Started`\n\n:::note\n\nБазовий клас події повинен бути названий: `BlocSubject` + `Event`.\n\n:::\n\n### Приклади\n\n✅ **Добре**\n\n<EventExamplesGood1 />\n\n❌ **Погано**\n\n<EventExamplesBad1 />\n\n## Угоди про стани\n\nСтани повинні бути іменниками, тому що стан -- це просто знімок у певний момент\nчасу. Існує два поширені способи представлення стану: використання підкласів або\nвикористання одного класу.\n\n### Анатомія\n\n#### Підкласи\n\n`BlocSubject` + `Дієслово (дія)` + `State`\n\nПри представленні стану у вигляді кількох підкласів `State` повинен бути одним з\nнаступних:\n\n`Initial` | `Success` | `Failure` | `InProgress`\n\n:::note\n\nПочаткові стани повинні дотримуватися угоди: `BlocSubject` + `Initial`.\n\n:::\n\n#### Один клас\n\n`BlocSubject` + `State`\n\nПри представленні стану у вигляді одного базового класу повинен\nвикористовуватися enum з назвою `BlocSubject` + `Status` для представлення\nстатусу стану:\n\n`initial` | `success` | `failure` | `loading`.\n\n:::note\n\nБазовий клас стану завжди повинен бути названий: `BlocSubject` + `State`.\n\n:::\n\n### Приклади\n\n✅ **Добре**\n\n##### Підкласи\n\n<StateExamplesGood1Snippet />\n\n##### Один клас\n\n<SingleStateExamplesGood1Snippet />\n\n❌ **Погано**\n\n<StateExamplesBad1Snippet />\n"
  },
  {
    "path": "docs/src/content/docs/uk/testing.mdx",
    "content": "---\ntitle: Тестування\ndescription: Основи написання тестів для ваших блоків.\n---\n\nimport CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro';\nimport AddDevDependenciesSnippet from '~/components/testing/AddDevDependenciesSnippet.astro';\nimport CounterBlocTestImportsSnippet from '~/components/testing/CounterBlocTestImportsSnippet.astro';\nimport CounterBlocTestMainSnippet from '~/components/testing/CounterBlocTestMainSnippet.astro';\nimport CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSetupSnippet.astro';\nimport CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro';\nimport CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro';\n\nBloc був розроблений так, щоб його було надзвичайно легко тестувати. У цьому\nрозділі ми розглянемо, як провести модульне тестування блоку.\n\nДля простоти давайте напишемо тести для `CounterBloc`, який ми створили в\nрозділі [Основні концепції](/uk/bloc-concepts).\n\nНагадаємо, що реалізація `CounterBloc` виглядає так:\n\n<CounterBlocSnippet />\n\n## Налаштування\n\nПерш ніж почати писати наші тести, нам потрібно додати фреймворк для тестування\nдо наших залежностей.\n\nНам потрібно додати [test](https://pub.dev/packages/test) та\n[bloc_test](https://pub.dev/packages/bloc_test) до нашого проєкту.\n\n<AddDevDependenciesSnippet />\n\n## Тестування\n\nДавайте почнемо зі створення файлу для наших тестів `CounterBloc`,\n`counter_bloc_test.dart`, та імпортуємо пакет test.\n\n<CounterBlocTestImportsSnippet />\n\nДалі нам потрібно створити нашу функцію `main`, а також нашу групу тестів.\n\n<CounterBlocTestMainSnippet />\n\n:::note\n\nГрупи використовуються для організації окремих тестів, а також для створення\nконтексту, в якому ви можете спільно використовувати загальні `setUp` та\n`tearDown` для всіх окремих тестів.\n\n:::\n\nДавайте почнемо зі створення екземпляру нашого `CounterBloc`, який буде\nвикористовуватися у всіх наших тестах.\n\n<CounterBlocTestSetupSnippet />\n\nТепер ми можемо почати писати наші окремі тести.\n\n<CounterBlocTestInitialStateSnippet />\n\n:::note\n\nМи можемо запустити всі наші тести за допомогою команди `dart test`.\n\n:::\n\nНа цьому етапі у нас повинен бути перший пройдений тест! Тепер давайте напишемо\nскладніший тест, використовуючи пакет\n[bloc_test](https://pub.dev/packages/bloc_test).\n\n<CounterBlocTestBlocTestSnippet />\n\nМи повинні мати можливість запустити тести та побачити, що всі вони проходять.\n\nОсь і все, тестування має бути легким, і ми повинні відчувати впевненість при\nвнесенні змін та рефакторингу нашого коду.\n\nВи можете звернутися до\n[додатку Weather](https://github.com/felangel/bloc/tree/master/examples/flutter_weather)\nяк прикладу повністю протестованого додатку.\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Лічильник Flutter\ndescription:\n  Детальний посібник зі створення додатку-лічильника на Flutter з використанням\n  bloc.\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nУ наступному підручнику ми створимо лічильник на Flutter з використанням\nбібліотеки Bloc.\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## Ключові теми\n\n- Спостереження за змінами стану за допомогою\n  [BlocObserver](/uk/bloc-concepts#blocobserver).\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), віджет Flutter, який\n  обробляє побудову віджета у відповідь на нові стани.\n- Використання Cubit замість Bloc.\n  [У чому різниця?](/uk/bloc-concepts#cubit-проти-bloc)\n- Додавання подій за допомогою\n  [context.read](/uk/flutter-bloc-concepts#contextread).\n\n## Налаштування\n\nПочнемо зі створення нового Flutter проєкту\n\n<FlutterCreateSnippet />\n\nПотім ми можемо замінити вміст `pubspec.yaml` на\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nа потім встановити всі наші залежності\n\n<FlutterPubGetSnippet />\n\n## Структура проєкту\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nДодаток використовує структуру каталогів, орієнтовану на функціональність. Ця\nструктура проєкту дозволяє нам масштабувати проєкт, маючи самодостатні\nфункціональні модулі. У цьому прикладі ми матимемо лише один модуль (сам\nлічильник), але у складніших додатках може бути сотні різних модулів.\n\n## BlocObserver\n\nПерше, на що ми звернемо увагу — як створити `BlocObserver`, який допоможе нам\nспостерігати за всіма змінами стану в додатку.\n\nСтворимо `lib/counter_observer.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\nУ цьому випадку ми перевизначаємо лише `onChange`, щоб бачити всі зміни стану,\nякі відбуваються.\n\n:::note\n\n`onChange` працює однаково для екземплярів `Bloc` та `Cubit`.\n\n:::\n\n## main.dart\n\nДалі замінимо вміст `lib/main.dart` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nМи ініціалізуємо щойно створений `CounterObserver` і викликаємо `runApp` з\nвіджетом `CounterApp`, який ми розглянемо далі.\n\n## Counter App\n\nСтворимо `lib/app.dart`:\n\n`CounterApp` буде `MaterialApp` і вказує `home` як `CounterPage`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\nМи розширюємо `MaterialApp`, тому що `CounterApp` _є_ `MaterialApp`. У більшості\nвипадків ми будемо створювати екземпляри `StatelessWidget` або `StatefulWidget`\nта компонувати віджети в `build`, але в цьому випадку немає віджетів для\nкомпонування, тому простіше просто розширити `MaterialApp`.\n\n:::\n\nДавайте розглянемо `CounterPage` далі!\n\n## Counter Page\n\nСтворимо `lib/counter/view/counter_page.dart`:\n\nВіджет `CounterPage` відповідає за створення `CounterCubit` (який ми розглянемо\nдалі) та надання його `CounterView`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\nВажливо відокремити або розділити створення `Cubit` від споживання `Cubit`, щоб\nмати код, який значно легше тестувати та повторно використовувати.\n\n:::\n\n## Counter Cubit\n\nСтворимо `lib/counter/cubit/counter_cubit.dart`:\n\nКлас `CounterCubit` надасть два методи:\n\n- `increment`: додає 1 до поточного стану\n- `decrement`: віднімає 1 від поточного стану\n\nТип стану, яким керує `CounterCubit` — це просто `int`, а початковий стан\nдорівнює `0`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\nВикористовуйте\n[розширення VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nабо [плагін IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) для\nавтоматичного створення нових cubit-ів.\n\n:::\n\nДалі розглянемо `CounterView`, який відповідатиме за споживання стану та\nвзаємодію з `CounterCubit`.\n\n## Counter View\n\nСтворимо `lib/counter/view/counter_view.dart`:\n\n`CounterView` відповідає за відображення поточного значення лічильника та\nвідображення двох FloatingActionButton для збільшення/зменшення лічильника.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\n`BlocBuilder` використовується для обгортання віджета `Text`, щоб оновлювати\nтекст кожного разу, коли змінюється стан `CounterCubit`. Крім того,\n`context.read<CounterCubit>()` використовується для пошуку найближчого\nекземпляра `CounterCubit`.\n\n:::note\n\nТільки віджет `Text` обгорнутий у `BlocBuilder`, оскільки це єдиний віджет, який\nпотрібно перебудовувати у відповідь на зміни стану в `CounterCubit`. Уникайте\nзайвого обгортання віджетів, які не потребують перебудови при зміні стану.\n\n:::\n\n## Barrel\n\nСтворимо `lib/counter/view/view.dart`:\n\nДодамо `view.dart` для експорту всіх публічних частин counter view.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\nСтворимо `lib/counter/counter.dart`:\n\nДодамо `counter.dart` для експорту всіх публічних частин функціональності\nлічильника.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\nОсь і все! Ми відокремили шар представлення від шару бізнес-логіки.\n`CounterView` не знає, що відбувається, коли користувач натискає кнопку; він\nпросто повідомляє `CounterCubit`. Крім того, `CounterCubit` не знає, що\nвідбувається зі станом (значенням лічильника); він просто випускає нові стани у\nвідповідь на виклики методів.\n\nМи можемо запустити наш додаток за допомогою `flutter run` та переглянути його\nна нашому пристрої або симуляторі/емуляторі.\n\nПовний вихідний код (включаючи unit та widget тести) цього прикладу можна знайти\n[тут](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-firebase-login.mdx",
    "content": "---\ntitle: Вхід Flutter Firebase\ndescription:\n  Детальний посібник зі створення потоку входу Flutter з використанням bloc та\n  Firebase.\nsidebar:\n  order: 7\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-firebase-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nУ наступному підручнику ми створимо потік входу через Firebase у Flutter з\nвикористанням бібліотеки Bloc.\n\n![demo](~/assets/tutorials/flutter-firebase-login.gif)\n\n## Ключові теми\n\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- Використання Cubit замість Bloc.\n  [У чому різниця?](/uk/bloc-concepts#cubit-проти-bloc)\n- Додавання подій за допомогою\n  [context.read](/uk/flutter-bloc-concepts#contextread).\n- Запобігання зайвим перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- [RepositoryProvider](/uk/flutter-bloc-concepts#repositoryprovider), віджет\n  Flutter, який надає сховище своїм дочірнім елементам.\n- [BlocListener](/uk/flutter-bloc-concepts#bloclistener), віджет Flutter, який\n  викликає код слухача у відповідь на зміни стану в bloc.\n- Додавання подій за допомогою\n  [context.read](/uk/flutter-bloc-concepts#contextselect).\n\n## Налаштування\n\nПочнемо зі створення нового Flutter проєкту.\n\n<FlutterCreateSnippet />\n\nЯк і в [підручнику з входу](/uk/tutorials/flutter-login), ми створимо внутрішні\nпакети для кращого шарування архітектури додатку та підтримки чітких меж, а\nтакож для максимального повторного використання та покращення тестованості.\n\nУ цьому випадку пакети [firebase_auth](https://pub.dev/packages/firebase_auth)\nта [google_sign_in](https://pub.dev/packages/google_sign_in) будуть нашим шаром\nданих, тому ми створимо лише `AuthenticationRepository` для компонування даних\nвід двох API-клієнтів.\n\n## Authentication Repository\n\n`AuthenticationRepository` відповідатиме за абстрагування внутрішніх деталей\nреалізації автентифікації та отримання інформації про користувача. У цьому\nвипадку він буде інтегруватися з Firebase, але ми завжди можемо змінити\nвнутрішню реалізацію пізніше, і наш додаток не буде порушено.\n\n### Налаштування\n\nПочнемо зі створення `packages/authentication_repository` та `pubspec.yaml` в\nкорені проєкту.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\nДалі ми можемо встановити залежності, виконавши:\n\n<FlutterPubGetSnippet />\n\nу каталозі `authentication_repository`.\n\nЯк і більшість пакетів, `authentication_repository` визначає свій API через\n`packages/authentication_repository/lib/authentication_repository.dart`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\n:::note\n\nПакет `authentication_repository` експортуватиме `AuthenticationRepository`, а\nтакож моделі.\n\n:::\n\nДалі розглянемо моделі.\n\n### User\n\nМодель `User` описуватиме користувача в контексті домену автентифікації. Для\nцілей цього прикладу користувач складатиметься з `email`, `id`, `name` та\n`photo`.\n\n:::note\n\nВи самі вирішуєте, як має виглядати користувач у контексті вашого домену.\n\n:::\n\n[user.dart](https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart ':include')\n\n:::note\n\nКлас `User` розширює [equatable](https://pub.dev/packages/equatable), щоб\nперевизначити порівняння рівності та мати можливість порівнювати різні\nекземпляри `User` за значенням.\n\n:::\n\n:::tip\n\nКорисно визначити `static` порожнього `User`, щоб не обробляти `null` Users і\nзавжди працювати з конкретним об'єктом `User`.\n\n:::\n\n### Repository\n\n`AuthenticationRepository` відповідає за абстрагування базової реалізації\nавтентифікації користувача, а також отримання даних про користувача.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository` надає `Stream<User>`, на який ми можемо підписатися,\nщоб отримувати сповіщення про зміну `User`. Крім того, він надає методи\n`signUp`, `logInWithGoogle`, `logInWithEmailAndPassword` та `logOut`.\n\n:::note\n\n`AuthenticationRepository` також відповідає за обробку низькорівневих помилок,\nякі можуть виникнути в шарі даних, та надає чистий, простий набір помилок, що\nвідповідають домену.\n\n:::\n\nНа цьому з `AuthenticationRepository` все. Далі розглянемо, як інтегрувати його\nу Flutter проєкт, який ми створили.\n\n## Налаштування Firebase\n\nНам потрібно виконати\n[інструкції з використання firebase_auth](https://pub.dev/packages/firebase_auth#usage),\nщоб підключити наш додаток до Firebase та увімкнути\n[google_sign_in](https://pub.dev/packages/google_sign_in).\n\n:::caution\n\nНе забудьте оновити `google-services.json` на Android та\n`GoogleService-Info.plist` й `Info.plist` на iOS, інакше додаток аварійно\nзавершить роботу.\n\n:::\n\n## Залежності проєкту\n\nМи можемо замінити згенерований `pubspec.yaml` у корені проєкту наступним:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nЗверніть увагу, що ми вказуємо каталог assets для всіх локальних ресурсів нашого\nдодатку. Створіть каталог `assets` у корені проєкту та додайте\n[логотип bloc](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png)\n(який ми використаємо пізніше).\n\nПотім встановіть усі залежності:\n\n<FlutterPubGetSnippet />\n\n:::note\n\nМи залежимо від пакету `authentication_repository` через шлях, що дозволяє нам\nшвидко ітерувати, зберігаючи чітке розділення.\n\n:::\n\n## main.dart\n\nФайл `main.dart` можна замінити наступним:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nВін просто налаштовує деяку глобальну конфігурацію для додатку та викликає\n`runApp` з екземпляром `App`.\n\n:::note\n\nМи впроваджуємо єдиний екземпляр `AuthenticationRepository` в `App`, і це є\nявною залежністю конструктора.\n\n:::\n\n## App\n\nЯк і в [підручнику з входу](/uk/tutorials/flutter-login), наш `app.dart` надає\nекземпляр `AuthenticationRepository` додатку через `RepositoryProvider`, а також\nстворює та надає екземпляр `AuthenticationBloc`. Потім `AppView` споживає\n`AuthenticationBloc` та обробляє оновлення поточного маршруту на основі\n`AuthenticationState`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/view/app.dart\"\n\ttitle=\"lib/app/view/app.dart\"\n/>\n\n## App Bloc\n\n`AppBloc` відповідає за керування глобальним станом додатку. Він залежить від\n`AuthenticationRepository` та підписується на потік `user`, щоб випускати нові\nстани у відповідь на зміни поточного користувача.\n\n### State\n\n`AppState` складається з `AppStatus` та `User`. Конструктор за замовчуванням\nприймає необов'язкового `User` та перенаправляє до приватного конструктора з\nвідповідним статусом автентифікації.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_state.dart\"\n\ttitle=\"lib/app/bloc/app_state.dart\"\n/>\n\n### Event\n\n`AppEvent` має два підкласи:\n\n- `AppUserSubscriptionRequested` — повідомляє bloc про підписку на потік\n  користувача.\n- `AppLogoutPressed` — повідомляє bloc про дію виходу користувача.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_event.dart\"\n\ttitle=\"lib/app/bloc/app_event.dart\"\n/>\n\n### Bloc\n\nУ тілі конструктора підкласи `AppEvent` зіставляються з відповідними обробниками\nподій.\n\nВ обробнику подій `_onUserSubscriptionRequested` `AppBloc` використовує\n`emit.onEach` для підписки на потік користувача `AuthenticationRepository` та\nвипуску стану у відповідь на кожного `User`.\n\n`emit.onEach` створює підписку на потік внутрішньо та піклується про її\nскасування, коли `AppBloc` або потік користувача закривається.\n\nЯкщо потік користувача випускає помилку, `addError` пересилає помилку та стек\nвиклику будь-якому `BlocObserver`, що слухає.\n\n:::caution\n\nЯкщо `onError` пропущено, будь-які помилки в потоці користувача вважаються\nнеобробленими та будуть викинуті через `onEach`. Як наслідок, підписка на потік\nкористувача буде скасована.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/uk/bloc-concepts/#blocobserver-1) чудово підходить для\nлогування подій, помилок та змін стану Bloc, особливо в контексті аналітики та\nзвітування про збої.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart\"\n\ttitle=\"lib/app/bloc/app_bloc.dart\"\n/>\n\n## Моделі\n\nМоделі введення `Email` та `Password` корисні для інкапсуляції логіки валідації\nта будуть використовуватися як у `LoginForm`, так і в `SignUpForm` (пізніше у\nпідручнику).\n\nОбидві моделі введення створені за допомогою пакету\n[formz](https://pub.dev/packages/formz) і дозволяють нам працювати з валідованим\nоб'єктом замість примітивного типу, такого як `String`.\n\n### Email\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart\"\n\ttitle=\"packages/form_inputs/lib/src/email.dart\"\n/>\n\n### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart\"\n\ttitle=\"packages/form_inputs/lib/src/password.dart\"\n/>\n\n## Сторінка входу\n\n`LoginPage` відповідає за створення та надання екземпляра `LoginCubit` для\n`LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::tip\n\nДуже важливо тримати створення блоків/кубітів окремо від місця їх споживання. Це\nдозволить вам легко впроваджувати мок-екземпляри та тестувати представлення\nізольовано.\n\n:::\n\n## Login Cubit\n\n`LoginCubit` відповідає за керування `LoginState` форми. Він надає API для\n`logInWithCredentials`, `logInWithGoogle`, а також отримує сповіщення при\nоновленні email/пароля.\n\n### State\n\n`LoginState` складається з `Email`, `Password` та `FormzStatus`. Моделі `Email`\nта `Password` розширюють `FormzInput` з пакету\n[formz](https://pub.dev/packages/formz).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_state.dart\"\n\ttitle=\"lib/login/cubit/login_state.dart\"\n/>\n\n### Cubit\n\n`LoginCubit` залежить від `AuthenticationRepository` для входу користувача через\nоблікові дані або через вхід через Google.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart\"\n\ttitle=\"lib/login/cubit/login_cubit.dart\"\n/>\n\n:::note\n\nМи використали `Cubit` замість `Bloc` тут, оскільки `LoginState` досить простий\nта локалізований. Навіть без подій ми все ще можемо мати досить гарне уявлення\nпро те, що відбулось, просто дивлячись на зміни від одного стану до іншого, а\nнаш код значно простіший та лаконічніший.\n\n:::\n\n## Форма входу\n\n`LoginForm` відповідає за відображення форми у відповідь на `LoginState` та\nвикликає методи `LoginCubit` у відповідь на взаємодії користувача.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`LoginForm` також відображає кнопку \"Створити обліковий запис\", яка переходить\nна `SignUpPage`, де користувач може створити новий обліковий запис.\n\n## Сторінка реєстрації\n\nСтруктура `SignUp` дзеркально відображає структуру `Login` та складається з\n`SignUpPage`, `SignUpView` та `SignUpCubit`.\n\n`SignUpPage` відповідає лише за створення та надання екземпляра `SignUpCubit`\nдля `SignUpForm` (так само, як у `LoginPage`).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_page.dart\"\n/>\n\n:::note\n\nЯк і у `LoginCubit`, `SignUpCubit` залежить від `AuthenticationRepository` для\nстворення нових облікових записів користувачів.\n\n:::\n\n## Sign Up Cubit\n\n`SignUpCubit` керує станом `SignUpForm` та взаємодіє з\n`AuthenticationRepository` для створення нових облікових записів користувачів.\n\n### State\n\n`SignUpState` повторно використовує ті самі моделі введення `Email` та\n`Password`, оскільки логіка валідації однакова.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_state.dart\"\n/>\n\n### Cubit\n\n`SignUpCubit` дуже схожий на `LoginCubit`, з основною відмінністю в тому, що він\nнадає API для відправки форми, а не для входу.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart\"\n\ttitle=\"lib/sign_up/cubit/sign_up_cubit.dart\"\n/>\n\n## Форма реєстрації\n\n`SignUpForm` відповідає за відображення форми у відповідь на `SignUpState` та\nвикликає методи `SignUpCubit` у відповідь на взаємодії користувача.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart\"\n\ttitle=\"lib/sign_up/view/sign_up_form.dart\"\n/>\n\n## Домашня сторінка\n\nПісля успішного входу або реєстрації користувача потік `user` буде оновлено, що\nпризведе до зміни стану в `AuthenticationBloc` і `AppView` додасть маршрут\n`HomePage` до стеку навігації.\n\nНа `HomePage` користувач може переглянути інформацію свого профілю та вийти,\nнатиснувши іконку виходу в `AppBar`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_firebase_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\nКаталог `widgets` було створено поряд з каталогом `view` у функціональному\nмодулі `home` для багаторазових компонентів, специфічних для цього модуля. У\nцьому випадку простий віджет `Avatar` експортується та використовується в\n`HomePage`.\n\n:::\n\n:::note\n\nКоли натискається `IconButton` виходу, подія `AuthenticationLogoutRequested`\nдодається до `AuthenticationBloc`, який здійснює вихід користувача та переводить\nйого назад на `LoginPage`.\n\n:::\n\nНа цьому етапі ми маємо досить надійну реалізацію входу з використанням\nFirebase, і ми відокремили шар представлення від шару бізнес-логіки за допомогою\nбібліотеки Bloc.\n\nПовний вихідний код цього прикладу можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-infinite-list.mdx",
    "content": "---\ntitle: Нескінченний список Flutter\ndescription:\n  Детальний посібник зі створення нескінченного списку на Flutter з\n  використанням bloc.\nsidebar:\n  order: 3\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-infinite-list/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/flutter-infinite-list/FlutterPubGetSnippet.astro';\nimport PostsJsonSnippet from '~/components/tutorials/flutter-infinite-list/PostsJsonSnippet.astro';\nimport PostBlocInitialStateSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocInitialStateSnippet.astro';\nimport PostBlocOnPostFetchedSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocOnPostFetchedSnippet.astro';\nimport PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite-list/PostBlocTransformerSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nУ цьому підручнику ми реалізуємо додаток, який завантажує дані з мережі та\nвідображає їх у міру прокручування користувачем, використовуючи Flutter та\nбібліотеку bloc.\n\n![demo](~/assets/tutorials/flutter-infinite-list.gif)\n\n## Ключові теми\n\n- Спостереження за змінами стану за допомогою\n  [BlocObserver](/uk/bloc-concepts#blocobserver).\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), віджет Flutter, який\n  обробляє побудову віджета у відповідь на нові стани.\n- Додавання подій за допомогою\n  [context.read](/uk/flutter-bloc-concepts#contextread).\n- Запобігання зайвим перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- Використання методу `transformEvents` з Rx.\n\n## Налаштування\n\nПочнемо зі створення нового Flutter проєкту\n\n<FlutterCreateSnippet />\n\nПотім ми можемо замінити вміст pubspec.yaml на\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nа потім встановити всі наші залежності\n\n<FlutterPubGetSnippet />\n\n## Структура проєкту\n\n```\n├── lib\n|   ├── posts\n│   │   ├── bloc\n│   │   │   └── post_bloc.dart\n|   |   |   └── post_event.dart\n|   |   |   └── post_state.dart\n|   |   └── models\n|   |   |   └── models.dart*\n|   |   |   └── post.dart\n│   │   └── view\n│   │   |   ├── posts_page.dart\n│   │   |   └── posts_list.dart\n|   |   |   └── view.dart*\n|   |   └── widgets\n|   |   |   └── bottom_loader.dart\n|   |   |   └── post_list_item.dart\n|   |   |   └── widgets.dart*\n│   │   ├── posts.dart*\n│   ├── app.dart\n│   ├── simple_bloc_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\nДодаток використовує структуру каталогів, орієнтовану на функціональність. Ця\nструктура проєкту дозволяє нам масштабувати проєкт, маючи самодостатні\nфункціональні модулі. У цьому прикладі ми матимемо лише один модуль (модуль\nпостів), який розділений на відповідні каталоги з barrel-файлами, позначеними\nзірочкою (\\*).\n\n## REST API\n\nДля цього демо-додатку ми використаємо\n[jsonplaceholder](http://jsonplaceholder.typicode.com) як джерело даних.\n\n:::note\n\njsonplaceholder — це онлайн REST API, який надає фейкові дані; він дуже корисний\nдля створення прототипів.\n\n:::\n\nВідкрийте нову вкладку у браузері та перейдіть за адресою\nhttps://jsonplaceholder.typicode.com/posts?_start=0&_limit=2, щоб побачити, що\nповертає API.\n\n<PostsJsonSnippet />\n\n:::note\n\nУ нашому URL ми вказали start та limit як параметри запиту для GET-запиту.\n\n:::\n\nЧудово, тепер, коли ми знаємо, як виглядатимуть наші дані, створимо модель.\n\n## Модель даних\n\nСтворіть `post.dart` і почнемо створювати модель нашого об'єкта Post.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/models/post.dart\"\n\ttitle=\"lib/posts/models/post.dart\"\n/>\n\n`Post` — це просто клас з `id`, `title` та `body`.\n\n:::note\n\nМи розширюємо [`Equatable`](https://pub.dev/packages/equatable), щоб мати\nможливість порівнювати `Post`. Без цього нам довелося б вручну змінювати наш\nклас для перевизначення рівності та hashCode, щоб відрізняти два об'єкти `Post`.\nДивіться [пакет](https://pub.dev/packages/equatable) для детальної інформації.\n\n:::\n\nТепер, коли у нас є модель об'єкта `Post`, почнемо працювати над компонентом\nбізнес-логіки (bloc).\n\n## Post Events\n\nПерш ніж зануритися в реалізацію, нам потрібно визначити, що буде робити наш\n`PostBloc`.\n\nНа високому рівні він реагуватиме на введення користувача (прокручування) та\nзавантажуватиме більше постів, щоб шар представлення міг їх відобразити. Почнемо\nзі створення нашої `Event`.\n\nНаш `PostBloc` реагуватиме лише на одну подію — `PostFetched`, яку буде додавати\nшар представлення, коли йому потрібно більше постів для відображення. Оскільки\nнаш `PostFetched` є типом `PostEvent`, ми можемо створити `bloc/post_event.dart`\nта реалізувати подію так.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_event.dart\"\n\ttitle=\"lib/posts/bloc/post_event.dart\"\n/>\n\nПідсумовуючи, наш `PostBloc` отримуватиме `PostEvents` та перетворюватиме їх на\n`PostStates`. Ми визначили всі наші `PostEvents` (PostFetched), тому далі\nвизначимо наш `PostState`.\n\n## Post States\n\nНаш шар представлення потребуватиме кілька фрагментів інформації для коректного\nвідображення:\n\n- `PostInitial` — повідомляє шар представлення, що потрібно відобразити\n  індикатор завантаження під час завантаження початкової партії постів\n- `PostSuccess` — повідомляє шар представлення, що є контент для відображення\n  - `posts` — буде `List<Post>`, який відображатиметься\n  - `hasReachedMax` — повідомляє шар представлення, чи досягнуто максимальну\n    кількість постів\n- `PostFailure` — повідомляє шар представлення, що сталася помилка під час\n  завантаження постів\n\nТепер ми можемо створити `bloc/post_state.dart` та реалізувати його так.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_state.dart\"\n\ttitle=\"lib/posts/bloc/post_state.dart\"\n/>\n\n:::note\n\nМи реалізували `copyWith`, щоб мати можливість копіювати екземпляр `PostSuccess`\nта зручно оновлювати нуль або більше властивостей (це знадобиться пізніше).\n\n:::\n\nТепер, коли ми реалізували наші `Events` та `States`, можемо створити наш\n`PostBloc`.\n\n## Post Bloc\n\nДля простоти наш `PostBloc` матиме пряму залежність від `http client`; однак у\nпродакшн-додатку ми рекомендуємо замість цього впроваджувати api клієнт та\nвикористовувати патерн репозиторій [документація](/uk/architecture).\n\nСтворимо `post_bloc.dart` та наш порожній `PostBloc`.\n\n<PostBlocInitialStateSnippet />\n\n:::note\n\nЛише з оголошення класу ми бачимо, що наш PostBloc прийматиме PostEvents як вхід\nта виводитиме PostStates.\n\n:::\n\nДалі нам потрібно зареєструвати обробник подій для обробки вхідних подій\n`PostFetched`. У відповідь на подію `PostFetched` ми викликатимемо `_fetchPosts`\nдля завантаження постів з API.\n\n<PostBlocOnPostFetchedSnippet />\n\nНаш `PostBloc` буде `emit` нові стани через `Emitter<PostState>`, наданий в\nобробнику подій. Перегляньте\n[основні концепції](/uk/bloc-concepts#потоки-streams) для детальної інформації.\n\nТепер кожного разу, коли додається `PostEvent`, якщо це подія `PostFetched` і є\nще пости для завантаження, наш `PostBloc` завантажить наступні 20 постів.\n\nAPI поверне порожній масив, якщо ми спробуємо завантажити більше максимальної\nкількості постів (100), тому якщо ми отримаємо порожній масив, наш bloc\n`emit`-не поточний стан, але встановить `hasReachedMax` у true.\n\nЯкщо ми не можемо отримати пости, ми випускаємо `PostStatus.failure`.\n\nЯкщо ми можемо отримати пости, ми випускаємо `PostStatus.success` та весь список\nпостів.\n\nОдна оптимізація, яку ми можемо зробити — це `throttle` подій `PostFetched`, щоб\nне спамити наш API без потреби. Ми можемо це зробити, використовуючи параметр\n`transform` при реєстрації обробника подій `_onFetched`.\n\n:::note\n\nПередача `transformer` до `on<PostFetched>` дозволяє налаштувати спосіб обробки\nподій.\n\n:::\n\n:::note\n\nПереконайтеся, що імпортували\n[`package:stream_transform`](https://pub.dev/packages/stream_transform) для\nвикористання API `throttle`.\n\n:::\n\n<PostBlocTransformerSnippet />\n\nНаш завершений `PostBloc` тепер має виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart\"\n\ttitle=\"lib/posts/bloc/post_bloc.dart\"\n/>\n\nЧудово! Тепер, коли ми завершили реалізацію бізнес-логіки, залишилося\nреалізувати шар представлення.\n\n## Шар представлення\n\nУ нашому `main.dart` ми можемо почати з реалізації головної функції та виклику\n`runApp` для відображення нашого кореневого віджета. Тут ми також можемо\nпідключити наш bloc observer для логування переходів та помилок.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nУ нашому віджеті `App`, кореневому елементі нашого проєкту, ми можемо встановити\nhome як `PostsPage`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nУ нашому віджеті `PostsPage` ми використовуємо `BlocProvider` для створення та\nнадання екземпляра `PostBloc` піддереву. Також ми додаємо подію `PostFetched`,\nщоб при завантаженні додатку він запитав початкову партію постів.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_page.dart\"\n\ttitle=\"lib/posts/view/posts_page.dart\"\n/>\n\nДалі нам потрібно реалізувати наше представлення `PostsList`, яке відображатиме\nнаші пости та підключатиметься до нашого `PostBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/view/posts_list.dart\"\n\ttitle=\"lib/posts/view/posts_list.dart\"\n/>\n\n:::note\n\n`PostsList` є `StatefulWidget`, оскільки потребує підтримки `ScrollController`.\nУ `initState` ми додаємо слухача до нашого `ScrollController`, щоб реагувати на\nподії прокручування. Також ми отримуємо екземпляр `PostBloc` через\n`context.read<PostBloc>()`.\n\n:::\n\nДалі наш метод build повертає `BlocBuilder`. `BlocBuilder` — це віджет Flutter з\nпакету [flutter_bloc](https://pub.dev/packages/flutter_bloc), який обробляє\nпобудову віджета у відповідь на нові стани bloc. Кожного разу, коли змінюється\nстан нашого `PostBloc`, функція builder буде викликана з новим `PostState`.\n\n:::caution\n\nНам потрібно пам'ятати про очищення ресурсів та утилізацію нашого\n`ScrollController`, коли StatefulWidget утилізується.\n\n:::\n\nКожного разу, коли користувач прокручує, ми обчислюємо, наскільки далеко він\nпрокрутив сторінку, і якщо наша відстань >= 90% нашого `maxScrollExtent`, ми\nдодаємо подію `PostFetched` для завантаження більше постів.\n\nДалі нам потрібно реалізувати наш віджет `BottomLoader`, який показуватиме\nкористувачу, що ми завантажуємо більше постів.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart\"\n\ttitle=\"lib/posts/widgets/bottom_loader.dart\"\n/>\n\nНарешті, нам потрібно реалізувати наш `PostListItem`, який відображатиме окремий\n`Post`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart\"\n\ttitle=\"lib/posts/widgets/post_list_item.dart\"\n/>\n\nНа цьому етапі ми повинні мати можливість запустити наш додаток, і все має\nпрацювати; однак є ще одна річ, яку ми можемо зробити.\n\nДодатковою перевагою використання бібліотеки bloc є те, що ми можемо мати доступ\nдо всіх `Transitions` в одному місці.\n\nЗміна від одного стану до іншого називається `Transition`.\n\n:::note\n\n`Transition` складається з поточного стану, події та наступного стану.\n\n:::\n\nНавіть хоча в цьому додатку ми маємо лише один bloc, у більших додатках досить\nпоширено мати багато блоків, що керують різними частинами стану додатку.\n\nЯкщо ми хочемо мати можливість виконувати дії у відповідь на всі `Transitions`,\nми можемо просто створити наш власний `BlocObserver`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_infinite_list/lib/simple_bloc_observer.dart\"\n\ttitle=\"lib/simple_bloc_observer.dart\"\n/>\n\n:::note\n\nВсе, що нам потрібно зробити — це розширити `BlocObserver` та перевизначити\nметод `onTransition`.\n\n:::\n\nТепер кожного разу, коли відбувається `Transition` Bloc, ми бачитимемо перехід,\nвиведений у консоль.\n\n:::note\n\nНа практиці ви можете створювати різні `BlocObservers`, і оскільки кожна зміна\nстану записується, ми можемо дуже легко інструментувати наші додатки та\nвідстежувати всі взаємодії користувачів та зміни стану в одному місці!\n\n:::\n\nОсь і все! Ми успішно реалізували нескінченний список у Flutter, використовуючи\nпакети [bloc](https://pub.dev/packages/bloc) та\n[flutter_bloc](https://pub.dev/packages/flutter_bloc), і ми успішно відокремили\nнаш шар представлення від бізнес-логіки.\n\nНаш `PostsPage` не знає, звідки беруться `Post` і як вони отримуються. І\nнавпаки, наш `PostBloc` не знає, як відображається `State`, він просто\nперетворює події на стани.\n\nПовний вихідний код цього прикладу можна знайти\n[тут](https://github.com/felangel/Bloc/tree/master/examples/flutter_infinite_list).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-login.mdx",
    "content": "---\ntitle: Вхід Flutter\ndescription:\n  Детальний посібник зі створення потоку входу Flutter з використанням bloc.\nsidebar:\n  order: 4\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-login/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![intermediate](https://img.shields.io/badge/level-intermediate-orange.svg)\n\nУ наступному підручнику ми створимо потік входу у Flutter з використанням\nбібліотеки Bloc.\n\n![demo](~/assets/tutorials/flutter-login.gif)\n\n## Ключові теми\n\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- Додавання подій за допомогою\n  [context.read](/uk/flutter-bloc-concepts#contextread).\n- Запобігання зайвим перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- [RepositoryProvider](/uk/flutter-bloc-concepts#repositoryprovider), віджет\n  Flutter, який надає сховище своїм дочірнім елементам.\n- [BlocListener](/uk/flutter-bloc-concepts#bloclistener), віджет Flutter, який\n  викликає код слухача у відповідь на зміни стану в bloc.\n- Оновлення UI на основі частини стану bloc за допомогою\n  [context.select](/uk/flutter-bloc-concepts#contextselect).\n\n## Налаштування проєкту\n\nПочнемо зі створення нового Flutter проєкту\n\n<FlutterCreateSnippet />\n\nДалі ми можемо встановити всі наші залежності\n\n<FlutterPubGetSnippet />\n\n## Authentication Repository\n\nПерше, що ми зробимо — створимо пакет `authentication_repository`, який\nвідповідатиме за керування доменом автентифікації.\n\nПочнемо зі створення каталогу `packages/authentication_repository` в корені\nпроєкту, який міститиме всі внутрішні пакети.\n\nНа високому рівні структура каталогів повинна виглядати так:\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   └── authentication_repository\n└── test\n```\n\nДалі створимо `pubspec.yaml` для пакету `authentication_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/pubspec.yaml\"\n\ttitle=\"packages/authentication_repository/pubspec.yaml\"\n/>\n\n:::note\n\n`package:authentication_repository` буде чистим Dart-пакетом без зовнішніх\nзалежностей.\n\n:::\n\nДалі нам потрібно реалізувати сам клас `AuthenticationRepository` у\n`packages/authentication_repository/lib/src/authentication_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/src/authentication_repository.dart\"\n/>\n\n`AuthenticationRepository` надає `Stream` оновлень `AuthenticationStatus`, який\nвикористовуватиметься для сповіщення додатку про вхід або вихід користувача.\n\nКрім того, є методи `logIn` та `logOut`, які для простоти є заглушками, але\nлегко можуть бути розширені для автентифікації через `FirebaseAuth`, наприклад,\nабо іншого провайдера автентифікації.\n\n:::note\n\nОскільки ми підтримуємо `StreamController` внутрішньо, метод `dispose`\nдоступний, щоб контролер можна було закрити, коли він більше не потрібен.\n\n:::\n\nНарешті, нам потрібно створити\n`packages/authentication_repository/lib/authentication_repository.dart`, який\nміститиме публічні експорти:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart\"\n\ttitle=\"packages/authentication_repository/lib/authentication_repository.dart\"\n/>\n\nНа цьому з `AuthenticationRepository` все, далі ми попрацюємо над\n`UserRepository`.\n\n## User Repository\n\nТак само, як і з `AuthenticationRepository`, ми створимо пакет `user_repository`\nвсередині каталогу `packages`.\n\n```\n├── android\n├── ios\n├── lib\n├── packages\n│   ├── authentication_repository\n│   └── user_repository\n└── test\n```\n\nДалі створимо `pubspec.yaml` для `user_repository`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/pubspec.yaml\"\n\ttitle=\"packages/user_repository/pubspec.yaml\"\n/>\n\n`user_repository` відповідатиме за домен користувача та надаватиме API для\nвзаємодії з поточним користувачем.\n\nПерше, що ми визначимо — це модель користувача в\n`packages/user_repository/lib/src/models/user.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/user.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/user.dart\"\n/>\n\nДля простоти користувач має лише властивість `id`, але на практиці ми можемо\nмати додаткові властивості, такі як `firstName`, `lastName`, `avatarUrl` тощо...\n\n:::note\n\n[`package:equatable`](https://pub.dev/packages/equatable) використовується для\nпорівняння об'єктів `User` за значенням.\n\n:::\n\nДалі створимо `models.dart` у `packages/user_repository/lib/src/models`, який\nекспортуватиме всі моделі, щоб можна було використовувати один імпорт для\nдекількох моделей.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/user_repository/lib/src/models/models.dart\"\n/>\n\nТепер, коли моделі визначені, ми можемо реалізувати клас `UserRepository` у\n`packages/user_repository/lib/src/user_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/src/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/src/user_repository.dart\"\n/>\n\nДля цього простого прикладу `UserRepository` надає єдиний метод `getUser`, який\nповертає поточного користувача. Ми використовуємо заглушку, але на практиці тут\nми б запитували поточного користувача з бекенду.\n\nМайже все з пакетом `user_repository` готово — залишилося лише створити файл\n`user_repository.dart` у `packages/user_repository/lib`, який визначає публічні\nекспорти:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/packages/user_repository/lib/user_repository.dart\"\n\ttitle=\"packages/user_repository/lib/user_repository.dart\"\n/>\n\nТепер, коли пакети `authentication_repository` та `user_repository` готові, ми\nможемо зосередитися на Flutter-додатку.\n\n## Встановлення залежностей\n\nПочнемо з оновлення згенерованого `pubspec.yaml` в корені нашого проєкту:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nМи можемо встановити залежності, виконавши:\n\n<FlutterPubGetSnippet />\n\n## Authentication Bloc\n\n`AuthenticationBloc` відповідатиме за реагування на зміни стану автентифікації\n(наданого `AuthenticationRepository`) та випускатиме стани, на які ми зможемо\nреагувати в шарі представлення.\n\nРеалізація `AuthenticationBloc` знаходиться всередині `lib/authentication`,\nоскільки ми розглядаємо автентифікацію як функціональний модуль у нашому шарі\nдодатку.\n\n```\n├── lib\n│   ├── app.dart\n│   ├── authentication\n│   │   ├── authentication.dart\n│   │   └── bloc\n│   │       ├── authentication_bloc.dart\n│   │       ├── authentication_event.dart\n│   │       └── authentication_state.dart\n│   ├── main.dart\n```\n\n:::tip\n\nВикористовуйте\n[розширення VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nабо [плагін IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) для\nавтоматичного створення блоків.\n\n:::\n\n### authentication_event.dart\n\nЕкземпляри `AuthenticationEvent` будуть вхідними даними для `AuthenticationBloc`\nта оброблятимуться для випуску нових екземплярів `AuthenticationState`.\n\nУ цьому додатку `AuthenticationBloc` реагуватиме на дві різні події:\n\n- `AuthenticationSubscriptionRequested`: початкова подія, яка повідомляє bloc\n  підписатися на потік `AuthenticationStatus`\n- `AuthenticationLogoutPressed`: повідомляє bloc про дію виходу користувача\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_event.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_event.dart\"\n/>\n\nДалі розглянемо `AuthenticationState`.\n\n### authentication_state.dart\n\nЕкземпляри `AuthenticationState` будуть виходом `AuthenticationBloc` та\nспоживатимуться шаром представлення.\n\nКлас `AuthenticationState` має три іменовані конструктори:\n\n- `AuthenticationState.unknown()`: стан за замовчуванням, який вказує, що bloc\n  ще не знає, чи автентифікований поточний користувач.\n\n- `AuthenticationState.authenticated()`: стан, який вказує, що користувач наразі\n  автентифікований.\n\n- `AuthenticationState.unauthenticated()`: стан, який вказує, що користувач\n  наразі не автентифікований.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_state.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_state.dart\"\n/>\n\nТепер, коли ми розглянули реалізації `AuthenticationEvent` та\n`AuthenticationState`, давайте подивимося на `AuthenticationBloc`.\n\n### authentication_bloc.dart\n\n`AuthenticationBloc` керує станом автентифікації додатку, який використовується\nдля визначення, чи показувати користувачу сторінку входу чи домашню сторінку.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart\"\n\ttitle=\"lib/authentication/bloc/authentication_bloc.dart\"\n/>\n\n`AuthenticationBloc` залежить як від `AuthenticationRepository`, так і від\n`UserRepository` та визначає початковий стан як `AuthenticationState.unknown()`.\n\nУ тілі конструктора підкласи `AuthenticationEvent` зіставляються з відповідними\nобробниками подій.\n\nВ обробнику подій `_onSubscriptionRequested` `AuthenticationBloc` використовує\n`emit.onEach` для підписки на потік `status` `AuthenticationRepository` та\nвипуску стану у відповідь на кожен `AuthenticationStatus`.\n\n`emit.onEach` створює підписку на потік внутрішньо та піклується про її\nскасування, коли `AuthenticationBloc` або потік `status` закривається.\n\nЯкщо потік `status` випускає помилку, `addError` пересилає помилку та stackTrace\nбудь-якому `BlocObserver`, що слухає.\n\n:::caution\n\nЯкщо `onError` пропущено, будь-які помилки в потоці `status` вважаються\nнеобробленими та будуть викинуті через `onEach`. Як наслідок, підписка на потік\n`status` буде скасована.\n\n:::\n\n:::tip\n\n[`BlocObserver`](/uk/bloc-concepts/#blocobserver-1) чудово підходить для\nлогування подій, помилок та змін стану Bloc, особливо в контексті аналітики та\nзвітування про збої.\n\n:::\n\nКоли потік `status` випускає `AuthenticationStatus.unknown` або\n`unauthenticated`, випускається відповідний `AuthenticationState`.\n\nКоли випускається `AuthenticationStatus.authenticated`, `AuthenticationBloc`\nзапитує користувача через `UserRepository`.\n\n## main.dart\n\nДалі ми можемо замінити стандартний `main.dart` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n## App\n\n`app.dart` міститиме кореневий віджет `App` для всього додатку.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n`app.dart` розділений на дві частини: `App` та `AppView`. `App` відповідає за\nстворення/надання `AuthenticationBloc`, який споживатиметься `AppView`. Це\nрозділення дозволить нам легко тестувати обидва віджети `App` та `AppView`\nнадалі.\n\n:::\n\n:::note\n\n`RepositoryProvider` використовується для надання єдиного екземпляра\n`AuthenticationRepository` всьому додатку, що знадобиться пізніше.\n\n:::\n\nЗа замовчуванням `BlocProvider` є лінивим і не викликає `create`, поки до Bloc\nне звернуться вперше. Оскільки `AuthenticationBloc` повинен негайно підписатися\nна потік `AuthenticationStatus` (через подію\n`AuthenticationSubscriptionRequested`), ми можемо явно відмовитися від цієї\nповедінки, встановивши `lazy: false`.\n\n`AppView` є `StatefulWidget`, оскільки підтримує `GlobalKey`, який\nвикористовується для доступу до `NavigatorState`. За замовчуванням `AppView`\nвідображатиме `SplashPage` (яку ми побачимо пізніше) та використовує\n`BlocListener` для навігації на різні сторінки на основі змін\n`AuthenticationState`.\n\n## Splash\n\nФункціональність splash міститиме просте представлення, яке відображатиметься\nпри запуску додатку, поки додаток визначає, чи автентифікований користувач.\n\n```\nlib\n└── splash\n    ├── splash.dart\n    └── view\n        └── splash_page.dart\n```\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/splash/view/splash_page.dart\"\n\ttitle=\"lib/splash/view/splash_page.dart\"\n/>\n\n:::tip\n\n`SplashPage` надає статичний `Route`, що дуже спрощує навігацію через\n`Navigator.of(context).push(SplashPage.route())`;\n\n:::\n\n## Login\n\nФункціональність входу містить `LoginPage`, `LoginForm` та `LoginBloc` і\nдозволяє користувачам вводити ім'я користувача та пароль для входу в додаток.\n\n```\n├── lib\n│   ├── login\n│   │   ├── bloc\n│   │   │   ├── login_bloc.dart\n│   │   │   ├── login_event.dart\n│   │   │   └── login_state.dart\n│   │   ├── login.dart\n│   │   ├── models\n│   │   │   ├── models.dart\n│   │   │   ├── password.dart\n│   │   │   └── username.dart\n│   │   └── view\n│   │       ├── login_form.dart\n│   │       ├── login_page.dart\n│   │       └── view.dart\n```\n\n### Моделі входу\n\nМи використовуємо [`package:formz`](https://pub.dev/packages/formz) для\nстворення повторно використовуваних та стандартних моделей для `username` та\n`password`.\n\n#### Username\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/username.dart\"\n\ttitle=\"lib/login/models/username.dart\"\n/>\n\nДля простоти ми лише перевіряємо, що ім'я користувача не порожнє, але на\nпрактиці можна вимагати використання спеціальних символів, довжину тощо...\n\n#### Password\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/password.dart\"\n\ttitle=\"lib/login/models/password.dart\"\n/>\n\nЗнову ж таки, ми просто виконуємо просту перевірку, щоб пароль не був порожнім.\n\n#### Barrel моделей\n\nЯк і раніше, є barrel-файл `models.dart` для зручного імпорту моделей `Username`\nта `Password` одним імпортом.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/models/models.dart\"\n\ttitle=\"lib/login/models/models.dart\"\n/>\n\n### Login Bloc\n\n`LoginBloc` керує станом `LoginForm` та відповідає за валідацію введення імені\nкористувача та пароля, а також стану форми.\n\n#### login_event.dart\n\nУ цьому додатку є три різні типи `LoginEvent`:\n\n- `LoginUsernameChanged`: повідомляє bloc про зміну імені користувача.\n- `LoginPasswordChanged`: повідомляє bloc про зміну пароля.\n- `LoginSubmitted`: повідомляє bloc про відправку форми.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_event.dart\"\n\ttitle=\"lib/login/bloc/login_event.dart\"\n/>\n\n#### login_state.dart\n\n`LoginState` міститиме статус форми, а також стани введення імені користувача та\nпароля.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_state.dart\"\n\ttitle=\"lib/login/bloc/login_state.dart\"\n/>\n\n:::note\n\nМоделі `Username` та `Password` використовуються як частина `LoginState`, а\nстатус також є частиною [package:formz](https://pub.dev/packages/formz).\n\n:::\n\n#### login_bloc.dart\n\n`LoginBloc` відповідає за реагування на взаємодії користувача в `LoginForm` та\nобробку валідації та відправки форми.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/bloc/login_bloc.dart\"\n\ttitle=\"lib/login/bloc/login_bloc.dart\"\n/>\n\n`LoginBloc` залежить від `AuthenticationRepository`, оскільки при відправці\nформи він викликає `logIn`. Початковий стан bloc є `pure`, що означає, що ні\nполя введення, ні форма ще не були торкнуті або взаємодіяли з ними.\n\nКожного разу, коли змінюється `username` або `password`, bloc створює \"брудний\"\nваріант моделі `Username`/`Password` та оновлює статус форми через API\n`Formz.validate`.\n\nКоли додається подія `LoginSubmitted`, якщо поточний статус форми дійсний, bloc\nробить виклик `logIn` та оновлює статус на основі результату запиту.\n\nДалі розглянемо `LoginPage` та `LoginForm`.\n\n### Login Page\n\n`LoginPage` відповідає за надання `Route`, а також за створення та надання\n`LoginBloc` для `LoginForm`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_page.dart\"\n\ttitle=\"lib/login/view/login_page.dart\"\n/>\n\n:::note\n\n`context.read<AuthenticationRepository>()` використовується для пошуку\nекземпляра `AuthenticationRepository` через `BuildContext`.\n\n:::\n\n### Login Form\n\n`LoginForm` обробляє сповіщення `LoginBloc` про події користувача, а також\nреагує на зміни стану за допомогою `BlocBuilder` та `BlocListener`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/login/view/login_form.dart\"\n\ttitle=\"lib/login/view/login_form.dart\"\n/>\n\n`BlocListener` використовується для показу `SnackBar`, якщо відправка входу не\nвдалася. Крім того, `context.select` використовується для ефективного доступу до\nконкретних частин `LoginState` для кожного віджета, запобігаючи зайвим\nперебудовам. Зворотний виклик `onChanged` використовується для сповіщення\n`LoginBloc` про зміни імені користувача/пароля.\n\nВіджет `_LoginButton` увімкнений лише тоді, коли статус форми дійсний, а\n`CircularProgressIndicator` показується на його місці під час відправки форми.\n\n## Home\n\nПісля успішного запиту `logIn` стан `AuthenticationBloc` зміниться на\n`authenticated`, і користувач буде переведений на `HomePage`, де ми відображаємо\n`id` користувача, а також кнопку виходу.\n\n```\n├── lib\n│   ├── home\n│   │   ├── home.dart\n│   │   └── view\n│   │       └── home_page.dart\n```\n\n### Home Page\n\n`HomePage` може отримати id поточного користувача через\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` та відображає\nйого за допомогою віджета `Text`. Крім того, при натисканні кнопки виходу подія\n`AuthenticationLogoutPressed` додається до `AuthenticationBloc`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_login/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\n:::note\n\n`context.select((AuthenticationBloc bloc) => bloc.state.user.id)` викликатиме\nоновлення при зміні id користувача.\n\n:::\n\nНа цьому етапі ми маємо досить надійну реалізацію входу, і ми відокремили шар\nпредставлення від шару бізнес-логіки за допомогою Bloc.\n\nПовний вихідний код (включаючи unit та widget тести) цього прикладу можна знайти\n[тут](https://github.com/felangel/Bloc/tree/master/examples/flutter_login).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-timer.mdx",
    "content": "---\ntitle: Таймер Flutter\ndescription:\n  Детальний посібник зі створення додатку-таймера на Flutter з використанням\n  bloc.\nsidebar:\n  order: 2\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-timer/FlutterCreateSnippet.astro';\nimport TimerBlocEmptySnippet from '~/components/tutorials/flutter-timer/TimerBlocEmptySnippet.astro';\nimport TimerBlocInitialStateSnippet from '~/components/tutorials/flutter-timer/TimerBlocInitialStateSnippet.astro';\nimport TimerBlocTickerSnippet from '~/components/tutorials/flutter-timer/TimerBlocTickerSnippet.astro';\nimport TimerBlocOnStartedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnStartedSnippet.astro';\nimport TimerBlocOnTickedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnTickedSnippet.astro';\nimport TimerBlocOnPausedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnPausedSnippet.astro';\nimport TimerBlocOnResumedSnippet from '~/components/tutorials/flutter-timer/TimerBlocOnResumedSnippet.astro';\nimport TimerPageSnippet from '~/components/tutorials/flutter-timer/TimerPageSnippet.astro';\nimport ActionsSnippet from '~/components/tutorials/flutter-timer/ActionsSnippet.astro';\nimport BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nУ наступному підручнику ми розглянемо, як створити додаток-таймер з\nвикористанням бібліотеки bloc. Готовий додаток повинен виглядати так:\n\n![demo](~/assets/tutorials/flutter-timer.gif)\n\n## Ключові теми\n\n- Спостереження за змінами стану за допомогою\n  [BlocObserver](/uk/bloc-concepts#blocobserver).\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), віджет Flutter, який\n  обробляє побудову віджета у відповідь на нові стани.\n- Запобігання зайвим перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- Навчитися використовувати `StreamSubscription` у Bloc.\n- Запобігання зайвим перебудовам за допомогою `buildWhen`.\n\n## Налаштування\n\nПочнемо зі створення нового Flutter проєкту:\n\n<FlutterCreateSnippet />\n\nПотім ми можемо замінити вміст pubspec.yaml на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nМи використовуватимемо пакети\n[flutter_bloc](https://pub.dev/packages/flutter_bloc) та\n[equatable](https://pub.dev/packages/equatable) у цьому додатку.\n\n:::\n\nДалі виконайте `flutter pub get` для встановлення всіх залежностей.\n\n## Структура проєкту\n\n```\n├── lib\n|   ├── timer\n│   │   ├── bloc\n│   │   │   └── timer_bloc.dart\n|   |   |   └── timer_event.dart\n|   |   |   └── timer_state.dart\n│   │   └── view\n│   │   |   ├── timer_page.dart\n│   │   ├── timer.dart\n│   ├── app.dart\n│   ├── ticker.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n## Ticker\n\nTicker буде нашим джерелом даних для додатку-таймера. Він надаватиме потік\nтіків, на які ми зможемо підписатися та реагувати.\n\nПочнемо зі створення `ticker.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/ticker.dart\"\n\ttitle=\"lib/ticker.dart\"\n/>\n\nВсе, що робить наш клас `Ticker` — це надає функцію tick, яка приймає кількість\nтіків (секунд) і повертає потік, який кожну секунду випускає секунди, що\nзалишилися.\n\nДалі нам потрібно створити наш `TimerBloc`, який споживатиме `Ticker`.\n\n## Timer Bloc\n\n### TimerState\n\nПочнемо з визначення `TimerStates`, в яких може перебувати наш `TimerBloc`.\n\nСтан нашого `TimerBloc` може бути одним з наступних:\n\n- `TimerInitial`: готовий почати зворотний відлік від вказаної тривалості.\n- `TimerRunInProgress`: активно відлічує від вказаної тривалості.\n- `TimerRunPause`: призупинений на певній тривалості, що залишилася.\n- `TimerRunComplete`: завершений з тривалістю 0, що залишилася.\n\nКожен з цих станів матиме вплив на інтерфейс користувача та дії, які може\nвиконувати користувач. Наприклад:\n\n- якщо стан `TimerInitial`, користувач зможе запустити таймер.\n- якщо стан `TimerRunInProgress`, користувач зможе призупинити та скинути\n  таймер, а також бачити тривалість, що залишилася.\n- якщо стан `TimerRunPause`, користувач зможе відновити таймер та скинути\n  таймер.\n- якщо стан `TimerRunComplete`, користувач зможе скинути таймер.\n\nДля зберігання всіх файлів bloc разом створимо каталог bloc з\n`bloc/timer_state.dart`.\n\n:::tip\n\nВи можете використовувати розширення\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) або\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nдля автоматичної генерації наступних файлів bloc.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_state.dart\"\n\ttitle=\"lib/timer/bloc/timer_state.dart\"\n/>\n\nЗверніть увагу, що всі `TimerStates` розширюють абстрактний базовий клас\n`TimerState`, який має властивість duration. Це тому, що незалежно від стану\nнашого `TimerBloc`, ми хочемо знати, скільки часу залишилося. Крім того,\n`TimerState` розширює `Equatable` для оптимізації коду, забезпечуючи, що наш\nдодаток не запускає перебудови, якщо відбувається однаковий стан.\n\nДалі визначимо та реалізуємо `TimerEvents`, які оброблятиме наш `TimerBloc`.\n\n### TimerEvent\n\nНаш `TimerBloc` повинен знати, як обробляти наступні події:\n\n- `TimerStarted`: інформує TimerBloc про запуск таймера.\n- `TimerPaused`: інформує TimerBloc про призупинення таймера.\n- `TimerResumed`: інформує TimerBloc про відновлення таймера.\n- `TimerReset`: інформує TimerBloc про скидання таймера до початкового стану.\n- `_TimerTicked`: інформує TimerBloc про те, що стався тік, і йому потрібно\n  оновити свій стан відповідно.\n\nЯкщо ви не використовували розширення\n[IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) або\n[VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc),\nстворіть `bloc/timer_event.dart` і реалізуємо ці події.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_event.dart\"\n\ttitle=\"lib/timer/bloc/timer_event.dart\"\n/>\n\nДалі реалізуємо `TimerBloc`!\n\n### TimerBloc\n\nЯкщо ви ще цього не зробили, створіть `bloc/timer_bloc.dart` та порожній\n`TimerBloc`.\n\n<TimerBlocEmptySnippet />\n\nПерше, що нам потрібно зробити — визначити початковий стан нашого `TimerBloc`. У\nцьому випадку ми хочемо, щоб `TimerBloc` починав зі стану `TimerInitial` з\nпопередньо встановленою тривалістю 1 хвилина (60 секунд).\n\n<TimerBlocInitialStateSnippet />\n\nДалі нам потрібно визначити залежність від нашого `Ticker`.\n\n<TimerBlocTickerSnippet />\n\nМи також визначаємо `StreamSubscription` для нашого `Ticker`, до якої\nповернемося трохи пізніше.\n\nНа цьому етапі залишилося лише реалізувати обробники подій. Для кращої\nчитабельності я виношу кожен обробник подій у окрему допоміжну функцію. Почнемо\nз події `TimerStarted`.\n\n<TimerBlocOnStartedSnippet />\n\nЯкщо `TimerBloc` отримує подію `TimerStarted`, він встановлює стан\n`TimerRunInProgress` з початковою тривалістю. Крім того, якщо вже була відкрита\n`_tickerSubscription`, нам потрібно скасувати її для вивільнення пам'яті. Нам\nтакож потрібно перевизначити метод `close` нашого `TimerBloc`, щоб скасувати\n`_tickerSubscription`, коли `TimerBloc` закривається. Нарешті, ми слухаємо потік\n`_ticker.tick` і на кожному тіку додаємо подію `_TimerTicked` з тривалістю, що\nзалишилася.\n\nДалі реалізуємо обробник подій `_TimerTicked`.\n\n<TimerBlocOnTickedSnippet />\n\nКожного разу, коли отримується подія `_TimerTicked`, якщо тривалість тіку більше\n0, нам потрібно встановити оновлений стан `TimerRunInProgress` з новою\nтривалістю. Інакше, якщо тривалість тіку дорівнює 0, наш таймер завершився, і\nнам потрібно встановити стан `TimerRunComplete`.\n\nТепер реалізуємо обробник подій `TimerPaused`.\n\n<TimerBlocOnPausedSnippet />\n\nВ `_onPaused`, якщо `state` нашого `TimerBloc` є `TimerRunInProgress`, ми можемо\nпризупинити `_tickerSubscription` та встановити стан `TimerRunPause` з поточною\nтривалістю таймера.\n\nДалі реалізуємо обробник подій `TimerResumed`, щоб мати можливість відновити\nтаймер.\n\n<TimerBlocOnResumedSnippet />\n\nОбробник подій `TimerResumed` дуже схожий на обробник подій `TimerPaused`. Якщо\n`TimerBloc` має `state` `TimerRunPause` та отримує подію `TimerResumed`, то він\nвідновлює `_tickerSubscription` та встановлює стан `TimerRunInProgress` з\nпоточною тривалістю.\n\nНарешті, нам потрібно реалізувати обробник подій `TimerReset`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/bloc/timer_bloc.dart\"\n\ttitle=\"lib/timer/bloc/timer_bloc.dart\"\n/>\n\nЯкщо `TimerBloc` отримує подію `TimerReset`, йому потрібно скасувати поточну\n`_tickerSubscription`, щоб не отримувати додаткові тіки, та встановити стан\n`TimerInitial` з початковою тривалістю.\n\nОсь і все щодо `TimerBloc`. Тепер залишилося реалізувати інтерфейс для нашого\nдодатку-таймера.\n\n## UI додатку\n\n### MyApp\n\nМи можемо почати з видалення вмісту `main.dart` та заміни його наступним.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nДалі створимо наш віджет 'App' у `app.dart`, який буде кореневим елементом\nнашого додатку.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\nДалі нам потрібно реалізувати наш віджет `Timer`.\n\n### Timer\n\nНаш віджет `Timer` (`lib/timer/view/timer_page.dart`) відповідатиме за\nвідображення часу, що залишився, а також відповідних кнопок, які дозволять\nкористувачам запускати, призупиняти та скидати таймер.\n\n<TimerPageSnippet />\n\nНаразі ми просто використовуємо `BlocProvider` для доступу до екземпляра нашого\n`TimerBloc`.\n\nДалі ми реалізуємо наш віджет `Actions`, який матиме відповідні дії (запуск,\nпауза та скидання).\n\n### Barrel\n\nДля очищення наших імпортів з розділу `Timer` нам потрібно створити barrel-файл\n`timer/timer.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_timer/lib/timer/timer.dart\"\n\ttitle=\"lib/timer/timer.dart\"\n/>\n\n### Actions\n\n<ActionsSnippet />\n\nВіджет `Actions` — це ще один `StatelessWidget`, який використовує `BlocBuilder`\nдля перебудови UI кожного разу, коли ми отримуємо новий `TimerState`. `Actions`\nвикористовує `context.read<TimerBloc>()` для доступу до екземпляра `TimerBloc`\nта повертає різні `FloatingActionButtons` залежно від поточного стану\n`TimerBloc`. Кожен з `FloatingActionButtons` додає подію у своєму зворотному\nвиклику `onPressed` для сповіщення `TimerBloc`.\n\nЯкщо ви хочете мати точний контроль над тим, коли викликається функція\n`builder`, можна надати необов'язковий `buildWhen` для `BlocBuilder`.\n`buildWhen` приймає попередній стан bloc та поточний стан bloc і повертає\n`boolean`. Якщо `buildWhen` повертає `true`, `builder` буде викликаний зі\n`state`, і віджет перебудується. Якщо `buildWhen` повертає `false`, `builder` не\nбуде викликаний зі `state`, і перебудова не відбудеться.\n\nУ цьому випадку ми не хочемо, щоб віджет `Actions` перебудовувався на кожному\nтіку, оскільки це було б неефективно. Замість цього ми хочемо, щоб `Actions`\nперебудовувався лише тоді, коли змінюється `runtimeType` `TimerState`\n(TimerInitial => TimerRunInProgress, TimerRunInProgress => TimerRunPause тощо).\n\nЯк наслідок, якби ми випадково розфарбовували віджети при кожній перебудові, це\nвиглядало б так:\n\n![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif)\n\n:::note\n\nНавіть хоча віджет `Text` перебудовується на кожному тіку, ми перебудовуємо\n`Actions` лише тоді, коли їх потрібно перебудувати.\n\n:::\n\n### Background\n\nНарешті, додамо віджет фону наступним чином:\n\n<BackgroundSnippet />\n\n### Збираємо все разом\n\nОсь і все! На цьому етапі ми маємо досить надійний додаток-таймер, який\nефективно перебудовує лише ті віджети, які потрібно перебудувати.\n\nПовний вихідний код цього прикладу можна знайти\n[тут](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-todos.mdx",
    "content": "---\ntitle: Список справ Flutter\ndescription:\n  Детальний посібник зі створення додатку списку справ на Flutter з\n  використанням bloc.\nsidebar:\n  order: 6\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-todos/FlutterCreateSnippet.astro';\nimport ActivateVeryGoodCLISnippet from '~/components/tutorials/flutter-todos/ActivateVeryGoodCLISnippet.astro';\nimport FlutterCreatePackagesSnippet from '~/components/tutorials/flutter-todos/FlutterCreatePackagesSnippet.astro';\nimport ProjectStructureSnippet from '~/components/tutorials/flutter-todos/ProjectStructureSnippet.astro';\nimport VeryGoodPackagesGetSnippet from '~/components/tutorials/flutter-todos/VeryGoodPackagesGetSnippet.astro';\nimport HomePageTreeSnippet from '~/components/tutorials/flutter-todos/HomePageTreeSnippet.astro';\nimport TodosOverviewPageTreeSnippet from '~/components/tutorials/flutter-todos/TodosOverviewPageTreeSnippet.astro';\nimport StatsPageTreeSnippet from '~/components/tutorials/flutter-todos/StatsPageTreeSnippet.astro';\nimport EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditTodosPageTreeSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nУ наступному підручнику ми створимо додаток списку справ на Flutter з\nвикористанням бібліотеки Bloc.\n\n![demo](~/assets/tutorials/flutter-todos.gif)\n\n## Ключові теми\n\n- [Bloc та Cubit](/uk/bloc-concepts#cubit-проти-bloc) для керування різними\n  станами функціональних модулів.\n- [Шарувата архітектура](/uk/architecture) для розділення відповідальності та\n  сприяння повторному використанню.\n- [BlocObserver](/uk/bloc-concepts#blocobserver) для спостереження за змінами\n  стану.\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), віджет Flutter, який\n  обробляє побудову віджета у відповідь на нові стани.\n- [BlocListener](/uk/flutter-bloc-concepts#bloclistener), віджет Flutter, який\n  виконує побічні ефекти у відповідь на зміни стану.\n- [RepositoryProvider](/uk/flutter-bloc-concepts#repositoryprovider), віджет\n  Flutter для надання сховища дочірнім елементам.\n- [Equatable](/uk/faqs#коли-використовувати-equatable) для запобігання зайвим\n  перебудовам.\n- [MultiBlocListener](/uk/flutter-bloc-concepts#multibloclistener), віджет\n  Flutter, який зменшує вкладеність при використанні кількох BlocListeners.\n\n## Налаштування\n\nПочнемо зі створення нового Flutter проєкту за допомогою\n[very_good_cli](https://pub.dev/packages/very_good_cli).\n\n<FlutterCreateSnippet />\n\n:::note\n\nВстановіть `very_good_cli` за допомогою наступної команди\n\n<ActivateVeryGoodCLISnippet />\n\n:::\n\nДалі створимо пакети `todos_api`, `local_storage_todos_api` та\n`todos_repository` за допомогою `very_good_cli`:\n\n<FlutterCreatePackagesSnippet />\n\nПотім ми можемо замінити вміст `pubspec.yaml` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nНарешті, ми можемо встановити всі залежності:\n\n<VeryGoodPackagesGetSnippet />\n\n## Структура проєкту\n\nСтруктура нашого проєкту додатку повинна виглядати так:\n\n<ProjectStructureSnippet />\n\nМи розділяємо проєкт на кілька пакетів для підтримки явних залежностей кожного\nпакету з чіткими межами, які забезпечують\n[принцип єдиної відповідальності](https://en.wikipedia.org/wiki/Single-responsibility_principle).\nМодуляризація нашого проєкту таким чином має багато переваг, включаючи, але не\nобмежуючись:\n\n- легке повторне використання пакетів у кількох проєктах\n- покращення CI/CD з точки зору ефективності (запуск перевірок лише для коду,\n  який змінився)\n- легке підтримання пакетів ізольовано з їхніми власними наборами тестів,\n  семантичним версіонуванням та циклом/каденцією випуску\n\n## Архітектура\n\n![Діаграма архітектури Todos](~/assets/tutorials/todos-architecture.png)\n\nШарування нашого коду надзвичайно важливе та допомагає нам ітерувати швидко й з\nвпевненістю. Кожен шар має єдину відповідальність і може використовуватися та\nтестуватися ізольовано. Це дозволяє нам утримувати зміни в конкретному шарі, щоб\nмінімізувати вплив на весь додаток. Крім того, шарування нашого додатку дозволяє\nнам легко повторно використовувати бібліотеки у кількох проєктах (особливо щодо\nшару даних).\n\nНаш додаток складається з трьох основних шарів:\n\n- шар даних\n- доменний шар\n- шар функціональності\n  - представлення/UI (віджети)\n  - бізнес-логіка (блоки/кубіти)\n\n**Шар даних**\n\nЦей шар є найнижчим і відповідає за отримання необроблених даних із зовнішніх\nджерел, таких як бази даних, API тощо. Пакети в шарі даних, як правило, не\nповинні залежати від будь-якого UI та можуть бути повторно використані і навіть\nопубліковані на [pub.dev](https://pub.dev) як окремий пакет. У цьому прикладі\nнаш шар даних складається з пакетів `todos_api` та `local_storage_todos_api`.\n\n**Доменний шар**\n\nЦей шар поєднує одного або кількох постачальників даних та застосовує\n\"бізнес-правила\" до даних. Кожен компонент у цьому шарі називається сховищем, і\nкожне сховище зазвичай керує одним доменом. Пакети у шарі сховища повинні\nвзаємодіяти лише з шаром даних. У цьому прикладі наш шар сховища складається з\nпакету `todos_repository`.\n\n**Шар функціональності**\n\nЦей шар містить усю функціональність та варіанти використання, специфічні для\nдодатку. Кожен функціональний модуль зазвичай складається з деякого UI та\nбізнес-логіки. Модулі повинні бути незалежними один від одного, щоб їх можна\nбуло легко додавати/видаляти без впливу на решту кодової бази. У кожному модулі\nстан та бізнес-логіка керуються блоками. Блоки взаємодіють з нулем або більше\nсховищ. Блоки реагують на події та випускають стани, які ініціюють зміни в UI.\nВіджети в кожному модулі повинні залежати лише від відповідного bloc та\nвідображати UI на основі поточного стану. UI може сповіщати bloc про введення\nкористувача через події. У цьому прикладі наш додаток складатиметься з модулів\n`home`, `todos_overview`, `stats` та `edit_todos`.\n\nТепер, коли ми оглянули шари на високому рівні, давайте почнемо будувати наш\nдодаток, починаючи з шару даних!\n\n## Шар даних\n\nШар даних є найнижчим шаром у нашому додатку та складається з постачальників\nнеоброблених даних. Пакети в цьому шарі в першу чергу стосуються того, звідки/як\nнадходять дані. У цьому випадку наш шар даних складатиметься з `TodosApi`, який\nє інтерфейсом, та `LocalStorageTodosApi`, який є реалізацією `TodosApi` на\nоснові `shared_preferences`.\n\n### TodosApi\n\nПакет `todos_api` експортуватиме загальний інтерфейс для взаємодії/керування\nсправами. Пізніше ми реалізуємо `TodosApi` з використанням `shared_preferences`.\nНаявність абстракції спрощує підтримку інших реалізацій без необхідності\nзмінювати будь-яку іншу частину нашого додатку. Наприклад, ми можемо пізніше\nдодати `FirestoreTodosApi`, що використовує `cloud_firestore` замість\n`shared_preferences`, з мінімальними змінами коду в решті додатку.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/pubspec.yaml\"\n\ttitle=\"packages/todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/src/todos_api.dart\"\n/>\n\n#### Модель Todo\n\nДалі визначимо нашу модель `Todo`.\n\nПерше, що слід зазначити — модель `Todo` не живе в нашому додатку — вона є\nчастиною пакету `todos_api`. Це тому, що `TodosApi` визначає API, які\nповертають/приймають об'єкти `Todo`. Модель — це Dart-представлення\nнеобробленого об'єкта Todo, який зберігатиметься/отримуватиметься.\n\nМодель `Todo` використовує\n[json_serializable](https://pub.dev/packages/json_serializable) для обробки json\n(де)серіалізації. Якщо ви слідуєте за підручником, вам потрібно буде виконати\n[крок генерації коду](https://pub.dev/packages/json_serializable#running-the-code-generator)\nдля вирішення помилок компілятора.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/todo.dart\"\n/>\n\n`json_map.dart` надає `typedef` для перевірки коду та лінтингу.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/json_map.dart\"\n/>\n\nМодель `Todo` визначена в `todos_api/models/todo.dart` та експортується через\n`package:todos_api/todos_api.dart`.\n\n#### Оновлення експортів\n\nНаша модель `Todo` та `TodosApi` експортуються через barrel-файли. Зверніть\nувагу, що ми не імпортуємо модель безпосередньо, а імпортуємо її в\n`lib/src/todos_api.dart` з посиланням на barrel-файл пакету:\n`import 'package:todos_api/todos_api.dart';`. Оновіть barrel-файли для вирішення\nрешти помилок імпорту:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/src/models/models.dart\"\n\ttitle=\"packages/todos_api/lib/src/models/models.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_api/lib/todos_api.dart\"\n\ttitle=\"packages/todos_api/lib/todos_api.dart\"\n/>\n\n#### Потоки проти Future\n\nУ попередній версії цього підручника `TodosApi` був заснований на `Future`\nзамість `Stream`.\n\nПриклад реалізації на основі `Future` дивіться у\n[реалізації Brian Egan в його Architecture Samples](https://github.com/brianegan/flutter_architecture_samples/tree/master/todos_repository_core).\n\nРеалізація на основі `Future` може складатися з двох методів: `loadTodos` та\n`saveTodos` (зверніть увагу на множину). Це означає, що повний список справ\nповинен надаватися методу кожного разу.\n\n- Одне обмеження цього підходу полягає в тому, що стандартна операція CRUD\n  (Create, Read, Update та Delete) вимагає надсилання повного списку справ з\n  кожним викликом. Наприклад, на екрані додавання справи неможливо надіслати\n  лише додану справу. Натомість потрібно відстежувати весь список та надавати\n  повний новий список справ при збереженні оновленого списку.\n- Друге обмеження полягає в тому, що `loadTodos` — це одноразова доставка даних.\n  Додаток повинен містити логіку для періодичного запиту оновлень.\n\nУ поточній реалізації `TodosApi` надає `Stream<List<Todo>>` через `getTodos()`,\nякий повідомлятиме про оновлення в реальному часі всіх підписників, коли список\nсправ змінюється.\n\nКрім того, справи можна створювати, видаляти або оновлювати окремо. Наприклад,\nяк видалення, так і збереження справи виконуються лише з `todo` як аргументом.\nНе потрібно надавати щоразу оновлений список справ.\n\n### LocalStorageTodosApi\n\nЦей пакет реалізує `todos_api` з використанням пакету\n[`shared_preferences`](https://pub.dev/packages/shared_preferences).\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml\"\n\ttitle=\"packages/local_storage_todos_api/pubspec.yaml\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n\ttitle=\"packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart\"\n/>\n\n## Шар сховища\n\n[Сховище](/uk/architecture#сховище) є частиною бізнес-шару. Сховище залежить від\nодного або кількох постачальників даних, що не мають бізнес-цінності, та поєднує\nїхні публічні API у API, що надають бізнес-цінність. Крім того, наявність шару\nсховища допомагає абстрагувати отримання даних від решти додатку, дозволяючи нам\nзмінювати місце/спосіб зберігання даних без впливу на інші частини додатку.\n\n### TodosRepository\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/src/todos_repository.dart\"\n/>\n\nСтворення екземпляра сховища вимагає вказівки `TodosApi`, який ми обговорювали\nраніше в цьому підручнику, тому ми додали його як залежність у наш\n`pubspec.yaml`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/pubspec.yaml\"\n\ttitle=\"packages/todos_repository/pubspec.yaml\"\n/>\n\n#### Експорти бібліотеки\n\nКрім експорту класу `TodosRepository`, ми також експортуємо модель `Todo` з\nпакету `todos_api`. Цей крок запобігає тісному зв'язку між додатком та\nпостачальниками даних.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart\"\n\ttitle=\"packages/todos_repository/lib/todos_repository.dart\"\n/>\n\nМи вирішили повторно експортувати ту саму модель `Todo` з `todos_api`, замість\nперевизначення окремої моделі в `todos_repository`, оскільки в цьому випадку ми\nповністю контролюємо модель даних. У багатьох випадках постачальник даних не\nбуде чимось, що ви контролюєте. У таких випадках стає все важливішим\nпідтримувати власні визначення моделей у шарі сховища для збереження повного\nконтролю над інтерфейсом та контрактом API.\n\n## Шар функціональності\n\n### Точка входу\n\nТочкою входу нашого додатку є `main.dart`. У цьому випадку є три версії:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_development.dart\"\n\ttitle=\"lib/main_development.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_staging.dart\"\n\ttitle=\"lib/main_staging.dart\"\n/>\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/main_production.dart\"\n\ttitle=\"lib/main_production.dart\"\n/>\n\nНайбільш помітним є те, що конкретна реалізація `local_storage_todos_api`\nстворюється в кожній точці входу.\n\n### Завантаження\n\n`bootstrap.dart` завантажує наш `BlocObserver` та створює екземпляр\n`TodosRepository`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/bootstrap.dart\"\n\ttitle=\"lib/bootstrap.dart\"\n/>\n\n### App\n\n`App` обгортає віджет `RepositoryProvider`, який надає сховище всім дочірнім\nелементам. Оскільки і `EditTodoPage`, і `HomePage` є нащадками, всі блоки та\nкубіти можуть отримати доступ до сховища.\n\n`AppView` створює `MaterialApp` та налаштовує тему та локалізації.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/app/app.dart\"\n\ttitle=\"lib/app/app.dart\"\n/>\n\n### Тема\n\nТут надається визначення теми для світлого та темного режиму.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/theme/theme.dart\"\n\ttitle=\"lib/theme/theme.dart\"\n/>\n\n### Home\n\nФункціональний модуль home відповідає за керування станом поточно обраної\nвкладки та відображення правильного піддерева.\n\n#### HomeState\n\nЄ лише два стани, пов'язані з двома екранами: `todos` та `stats`.\n\n:::note\n\n`EditTodo` є окремим маршрутом, тому він не є частиною `HomeState`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_state.dart\"\n\ttitle=\"lib/home/cubit/home_state.dart\"\n/>\n\n#### HomeCubit\n\nCubit є доречним у цьому випадку через простоту бізнес-логіки. Ми маємо один\nметод `setTab` для зміни вкладки.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/cubit/home_cubit.dart\"\n\ttitle=\"lib/home/cubit/home_cubit.dart\"\n/>\n\n#### HomeView\n\n`view.dart` є barrel-файлом, який експортує всі відповідні компоненти UI для\nмодуля home.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/view.dart\"\n\ttitle=\"lib/home/view/view.dart\"\n/>\n\n`home_page.dart` містить UI для кореневої сторінки, яку побачить користувач при\nзапуску додатку.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/home/view/home_page.dart\"\n\ttitle=\"lib/home/view/home_page.dart\"\n/>\n\nСпрощене представлення дерева віджетів для `HomePage`:\n\n<HomePageTreeSnippet />\n\n`HomePage` надає екземпляр `HomeCubit` для `HomeView`. `HomeView` використовує\n`context.select` для вибіркової перебудови кожного разу, коли змінюється\nвкладка. Це дозволяє нам легко тестувати `HomeView`, надаючи мок `HomeCubit` та\nпідставляючи стан.\n\n`BottomAppBar` містить віджети `HomeTabButton`, які викликають `setTab` на\n`HomeCubit`. Екземпляр cubit знаходиться через `context.read`, і відповідний\nметод викликається на екземплярі cubit.\n\n:::caution\n\n`context.read` не слухає зміни, він просто використовується для доступу до\n`HomeCubit` та виклику `setTab`.\n\n:::\n\n### TodosOverview\n\nМодуль огляду справ дозволяє користувачам керувати своїми справами шляхом\nстворення, редагування, видалення та фільтрації справ.\n\n#### TodosOverviewEvent\n\nСтворимо `todos_overview/bloc/todos_overview_event.dart` та визначимо події.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_event.dart\"\n/>\n\n- `TodosOverviewSubscriptionRequested`: початкова подія. У відповідь bloc\n  підписується на потік справ з `TodosRepository`.\n- `TodosOverviewTodoDeleted`: видаляє справу.\n- `TodosOverviewTodoCompletionToggled`: перемикає статус завершення справи.\n- `TodosOverviewToggleAllRequested`: перемикає завершення всіх справ.\n- `TodosOverviewClearCompletedRequested`: видаляє всі завершені справи.\n- `TodosOverviewUndoDeletionRequested`: скасовує видалення справи, наприклад,\n  випадкове видалення.\n- `TodosOverviewFilterChanged`: приймає `TodosViewFilter` як аргумент та змінює\n  відображення, застосовуючи фільтр.\n\n#### TodosOverviewState\n\nСтворимо `todos_overview/bloc/todos_overview_state.dart` та визначимо стан.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_state.dart\"\n/>\n\n`TodosOverviewState` відстежуватиме список справ, активний фільтр,\n`lastDeletedTodo` та статус.\n\n:::note\n\nКрім стандартних гетерів та сетерів, ми маємо власний гетер `filteredTodos`. UI\nвикористовує `BlocBuilder` для доступу до `state.filteredTodos` або\n`state.todos`.\n\n:::\n\n#### TodosOverviewBloc\n\nСтворимо `todos_overview/bloc/todos_overview_bloc.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart\"\n\ttitle=\"lib/todos_overview/bloc/todos_overview_bloc.dart\"\n/>\n\n:::note\n\nBloc не створює екземпляр `TodosRepository` внутрішньо. Замість цього він\nпокладається на екземпляр сховища, впроваджений через конструктор.\n\n:::\n\n##### onSubscriptionRequested\n\nКоли додається `TodosOverviewSubscriptionRequested`, bloc спочатку випускає стан\n`loading`. У відповідь UI може відобразити індикатор завантаження.\n\nДалі ми використовуємо `emit.forEach<List<Todo>>( ... )`, який створює підписку\nна потік справ з `TodosRepository`.\n\n:::caution\n\n`emit.forEach()` — це не те саме `forEach()`, що використовується для списків.\nЦей `forEach` дозволяє bloc підписатися на `Stream` та випускати новий стан для\nкожного оновлення з потоку.\n\n:::\n\n:::note\n\n`stream.listen` ніколи не викликається безпосередньо в цьому підручнику.\nВикористання `await emit.forEach()` є новішим патерном для підписки на потік,\nякий дозволяє bloc керувати підпискою внутрішньо.\n\n:::\n\nТепер, коли підписка оброблена, ми обробимо інші події, такі як додавання,\nмодифікація та видалення справ.\n\n##### onTodoSaved\n\n`_onTodoSaved` просто викликає `_todosRepository.saveTodo(event.todo)`.\n\n:::note\n\n`emit` ніколи не викликається з `onTodoSaved` та багатьох інших обробників\nподій. Замість цього вони повідомляють сховище, яке випускає оновлений список\nчерез потік справ. Дивіться розділ [потік даних](#потік-даних) для детальної\nінформації.\n\n:::\n\n##### Скасування\n\nФункція скасування дозволяє користувачам відновити останній видалений елемент.\n\n`_onTodoDeleted` робить дві речі. По-перше, він випускає новий стан зі справою,\nяку потрібно видалити. Потім видаляє справу через виклик сховища.\n\n`_onUndoDeletionRequested` запускається, коли подія запиту скасування видалення\nнадходить від UI.\n\n`_onUndoDeletionRequested` виконує наступне:\n\n- Тимчасово зберігає копію останньої видаленої справи.\n- Оновлює стан, видаляючи `lastDeletedTodo`.\n- Скасовує видалення.\n\n##### Фільтрація\n\n`_onFilterChanged` випускає новий стан з новим фільтром подій.\n\n#### Моделі\n\nЄ один файл моделі, який стосується фільтрації перегляду.\n\n`todos_view_filter.dart` — це enum, який представляє три фільтри перегляду та\nметоди для застосування фільтра.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart\"\n\ttitle=\"lib/todos_overview/models/todos_view_filter.dart\"\n/>\n\n`models.dart` — це barrel-файл для експортів.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/models/models.dart\"\n\ttitle=\"lib/todos_overview/models/models.dart\"\n/>\n\nДалі розглянемо `TodosOverviewPage`.\n\n#### TodosOverviewPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart\"\n\ttitle=\"lib/todos_overview/view/todos_overview_page.dart\"\n/>\n\nСпрощене представлення дерева віджетів для `TodosOverviewPage`:\n\n<TodosOverviewPageTreeSnippet />\n\nЯк і з модулем `Home`, `TodosOverviewPage` надає екземпляр `TodosOverviewBloc`\nпіддереву через `BlocProvider<TodosOverviewBloc>`. Це обмежує область дії\n`TodosOverviewBloc` лише віджетами нижче `TodosOverviewPage`.\n\nЄ три віджети, які слухають зміни в `TodosOverviewBloc`.\n\n1. Перший — `BlocListener`, який слухає помилки. `listener` буде викликаний лише\n   тоді, коли `listenWhen` поверне `true`. Якщо статус\n   `TodosOverviewStatus.failure`, відображається `SnackBar`.\n\n2. Ми створили другий `BlocListener`, який слухає видалення. Коли справу\n   видалено, відображається `SnackBar` з кнопкою скасування. Якщо користувач\n   натисне скасувати, подія `TodosOverviewUndoDeletionRequested` буде додана до\n   bloc.\n\n3. Нарешті, ми використовуємо `BlocBuilder` для побудови ListView, який\n   відображає справи.\n\n`AppBar` містить два дії, які є випадаючими списками для фільтрації та\nманіпулювання справами.\n\n:::note\n\n`TodosOverviewTodoCompletionToggled` та `TodosOverviewTodoDeleted` додаються до\nbloc через `context.read`.\n\n:::\n\n`view.dart` — це barrel-файл, який експортує `todos_overview_page.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/view/view.dart\"\n\ttitle=\"lib/todos_overview/view/view.dart\"\n/>\n\n#### Віджети\n\n`widgets.dart` — це ще один barrel-файл, який експортує всі компоненти,\nвикористовувані в модулі `todos_overview`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/widgets.dart\"\n\ttitle=\"lib/todos_overview/widgets/widgets.dart\"\n/>\n\n`todo_list_tile.dart` — це `ListTile` для кожного елемента справи.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart\"\n\ttitle=\"lib/todos_overview/widgets/todo_list_tile.dart\"\n/>\n\n`todos_overview_options_button.dart` надає два варіанти маніпулювання справами:\n\n- `toggleAll`\n- `clearCompleted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_options_button.dart\"\n/>\n\n`todos_overview_filter_button.dart` надає три варіанти фільтра:\n\n- `all`\n- `activeOnly`\n- `completedOnly`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n\ttitle=\"lib/todos_overview/widgets/todos_overview_filter_button.dart\"\n/>\n\n### Stats\n\nМодуль статистики відображає статистику про активні та завершені справи.\n\n#### StatsState\n\n`StatsState` відстежує зведену інформацію та поточний `StatsStatus`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_state.dart\"\n\ttitle=\"lib/stats/bloc/stats_state.dart\"\n/>\n\n#### StatsEvent\n\n`StatsEvent` має лише одну подію під назвою `StatsSubscriptionRequested`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_event.dart\"\n\ttitle=\"lib/stats/bloc/stats_event.dart\"\n/>\n\n#### StatsBloc\n\n`StatsBloc` залежить від `TodosRepository` так само, як `TodosOverviewBloc`. Він\nпідписується на потік справ через `_todosRepository.getTodos`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/bloc/stats_bloc.dart\"\n\ttitle=\"lib/stats/bloc/stats_bloc.dart\"\n/>\n\n#### Stats View\n\n`view.dart` — це barrel-файл для `stats_page`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/view.dart\"\n\ttitle=\"lib/stats/view/view.dart\"\n/>\n\n`stats_page.dart` містить UI для сторінки, що відображає статистику справ.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/stats/view/stats_page.dart\"\n\ttitle=\"lib/stats/view/stats_page.dart\"\n/>\n\nСпрощене представлення дерева віджетів для `StatsPage`:\n\n<StatsPageTreeSnippet />\n\n:::caution\n\n`TodosOverviewBloc` та `StatsBloc` обидва взаємодіють з `TodosRepository`, але\nважливо зазначити, що немає прямого зв'язку між блоками. Дивіться розділ\n[потік даних](#потік-даних) для детальної інформації.\n\n:::\n\n### EditTodo\n\nМодуль `EditTodo` дозволяє користувачам редагувати існуючу справу та зберігати\nзміни.\n\n#### EditTodoState\n\n`EditTodoState` відстежує інформацію, необхідну при редагуванні справи.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_state.dart\"\n/>\n\n#### EditTodoEvent\n\nРізні події, на які реагуватиме bloc:\n\n- `EditTodoTitleChanged`\n- `EditTodoDescriptionChanged`\n- `EditTodoSubmitted`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_event.dart\"\n/>\n\n#### EditTodoBloc\n\n`EditTodoBloc` залежить від `TodosRepository`, так само як `TodosOverviewBloc`\nта `StatsBloc`.\n\n:::caution\n\nНа відміну від інших блоків, `EditTodoBloc` не підписується на\n`_todosRepository.getTodos`. Це bloc \"лише для запису\", тобто йому не потрібно\nчитати інформацію зі сховища.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart\"\n\ttitle=\"lib/edit_todo/bloc/edit_todo_bloc.dart\"\n/>\n\n##### Потік даних\n\nНавіть хоча є багато модулів, які залежать від одного й того ж списку справ,\nнемає зв'язку між блоками. Замість цього всі модулі незалежні один від одного та\nпокладаються на `TodosRepository` для прослуховування змін у списку справ, а\nтакож для виконання оновлень списку.\n\nНаприклад, `EditTodos` нічого не знає про модулі `TodosOverview` або `Stats`.\n\nКоли UI відправляє подію `EditTodoSubmitted`:\n\n- `EditTodoBloc` обробляє бізнес-логіку для оновлення `TodosRepository`.\n- `TodosRepository` повідомляє `TodosOverviewBloc` та `StatsBloc`.\n- `TodosOverviewBloc` та `StatsBloc` повідомляють UI, який оновлюється з новим\n  станом.\n\n#### EditTodoPage\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart\"\n\ttitle=\"lib/edit_todo/view/edit_todo_page.dart\"\n/>\n\nЯк і з попередніми модулями, `EditTodosPage` надає екземпляр `EditTodosBloc`\nчерез `BlocProvider`. На відміну від інших модулів, `EditTodosPage` є окремим\nмаршрутом, тому він надає `static` метод `route`. Це спрощує додавання\n`EditTodosPage` до стеку навігації через `Navigator.of(context).push(...)`.\n\nСпрощене представлення дерева віджетів для `EditTodosPage`:\n\n<EditTodosPageTreeSnippet />\n\n## Підсумок\n\nОсь і все, ми завершили підручник!\n\nПовний вихідний код цього прикладу, включаючи unit та widget тести, можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/flutter_todos).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/flutter-weather.mdx",
    "content": "---\ntitle: Погода Flutter\ndescription:\n  Детальний посібник зі створення додатку погоди на Flutter з використанням\n  bloc.\nsidebar:\n  order: 5\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-weather/FlutterCreateSnippet.astro';\nimport FeatureTreeSnippet from '~/components/tutorials/flutter-weather/FeatureTreeSnippet.astro';\nimport FlutterCreateApiClientSnippet from '~/components/tutorials/flutter-weather/FlutterCreateApiClientSnippet.astro';\nimport OpenMeteoModelsTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsTreeSnippet.astro';\nimport LocationJsonSnippet from '~/components/tutorials/flutter-weather/LocationJsonSnippet.astro';\nimport LocationDartSnippet from '~/components/tutorials/flutter-weather/LocationDartSnippet.astro';\nimport WeatherJsonSnippet from '~/components/tutorials/flutter-weather/WeatherJsonSnippet.astro';\nimport WeatherDartSnippet from '~/components/tutorials/flutter-weather/WeatherDartSnippet.astro';\nimport OpenMeteoModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoModelsBarrelTreeSnippet.astro';\nimport OpenMeteoLibrarySnippet from '~/components/tutorials/flutter-weather/OpenMeteoLibrarySnippet.astro';\nimport BuildRunnerBuildSnippet from '~/components/tutorials/flutter-weather/BuildRunnerBuildSnippet.astro';\nimport OpenMeteoApiClientTreeSnippet from '~/components/tutorials/flutter-weather/OpenMeteoApiClientTreeSnippet.astro';\nimport LocationSearchMethodSnippet from '~/components/tutorials/flutter-weather/LocationSearchMethodSnippet.astro';\nimport GetWeatherMethodSnippet from '~/components/tutorials/flutter-weather/GetWeatherMethodSnippet.astro';\nimport FlutterTestCoverageSnippet from '~/components/tutorials/flutter-weather/FlutterTestCoverageSnippet.astro';\nimport FlutterCreateRepositorySnippet from '~/components/tutorials/flutter-weather/FlutterCreateRepositorySnippet.astro';\nimport RepositoryModelsBarrelTreeSnippet from '~/components/tutorials/flutter-weather/RepositoryModelsBarrelTreeSnippet.astro';\nimport WeatherRepositoryLibrarySnippet from '~/components/tutorials/flutter-weather/WeatherRepositoryLibrarySnippet.astro';\nimport WeatherCubitTreeSnippet from '~/components/tutorials/flutter-weather/WeatherCubitTreeSnippet.astro';\nimport WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/WeatherBarrelDartSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nУ цьому підручнику ми створимо додаток погоди на Flutter, який демонструє\nкерування кількома cubit-ами для реалізації динамічної теми, pull-to-refresh та\nбагато іншого. Наш додаток погоди отримуватиме дані про погоду в реальному часі\nз публічного API OpenMeteo та демонструватиме, як розділити наш додаток на шари\n(дані, сховище, бізнес-логіка та представлення).\n\n![demo](~/assets/tutorials/flutter-weather.gif)\n\n## Вимоги до проєкту\n\nНаш додаток повинен дозволяти користувачам\n\n- Шукати місто на спеціальній сторінці пошуку\n- Бачити приємне відображення даних про погоду, отриманих від\n  [Open Meteo API](https://open-meteo.com)\n- Змінювати одиниці вимірювання (метричні чи імперські)\n\nДодатково,\n\n- Тема додатку повинна відображати погоду для обраного міста\n- Стан додатку повинен зберігатися між сесіями: тобто додаток повинен пам'ятати\n  свій стан після закриття та повторного відкриття (використовуючи\n  [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc))\n\n## Ключові концепції\n\n- Спостереження за змінами стану за допомогою\n  [BlocObserver](/uk/bloc-concepts#blocobserver).\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), віджет Flutter, який\n  надає bloc своїм дочірнім елементам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), віджет Flutter, який\n  обробляє побудову віджета у відповідь на нові стани.\n- Запобігання зайвим перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- [RepositoryProvider](/uk/flutter-bloc-concepts#repositoryprovider), віджет\n  Flutter, який надає сховище своїм дочірнім елементам.\n- [BlocListener](/uk/flutter-bloc-concepts#bloclistener), віджет Flutter, який\n  викликає код слухача у відповідь на зміни стану в bloc.\n- [MultiBlocProvider](/uk/flutter-bloc-concepts#multiblocprovider), віджет\n  Flutter, який об'єднує кілька BlocProvider віджетів в один.\n- [BlocConsumer](/uk/flutter-bloc-concepts#blocconsumer), віджет Flutter, який\n  надає builder та listener для реагування на нові стани.\n- [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)\n  для керування та збереження стану.\n\n## Налаштування\n\nДля початку створіть новий flutter проєкт\n\n<FlutterCreateSnippet />\n\n### Структура проєкту\n\nНаш додаток складатиметься з ізольованих функціональних модулів у відповідних\nкаталогах. Це дозволяє масштабувати по мірі зростання кількості модулів та\nдозволяє розробникам працювати над різними модулями паралельно.\n\nНаш додаток можна розділити на чотири основні модулі: **search, settings, theme,\nweather**. Створимо ці каталоги.\n\n<FeatureTreeSnippet />\n\n### Архітектура\n\nДотримуючись рекомендацій [архітектури bloc](/uk/architecture), наш додаток\nскладатиметься з кількох шарів.\n\nУ цьому підручнику ось що робитимуть ці шари:\n\n- **Дані**: отримання необроблених даних про погоду з API\n- **Сховище**: абстрагування шару даних та надання доменних моделей для\n  споживання додатком\n- **Бізнес-логіка**: керування станом кожного модуля (інформація про одиниці,\n  деталі міста, теми тощо)\n- **Представлення**: відображення інформації про погоду та збір введення від\n  користувачів (сторінка налаштувань, сторінка пошуку тощо)\n\n## Шар даних\n\nДля цього додатку ми будемо використовувати\n[Open Meteo API](https://open-meteo.com).\n\nМи зосередимося на двох ендпоінтах:\n\n- `https://geocoding-api.open-meteo.com/v1/search?name=$city&count=1` для\n  отримання місцезнаходження за назвою міста\n- `https://api.open-meteo.com/v1/forecast?latitude=$latitude&longitude=$longitude&current_weather=true`\n  для отримання погоди за місцезнаходженням\n\nВідкрийте\n[https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1](https://geocoding-api.open-meteo.com/v1/search?name=chicago&count=1)\nу вашому браузері, щоб побачити відповідь для міста Чикаго. Ми використаємо\n`latitude` та `longitude` з відповіді для запиту до ендпоінта погоди.\n\n`latitude`/`longitude` для Чикаго — `41.85003`/`-87.65005`. Перейдіть за адресою\n[https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true](https://api.open-meteo.com/v1/forecast?latitude=43.0389&longitude=-87.90647&current_weather=true)\nу вашому браузері, і ви побачите відповідь для погоди в Чикаго, яка містить усі\nдані, необхідні для нашого додатку.\n\n### OpenMeteo API Client\n\nOpenMeteo API Client незалежний від нашого додатку. Тому ми створимо його як\nвнутрішній пакет (і навіть зможемо опублікувати його на\n[pub.dev](https://pub.dev)). Потім ми зможемо використовувати пакет, додавши\nйого до `pubspec.yaml` для шару сховища, який оброблятиме запити даних для\nнашого основного додатку погоди.\n\nСтворіть новий каталог на рівні проєкту під назвою `packages`. Цей каталог\nзберігатиме всі наші внутрішні пакети.\n\nУ цьому каталозі виконайте вбудовану команду `flutter create` для створення\nнового пакету під назвою `open_meteo_api` для нашого API клієнта.\n\n<FlutterCreateApiClientSnippet />\n\n### Модель даних погоди\n\nДалі створимо `location.dart` та `weather.dart`, які міститимуть моделі для\nвідповідей ендпоінтів API `location` та `weather`.\n\n<OpenMeteoModelsTreeSnippet />\n\n#### Модель Location\n\nМодель `location.dart` повинна зберігати дані, повернуті API місцезнаходження,\nякі виглядають наступним чином:\n\n<LocationJsonSnippet />\n\nОсь файл `location.dart` в процесі розробки, який зберігає наведену вище\nвідповідь:\n\n<LocationDartSnippet />\n\n#### Модель Weather\n\nДалі попрацюємо над `weather.dart`. Наша модель погоди повинна зберігати дані,\nповернуті API погоди, які виглядають наступним чином:\n\n<WeatherJsonSnippet />\n\nОсь файл `weather.dart` в процесі розробки, який зберігає наведену вище\nвідповідь:\n\n<WeatherDartSnippet />\n\n### Barrel-файли\n\nПоки ми тут, створимо швидко\n[barrel-файл](https://adrianfaciu.dev/posts/barrel-files/) для очищення деяких\nнаших імпортів у подальшому.\n\nСтворіть barrel-файл `models.dart` та експортуйте дві моделі:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/models.dart\"\n/>\n\nСтворімо також barrel-файл рівня пакету `open_meteo_api.dart`\n\n<OpenMeteoModelsBarrelTreeSnippet />\n\nНа верхньому рівні `open_meteo_api.dart` експортуємо моделі:\n\n<OpenMeteoLibrarySnippet />\n\n### Налаштування\n\nНам потрібно мати можливість\n[серіалізувати та десеріалізувати](https://en.wikipedia.org/wiki/Serialization)\nнаші моделі для роботи з даними API. Для цього ми додамо методи `toJson` та\n`fromJson` до наших моделей.\n\nТакож нам потрібен спосіб\n[здійснювати HTTP-запити](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)\nдля отримання даних з API. На щастя, є кілька популярних пакетів саме для цього.\n\nМи використаємо пакети\n[json_annotation](https://pub.dev/packages/json_annotation),\n[json_serializable](https://pub.dev/packages/json_serializable) та\n[build_runner](https://pub.dev/packages/build_runner) для генерації реалізацій\n`toJson` та `fromJson` за нас.\n\nНа наступному кроці ми також використаємо пакет\n[http](https://pub.dev/packages/http) для надсилання мережевих запитів до API\nпогоди, щоб наш додаток міг відображати поточні дані про погоду.\n\nДодамо ці залежності до `pubspec.yaml`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/pubspec.yaml\"\n\ttitle=\"packages/open_meteo_api/pubspec.yaml\"\n/>\n\n:::note\n\nНе забудьте виконати `flutter pub get` після додавання залежностей.\n\n:::\n\n### (Де)серіалізація\n\nДля роботи генерації коду нам потрібно анотувати наш код наступним чином:\n\n- `@JsonSerializable` для позначення класів, які можна серіалізувати\n- `@JsonKey` для надання рядкових представлень імен полів\n- `@JsonValue` для надання рядкових представлень значень полів\n- Реалізувати `JSONConverter` для перетворення об'єктних представлень у JSON\n  представлення\n\nДля кожного файлу також потрібно:\n\n- Імпортувати `json_annotation`\n- Включити згенерований код за допомогою ключового слова\n  [part](https://dart.dev/tools/pub/create-packages#organizing-a-package)\n- Включити методи `fromJson` для десеріалізації\n\n#### Модель Location\n\nОсь наш завершений файл моделі `location.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/location.dart\"\n/>\n\n#### Модель Weather\n\nОсь наш завершений файл моделі `weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/models/weather.dart\"\n/>\n\n#### Створення файлу збірки\n\nУ каталозі `open_meteo_api` створіть файл `build.yaml`. Мета цього файлу —\nобробка невідповідностей між конвенціями іменування в іменах полів\n`json_serializable`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/build.yaml\"\n\ttitle=\"packages/open_meteo_api/build.yaml\"\n/>\n\n#### Генерація коду\n\nВикористаємо `build_runner` для генерації коду.\n\n<BuildRunnerBuildSnippet />\n\n`build_runner` повинен згенерувати файли `location.g.dart` та `weather.g.dart`.\n\n### OpenMeteo API Client\n\nСтворимо наш API клієнт у `open_meteo_api_client.dart` в каталозі `src`.\nСтруктура нашого проєкту тепер повинна виглядати так:\n\n<OpenMeteoApiClientTreeSnippet />\n\nТепер ми можемо використовувати пакет [http](https://pub.dev/packages/http),\nякий ми додали раніше до файлу `pubspec.yaml`, для здійснення HTTP-запитів до\nAPI погоди та використання цієї інформації в нашому додатку.\n\nНаш API клієнт надасть два методи:\n\n- `locationSearch`, який повертає `Future<Location>`\n- `getWeather`, який повертає `Future<Weather>`\n\n#### Location Search\n\nМетод `locationSearch` звертається до API місцезнаходження та генерує помилки\n`LocationRequestFailure` за потреби. Завершений метод виглядає наступним чином:\n\n<LocationSearchMethodSnippet />\n\n#### Get Weather\n\nАналогічно, метод `getWeather` звертається до API погоди та генерує помилки\n`WeatherRequestFailure` за потреби. Завершений метод виглядає наступним чином:\n\n<GetWeatherMethodSnippet />\n\nЗавершений файл виглядає так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n\ttitle=\"packages/open_meteo_api/lib/src/open_meteo_api_client.dart\"\n/>\n\n#### Оновлення barrel-файлу\n\nЗавершимо цей пакет, додавши наш API клієнт до barrel-файлу.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart\"\n\ttitle=\"packages/open_meteo_api/lib/open_meteo_api.dart\"\n/>\n\n### Unit-тести\n\nОсобливо важливо писати unit-тести для шару даних, оскільки він є фундаментом\nнашого додатку. Unit-тести дадуть нам впевненість, що пакет працює як\nочікується.\n\n#### Налаштування\n\nРаніше ми додали пакет [test](https://pub.dev/packages/test) до нашого\npubspec.yaml, що дозволяє легко писати unit-тести.\n\nМи створимо тестовий файл для API клієнта, а також для двох моделей.\n\n#### Тести Location\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/location_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/location_test.dart\"\n/>\n\n#### Тести Weather\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/weather_test.dart\"\n/>\n\n#### Тести API Client\n\nДалі протестуємо наш API клієнт. Ми повинні перевірити, що наш API клієнт\nкоректно обробляє обидва виклики API, включаючи граничні випадки.\n\n:::note\n\nМи не хочемо, щоб наші тести здійснювали реальні виклики API, оскільки наша мета\n— тестувати логіку API клієнта (включаючи всі граничні випадки), а не сам API.\nДля забезпечення узгодженого, контрольованого тестового середовища ми\nвикористаємо [mocktail](https://github.com/felangel/mocktail) (який ми додали до\nфайлу pubspec.yaml раніше) для мокування `http` клієнта.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n\ttitle=\"packages/open_meteo_api/test/open_meteo_api_client_test.dart\"\n/>\n\n#### Покриття тестами\n\nНарешті, зберемо покриття тестами, щоб переконатися, що ми покрили кожен рядок\nкоду хоча б одним тестовим випадком.\n\n<FlutterTestCoverageSnippet />\n\n## Шар сховища\n\nМета нашого шару сховища — абстрагувати шар даних та сприяти комунікації з шаром\nbloc. Роблячи це, решта нашої кодової бази залежить лише від функцій, наданих\nнашим шаром сховища, замість конкретних реалізацій постачальників даних. Це\nдозволяє нам змінювати постачальників даних без порушення будь-якого коду на\nрівні додатку. Наприклад, якщо ми вирішимо мігрувати з цього конкретного API\nпогоди, ми зможемо створити новий API клієнт та замінити його без необхідності\nвносити зміни до публічного API шарів сховища або додатку.\n\n### Налаштування\n\nУ каталозі packages виконайте наступну команду:\n\n<FlutterCreateRepositorySnippet />\n\nМи використаємо ті самі пакети, що і в пакеті `open_meteo_api`, включаючи пакет\n`open_meteo_api` з попереднього кроку. Оновіть ваш `pubspec.yaml` та виконайте\n`flutter pub get`.\n\n:::note\n\nМи використовуємо `path` для вказівки розташування `open_meteo_api`, що дозволяє\nобробляти його як зовнішній пакет з `pub.dev`.\n\n:::\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/pubspec.yaml\"\n\ttitle=\"packages/weather_repository/pubspec.yaml\"\n/>\n\n### Моделі Weather Repository\n\nМи створимо новий файл `weather.dart` для надання доменно-специфічної моделі\nпогоди. Ця модель міститиме лише дані, релевантні для наших бізнес-потреб —\nіншими словами, вона повинна бути повністю від'єднана від API клієнта та\nнеобробленого формату даних. Як зазвичай, ми також створимо barrel-файл\n`models.dart`.\n\n<RepositoryModelsBarrelTreeSnippet />\n\nЦього разу наша модель погоди зберігатиме лише властивості\n`location, temperature, condition`. Ми продовжуватимемо анотувати наш код для\nсеріалізації та десеріалізації.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/weather.dart\"\n/>\n\nОновіть barrel-файл, який ми створили раніше, щоб включити моделі.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart\"\n\ttitle=\"packages/weather_repository/lib/src/models/models.dart\"\n/>\n\n#### Створення файлу збірки\n\nЯк і раніше, потрібно створити `build.yaml` з наступним вмістом:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/build.yaml\"\n\ttitle=\"packages/weather_repository/build.yaml\"\n/>\n\n#### Генерація коду\n\nЯк ми робили раніше, виконайте наступну команду для генерації реалізації\n(де)серіалізації.\n\n<BuildRunnerBuildSnippet />\n\n#### Barrel-файл\n\nСтворимо також barrel-файл рівня пакету з іменем\n`packages/weather_repository/lib/weather_repository.dart` для експорту наших\nмоделей:\n\n<WeatherRepositoryLibrarySnippet />\n\n### Weather Repository\n\nОсновна мета `WeatherRepository` — надати інтерфейс, що абстрагує постачальника\nданих. У цьому випадку `WeatherRepository` матиме залежність від\n`WeatherApiClient` та надаватиме єдиний публічний метод\n`getWeather(String city)`.\n\n:::note\n\nСпоживачі `WeatherRepository` не знають про деталі внутрішньої реалізації, такі\nяк те, що здійснюються два мережеві запити до API погоди. Мета\n`WeatherRepository` — відокремити \"що\" від \"як\" — іншими словами, ми хочемо мати\nспосіб отримати погоду для даного міста, але не турбуємося про те, як або звідки\nці дані надходять.\n\n:::\n\n#### Налаштування\n\nСтворимо файл `weather_repository.dart` у каталозі `src` нашого пакету та\nпопрацюємо над реалізацією сховища.\n\nОсновний метод, на якому ми зосередимося — `getWeather(String city)`. Ми можемо\nреалізувати його за допомогою двох викликів до API клієнта наступним чином:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/src/weather_repository.dart\"\n/>\n\n#### Barrel-файл\n\nОновіть barrel-файл, який ми створили раніше.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart\"\n\ttitle=\"packages/weather_repository/lib/weather_repository.dart\"\n/>\n\n### Unit-тести\n\nЯк і з шаром даних, критично важливо тестувати шар сховища, щоб переконатися, що\nлогіка доменного рівня коректна. Для тестування нашого `WeatherRepository` ми\nвикористаємо бібліотеку [mocktail](https://github.com/felangel/mocktail). Ми\nзамокуємо базовий API клієнт для unit-тестування логіки `WeatherRepository` в\nізольованому, контрольованому середовищі.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart\"\n\ttitle=\"packages/weather_repository/test/weather_repository_test.dart\"\n/>\n\n## Шар бізнес-логіки\n\nУ шарі бізнес-логіки ми будемо споживати доменну модель погоди з\n`WeatherRepository` та надавати модель рівня функціональності, яка буде\nпоказуватися користувачу через UI.\n\n:::note\n\nЦе третій різний тип моделі погоди, який ми реалізуємо. В API клієнті наша\nмодель погоди містила всю інформацію, повернуту API. У шарі сховища наша модель\nпогоди містила лише абстраговану модель на основі нашого бізнес-випадку. У цьому\nшарі наша модель погоди міститиме релевантну інформацію, необхідну саме для\nпоточного набору функціональності.\n\n:::\n\n### Налаштування\n\nОскільки наш шар бізнес-логіки знаходиться в нашому основному додатку, нам\nпотрібно відредагувати `pubspec.yaml` для всього проєкту `flutter_weather` та\nвключити всі пакети, які ми використовуватимемо.\n\n- Використання [equatable](https://pub.dev/packages/equatable) дозволяє\n  порівнювати екземпляри класів стану нашого додатку за допомогою оператора\n  рівності `==`. Під капотом bloc порівнюватиме наші стани, щоб перевірити, чи\n  вони рівні, і якщо ні, ініціюватиме перебудову. Це гарантує, що наше дерево\n  віджетів перебудовуватиметься лише за потреби для підтримки швидкої та чуйної\n  продуктивності.\n- Ми можемо покращити наш інтерфейс за допомогою\n  [google_fonts](https://pub.dev/packages/google_fonts).\n- [HydratedBloc](https://pub.dev/packages/hydrated_bloc) дозволяє нам зберігати\n  стан додатку при закритті та повторному відкритті.\n- Ми включимо пакет `weather_repository`, який ми щойно створили, для отримання\n  поточних даних про погоду!\n\nДля тестування ми включимо звичайний пакет `test` разом з `mocktail` для\nмокування залежностей та [bloc_test](https://pub.dev/packages/bloc_test) для\nзручного тестування одиниць бізнес-логіки, або блоків!\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nДалі ми працюватимемо над шаром додатку в каталозі функціональності `weather`.\n\n### Модель Weather\n\nМета нашої моделі погоди — відстежувати дані про погоду, що відображаються нашим\nдодатком, а також налаштування температури (Цельсій або Фаренгейт).\n\nСтворіть `flutter_weather/lib/weather/models/weather.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/weather.dart\"\n\ttitle=\"lib/weather/models/weather.dart\"\n/>\n\n### Створення файлу збірки\n\nСтворіть `build.yaml` для шару бізнес-логіки.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/build.yaml\"\n\ttitle=\"build.yaml\"\n/>\n\n### Генерація коду\n\nВиконайте `build_runner` для генерації реалізацій (де)серіалізації.\n\n<BuildRunnerBuildSnippet />\n\n### Barrel-файл\n\nЕкспортуємо наші моделі з barrel-файлу\n(`flutter_weather/lib/weather/models/models.dart`):\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/models/models.dart\"\n\ttitle=\"lib/weather/models/models.dart\"\n/>\n\nПотім створимо barrel-файл верхнього рівня для погоди\n(`flutter_weather/lib/weather/weather.dart`);\n\n<WeatherBarrelDartSnippet />\n\n### Weather\n\nМи використаємо `HydratedCubit`, щоб наш додаток запам'ятовував свій стан,\nнавіть після закриття та повторного відкриття.\n\n:::note\n\n`HydratedCubit` є розширенням `Cubit`, яке обробляє збереження та відновлення\nстану між сесіями.\n\n:::\n\n#### Weather State\n\nВикористовуючи розширення\n[Bloc VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\nабо [Bloc IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc), натисніть\nправою кнопкою на каталог `weather` та створіть новий cubit під назвою\n`Weather`. Структура проєкту повинна виглядати так:\n\n<WeatherCubitTreeSnippet />\n\nЄ чотири стани, в яких може перебувати наш додаток погоди:\n\n- `initial` — до завантаження будь-чого\n- `loading` — під час виклику API\n- `success` — якщо виклик API успішний\n- `failure` — якщо виклик API невдалий\n\nEnum `WeatherStatus` представлятиме наведені вище стани.\n\nПовний стан погоди повинен виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_state.dart\"\n\ttitle=\"lib/weather/cubit/weather_state.dart\"\n/>\n\n#### Weather Cubit\n\nТепер, коли ми визначили `WeatherState`, напишемо `WeatherCubit`, який надасть\nнаступні методи:\n\n- `fetchWeather(String? city)` — використовує сховище погоди для спроби отримати\n  об'єкт погоди для даного міста\n- `refreshWeather()` — отримує новий об'єкт погоди, використовуючи сховище\n  погоди на основі поточного стану погоди\n- `toggleUnits()` — перемикає стан між Цельсієм та Фаренгейтом\n- `fromJson(Map<String, dynamic> json)`, `toJson(WeatherState state)` —\n  використовуються для збереження\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/cubit/weather_cubit.dart\"\n\ttitle=\"lib/weather/cubit/weather_cubit.dart\"\n/>\n\n:::note\n\nНе забудьте згенерувати код (де)серіалізації через:\n\n<BuildRunnerBuildSnippet />\n:::\n\n### Unit-тести\n\nАналогічно шарам даних та сховища, критично важливо тестувати шар бізнес-логіки\nза допомогою unit-тестів, щоб переконатися, що логіка рівня функціональності\nпрацює як очікується. Ми використовуватимемо\n[bloc_test](https://pub.dev/packages/bloc_test) на додаток до `mocktail` та\n`test`.\n\nДодамо пакети `test`, `bloc_test` та `mocktail` до `dev_dependencies`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n:::note\n\nПакет [bloc_test](https://pub.dev/packages/bloc_test) дозволяє нам легко\nпідготувати наші блоки для тестування, обробляти зміни стану та перевіряти\nрезультати послідовним чином.\n\n:::\n\n#### Тести Weather Cubit\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart\"\n\ttitle=\"test/weather/cubit/weather_cubit_test.dart\"\n/>\n\n## Шар представлення\n\n### Weather Page\n\nМи почнемо з `WeatherPage`, яка використовує `BlocProvider` для надання\nекземпляра `WeatherCubit` дереву віджетів.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/view/weather_page.dart\"\n\ttitle=\"lib/weather/view/weather_page.dart\"\n/>\n\nВи помітите, що сторінка залежить від віджетів `SettingsPage` та `SearchPage`,\nякі ми створимо далі.\n\n### SettingsPage\n\nСторінка налаштувань дозволяє користувачам оновлювати свої уподобання щодо\nодиниць температури.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/settings/view/settings_page.dart\"\n\ttitle=\"lib/settings/view/settings_page.dart\"\n/>\n\n### SearchPage\n\nСторінка пошуку дозволяє користувачам вводити назву бажаного міста та передає\nрезультат пошуку попередньому маршруту через `Navigator.of(context).pop`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/search/view/search_page.dart\"\n\ttitle=\"lib/search/view/search_page.dart\"\n/>\n\n### Віджети погоди\n\nДодаток відображатиме різні екрани залежно від чотирьох можливих станів\n`WeatherCubit`.\n\n#### WeatherEmpty\n\nЦей екран відображатиметься, коли немає даних для показу, оскільки користувач ще\nне вибрав місто.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_empty.dart\"\n\ttitle=\"lib/weather/widgets/weather_empty.dart\"\n/>\n\n#### WeatherError\n\nЦей екран відображатиметься, якщо виникне помилка.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_error.dart\"\n\ttitle=\"lib/weather/widgets/weather_error.dart\"\n/>\n\n#### WeatherLoading\n\nЦей екран відображатиметься під час завантаження даних додатком.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_loading.dart\"\n\ttitle=\"lib/weather/widgets/weather_loading.dart\"\n/>\n\n#### WeatherPopulated\n\nЦей екран відображатиметься після того, як користувач обрав місто та ми отримали\nдані.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/weather_populated.dart\"\n\ttitle=\"lib/weather/widgets/weather_populated.dart\"\n/>\n\n### Barrel-файл\n\nДодамо ці стани до barrel-файлу для очищення наших імпортів.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/weather/widgets/widgets.dart\"\n\ttitle=\"lib/weather/widgets/widgets.dart\"\n/>\n\n### Точка входу\n\nНаш `main.dart` файл повинен ініціалізувати `WeatherApp` та `BlocObserver` (для\nцілей налагодження), а також налаштувати `HydratedStorage` для збереження стану\nміж сесіями.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\nНаш віджет `app.dart` обробляє побудову представлення `WeatherPage`, яке ми\nстворили раніше, та використовує `BlocProvider` для впровадження нашого\n`WeatherCubit`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n### Widget-тести\n\nБібліотека [`bloc_test`](https://pub.dev/packages/bloc_test) також надає\n`MockBlocs` та `MockCubits`, які спрощують тестування UI. Ми можемо мокувати\nстани різних cubit-ів та переконатися, що UI реагує коректно.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_weather/test/weather/view/weather_page_test.dart\"\n\ttitle=\"test/weather/view/weather_page_test.dart\"\n/>\n\n:::note\n\nМи використовуємо `MockWeatherCubit` разом з API `when` з `mocktail` для\nпідставлення стану cubit у кожному тестовому випадку. Це дозволяє нам симулювати\nвсі стани та перевіряти, що UI працює коректно за будь-яких обставин.\n\n:::\n\n## Підсумок\n\nОсь і все, ми завершили підручник!\n\nМи можемо запустити фінальний додаток за допомогою команди `flutter run`.\n\nПовний вихідний код цього прикладу, включаючи unit та widget тести, можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/flutter_weather).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/github-search.mdx",
    "content": "---\ntitle: Пошук GitHub\ndescription:\n  Детальний посібник зі створення додатку пошуку GitHub у Flutter та AngularDart\n  з використанням bloc.\nsidebar:\n  order: 9\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport SetupSnippet from '~/components/tutorials/github-search/SetupSnippet.astro';\nimport DartPubGetSnippet from '~/components/tutorials/github-search/DartPubGetSnippet.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/github-search/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/github-search/StagehandSnippet.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/github-search/ActivateStagehandSnippet.astro';\n\n![advanced](https://img.shields.io/badge/level-advanced-red.svg)\n\nУ наступному посібнику ми створимо додаток пошуку GitHub у Flutter та\nAngularDart, щоб продемонструвати, як ми можемо спільно використовувати шари\nданих та бізнес-логіки між двома проєктами.\n\n![demo](~/assets/tutorials/flutter-github-search.gif)\n\n![demo](~/assets/tutorials/ngdart-github-search.gif)\n\n## Ключові теми\n\n- [BlocProvider](/uk/flutter-bloc-concepts#blocprovider), Flutter віджет, який\n  надає bloc своїм нащадкам.\n- [BlocBuilder](/uk/flutter-bloc-concepts#blocbuilder), Flutter віджет, який\n  обробляє побудову віджета у відповідь на нові стани.\n- Використання Cubit замість Bloc.\n  [У чому різниця?](/uk/bloc-concepts#cubit-проти-bloc)\n- Запобігання непотрібним перебудовам за допомогою\n  [Equatable](/uk/faqs#коли-використовувати-equatable).\n- Використання користувацького `EventTransformer` з\n  [`bloc_concurrency`](https://pub.dev/packages/bloc_concurrency).\n- Виконання мережевих запитів за допомогою пакету `http`.\n\n## Спільна бібліотека пошуку GitHub\n\nСпільна бібліотека пошуку GitHub міститиме моделі, провайдер даних, сховище, а\nтакож bloc, який буде спільно використовуватися між AngularDart та Flutter.\n\n### Налаштування\n\nМи почнемо зі створення нової директорії для нашого додатку.\n\n<SetupSnippet />\n\n:::note\n\nДиректорія `common_github_search` міститиме спільну бібліотеку.\n\n:::\n\nНам потрібно створити `pubspec.yaml` з необхідними залежностями.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/pubspec.yaml\"\n\ttitle=\"common_github_search/pubspec.yaml\"\n/>\n\nНарешті, нам потрібно встановити наші залежності.\n\n<DartPubGetSnippet />\n\nЦе все для налаштування проєкту! Тепер ми можемо приступити до роботи над\nстворенням пакету `common_github_search`.\n\n### Github Client\n\n`GithubClient` надаватиме необроблені дані з\n[GitHub API](https://developer.github.com/v3/).\n\n:::note\n\nВи можете побачити зразок даних, які ми отримаємо назад,\n[тут](https://api.github.com/search/repositories?q=dartlang).\n\n:::\n\nДавайте створимо `github_client.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_client.dart\"\n\ttitle=\"common_github_search/lib/src/github_client.dart\"\n/>\n\n:::note\n\nНаш `GithubClient` просто виконує мережевий запит до API пошуку репозиторіїв\nGithub і конвертує результат або в `SearchResult`, або в `SearchResultError` як\n`Future`.\n\n:::\n\n:::note\n\nРеалізація `GithubClient` залежить від `SearchResult.fromJson`, який ми ще не\nреалізували.\n\n:::\n\nДалі нам потрібно визначити наші моделі `SearchResult` та `SearchResultError`.\n\n#### Модель результату пошуку\n\nСтворіть `search_result.dart`, який представляє список `SearchResultItems` на\nоснові запиту користувача:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result.dart\"\n\ttitle=\"lib/src/models/search_result.dart\"\n/>\n\n:::note\n\nРеалізація `SearchResult` залежить від `SearchResultItem.fromJson`, який ми ще\nне реалізували.\n\n:::\n\n:::note\n\nМи не включаємо властивості, які не будуть використовуватися в нашій моделі.\n\n:::\n\n#### Модель елемента результату пошуку\n\nДалі ми створимо `search_result_item.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_item.dart\"\n\ttitle=\"lib/src/models/search_result_item.dart\"\n/>\n\n:::note\n\nЗнову ж таки, реалізація `SearchResultItem` залежить від `GithubUser.fromJson`,\nякий ми ще не реалізували.\n\n:::\n\n#### Модель користувача GitHub\n\nДалі ми створимо `github_user.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/github_user.dart\"\n\ttitle=\"lib/src/models/github_user.dart\"\n/>\n\nНа цьому етапі ми завершили реалізацію `SearchResult` та його залежностей. Тепер\nперейдемо до `SearchResultError`.\n\n#### Модель помилки результату пошуку\n\nСтворіть `search_result_error.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/models/search_result_error.dart\"\n\ttitle=\"lib/src/models/search_result_error.dart\"\n/>\n\nНаш `GithubClient` завершений, тому далі ми перейдемо до `GithubCache`, який\nвідповідатиме за [мемоізацію](https://en.wikipedia.org/wiki/Memoization) як\nоптимізацію продуктивності.\n\n### GitHub Cache\n\nНаш `GithubCache` відповідатиме за запам'ятовування всіх минулих запитів, щоб ми\nмогли уникнути непотрібних мережевих запитів до GitHub API. Це також допоможе\nпокращити продуктивність нашого додатку.\n\nСтворіть `github_cache.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_cache.dart\"\n\ttitle=\"lib/src/github_cache.dart\"\n/>\n\nТепер ми готові створити наш `GithubRepository`!\n\n### GitHub Repository\n\nGithub Repository відповідає за створення абстракції між шаром даних\n(`GithubClient`) та шаром бізнес-логіки (`Bloc`). Тут також ми будемо\nвикористовувати наш `GithubCache`.\n\nСтворіть `github_repository.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_repository.dart\"\n\ttitle=\"lib/src/github_repository.dart\"\n/>\n\n:::note\n\n`GithubRepository` має залежність від `GithubCache` та `GithubClient` і\nабстрагує базову реалізацію. Наш додаток ніколи не повинен знати, як дані\nотримуються або звідки вони надходять, оскільки це його не стосується. Ми можемо\nзмінити роботу сховища в будь-який час, і поки ми не змінюємо інтерфейс, нам не\nпотрібно змінювати жоден клієнтський код.\n\n:::\n\nНа цьому етапі ми завершили шар провайдера даних та шар сховища, тому ми готові\nперейти до шару бізнес-логіки.\n\n### GitHub Search Event\n\nНаш Bloc буде сповіщений, коли користувач введе назву репозиторію, що ми\nпредставимо як `TextChanged` `GithubSearchEvent`.\n\nСтворіть `github_search_event.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_event.dart\"\n/>\n\n:::note\n\nМи розширюємо [`Equatable`](https://pub.dev/packages/equatable), щоб мати\nможливість порівнювати екземпляри `GithubSearchEvent`. За замовчуванням оператор\nрівності повертає true тоді і тільки тоді, коли this та other є одним і тим же\nекземпляром.\n\n:::\n\n### Github Search State\n\nНаш шар представлення повинен мати кілька частин інформації, щоб правильно себе\nвідобразити:\n\n- `SearchStateEmpty` -- повідомить шару представлення, що користувач не надав\n  жодного введення.\n\n- `SearchStateLoading` -- повідомить шару представлення, що він повинен\n  відобразити якийсь індикатор завантаження.\n\n- `SearchStateSuccess` -- повідомить шару представлення, що він має дані для\n  відображення.\n\n  - `items` -- буде `List<SearchResultItem>`, який буде відображено.\n\n- `SearchStateError` -- повідомить шару представлення, що виникла помилка при\n  отриманні репозиторіїв.\n\n  - `error` -- буде точна помилка, яка виникла.\n\nТепер ми можемо створити `github_search_state.dart` і реалізувати його наступним\nчином.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_state.dart\"\n/>\n\n:::note\n\nМи розширюємо [`Equatable`](https://pub.dev/packages/equatable), щоб мати\nможливість порівнювати екземпляри `GithubSearchState`. За замовчуванням оператор\nрівності повертає true тоді і тільки тоді, коли this та other є одним і тим же\nекземпляром.\n\n:::\n\nТепер, коли ми реалізували наші події та стани, ми можемо створити наш\n`GithubSearchBloc`.\n\n### GitHub Search Bloc\n\nСтворіть `github_search_bloc.dart`:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart\"\n\ttitle=\"lib/src/github_search_bloc/github_search_bloc.dart\"\n/>\n\n:::note\n\nНаш `GithubSearchBloc` конвертує `GithubSearchEvent` у `GithubSearchState` та\nмає залежність від `GithubRepository`.\n\n:::\n\n:::note\n\nМи створюємо користувацький `EventTransformer` для\n[debounce](https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html)\n`GithubSearchEvents`. Однією з причин, чому ми створили `Bloc` замість `Cubit`,\nбуло використання переваг трансформерів потоків.\n\n:::\n\nЧудово! Ми завершили наш пакет `common_github_search`. Готовий продукт повинен\nвиглядати як\n[цей](https://github.com/felangel/bloc/tree/master/examples/github_search/common_github_search).\n\nДалі ми працюватимемо над реалізацією на Flutter.\n\n## Flutter GitHub Search\n\nFlutter Github Search буде додатком Flutter, який повторно використовує моделі,\nпровайдери даних, сховища та блоки з `common_github_search` для реалізації\nпошуку Github.\n\n### Налаштування\n\nНам потрібно почати зі створення нового проєкту Flutter у нашій директорії\n`github_search` на тому ж рівні, що й `common_github_search`.\n\n<FlutterCreateSnippet />\n\nДалі нам потрібно оновити наш `pubspec.yaml`, щоб включити всі необхідні\nзалежності.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/pubspec.yaml\"\n\ttitle=\"flutter_github_search/pubspec.yaml\"\n/>\n\n:::note\n\nМи включаємо нашу щойно створену бібліотеку `common_github_search` як\nзалежність.\n\n:::\n\nТепер нам потрібно встановити залежності.\n\n<FlutterPubGetSnippet />\n\nЦе все для налаштування проєкту. Оскільки пакет `common_github_search` містить\nнаш шар даних, а також наш шар бізнес-логіки, все, що нам потрібно побудувати --\nце шар представлення.\n\n### Форма пошуку\n\nНам потрібно створити форму з віджетами `_SearchBar` та `_SearchBody`.\n\n- `_SearchBar` відповідатиме за отримання введення від користувача.\n- `_SearchBody` відповідатиме за відображення результатів пошуку, індикаторів\n  завантаження та помилок.\n\nДавайте створимо `search_form.dart`.\n\nНаш `SearchForm` буде `StatelessWidget`, який відображає віджети `_SearchBar` та\n`_SearchBody`.\n\n`_SearchBar` також буде `StatefulWidget`, тому що йому потрібно підтримувати\nсвій власний `TextEditingController`, щоб ми могли відстежувати, що користувач\nввів.\n\n`_SearchBody` -- це `StatelessWidget`, який відповідатиме за відображення\nрезультатів пошуку, помилок та індикаторів завантаження. Він буде споживачем\n`GithubSearchBloc`.\n\nЯкщо наш стан `SearchStateSuccess`, ми відображаємо `_SearchResults`, який ми\nреалізуємо далі.\n\n`_SearchResults` -- це `StatelessWidget`, який приймає `List<SearchResultItem>`\nі відображає їх як список `_SearchResultItems`.\n\n`_SearchResultItem` -- це `StatelessWidget`, який відповідає за відображення\nінформації про один результат пошуку. Він також відповідає за обробку взаємодії\nкористувача та навігацію за URL-адресою репозиторію при натисканні користувача.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/search_form.dart\"\n\ttitle=\"flutter_github_search/lib/search_form.dart\"\n/>\n\n:::note\n\n`_SearchBar` отримує доступ до `GitHubSearchBloc` через\n`context.read<GithubSearchBloc>()` та сповіщає bloc про події `TextChanged`.\n\n:::\n\n:::note\n\n`_SearchBody` використовує `BlocBuilder` для перебудови у відповідь на зміни\nстану. Оскільки параметр bloc об'єкта `BlocBuilder` було пропущено,\n`BlocBuilder` автоматично виконає пошук за допомогою `BlocProvider` та поточного\n`BuildContext`. Детальніше [тут.](/uk/flutter-bloc-concepts#blocbuilder) :::\n\n:::note\n\nМи використовуємо `ListView.builder` для побудови прокручуваного списку\n`_SearchResultItem`.\n\n:::\n\n:::note\n\nМи використовуємо пакет [url_launcher](https://pub.dev/packages/url_launcher)\nдля відкриття зовнішніх URL-адрес.\n\n:::\n\n### Збираємо все разом\n\nТепер залишилося лише реалізувати наш головний додаток у `main.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/flutter_github_search/lib/main.dart\"\n\ttitle=\"flutter_github_search/lib/main.dart\"\n/>\n\n:::note\n\nНаш `GithubRepository` створюється в `main` та впроваджується в наш `App`. Наша\n`SearchForm` обгорнута в `BlocProvider`, який відповідає за ініціалізацію,\nзакриття та надання доступу до екземпляру `GithubSearchBloc` для віджета\n`SearchForm` та його нащадків.\n\n:::\n\nОсь і все! Ми успішно реалізували додаток пошуку GitHub у Flutter,\nвикористовуючи пакети [bloc](https://pub.dev/packages/bloc) та\n[flutter_bloc](https://pub.dev/packages/flutter_bloc), і ми успішно відокремили\nнаш шар представлення від нашої бізнес-логіки.\n\nПовний вихідний код можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search).\n\nНарешті, ми створимо наш додаток AngularDart GitHub Search.\n\n## AngularDart GitHub Search\n\nAngularDart GitHub Search буде додатком AngularDart, який повторно використовує\nмоделі, провайдери даних, сховища та блоки з `common_github_search` для\nреалізації пошуку Github.\n\n### Налаштування\n\nНам потрібно почати зі створення нового проєкту AngularDart у нашій директорії\ngithub_search на тому ж рівні, що й `common_github_search`.\n\n<StagehandSnippet />\n\n:::note\n\nВи можете встановити `stagehand` за допомогою:\n\n<ActivateStagehandSnippet />\n:::\n\nПотім ми можемо замінити вміст `pubspec.yaml` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/pubspec.yaml\"\n\ttitle=\"angular_github_search/pubspec.yaml\"\n/>\n\n### Форма пошуку\n\nТак само, як і в нашому додатку Flutter, нам потрібно створити `SearchForm` з\nкомпонентами `SearchBar` та `SearchBody`.\n\nНаш компонент `SearchForm` реалізовуватиме `OnInit` та `OnDestroy`, тому що йому\nпотрібно створити та закрити `GithubSearchBloc`.\n\n- `SearchBar` відповідатиме за отримання введення від користувача.\n- `SearchBody` відповідатиме за відображення результатів пошуку, індикаторів\n  завантаження та помилок.\n\nДавайте створимо `search_form_component.dart.`\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.dart\"\n/>\n\n:::note\n\n`GithubRepository` впроваджується в `SearchFormComponent`.\n\n:::\n\n:::note\n\n`GithubSearchBloc` створюється та закривається `SearchFormComponent`.\n\n:::\n\nНаш шаблон (`search_form_component.html`) буде виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_form_component.html\"\n/>\n\nДалі ми реалізуємо компонент `SearchBar`.\n\n### Панель пошуку\n\n`SearchBar` -- це компонент, який відповідатиме за отримання введення від\nкористувача та сповіщення `GithubSearchBloc` про зміни тексту.\n\nСтворіть `search_bar_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart\"\n/>\n\n:::note\n\n`SearchBarComponent` має залежність від `GitHubSearchBloc`, тому що він\nвідповідає за сповіщення bloc про події `TextChanged`.\n\n:::\n\nДалі ми можемо створити `search_bar_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_bar/search_bar_component.html\"\n/>\n\nМи закінчили з `SearchBar`, тепер переходимо до `SearchBody`.\n\n### Тіло пошуку\n\n`SearchBody` -- це компонент, який відповідатиме за відображення результатів\nпошуку, помилок та індикаторів завантаження. Він буде споживачем\n`GithubSearchBloc`.\n\nСтворіть `search_body_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.dart\"\n/>\n\n:::note\n\n`SearchBodyComponent` має залежність від `GithubSearchState`, який надається\n`GithubSearchBloc` за допомогою пайпу `angular_bloc` bloc.\n\n:::\n\nСтворіть `search_body_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_body_component.html\"\n/>\n\nЯкщо наш стан `isSuccess`, ми відображаємо `SearchResults`. Ми реалізуємо його\nдалі.\n\n### Результати пошуку\n\n`SearchResults` -- це компонент, який приймає `List<SearchResultItem>` і\nвідображає їх як список `SearchResultItems`.\n\nСтворіть `search_results_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart\"\n/>\n\nДалі ми створимо `search_results_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html\"\n/>\n\n:::note\n\nМи використовуємо `ngFor` для побудови списку компонентів `SearchResultItem`.\n:::\n\nЧас реалізувати `SearchResultItem`.\n\n### Елемент результату пошуку\n\n`SearchResultItem` -- це компонент, який відповідає за відображення інформації\nпро один результат пошуку. Він також відповідає за обробку взаємодії користувача\nта навігацію за URL-адресою репозиторію при натисканні користувача.\n\nСтворіть `search_result_item_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart\"\n/>\n\nта відповідний шаблон у `search_result_item_component.html`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n\ttitle=\"angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html\"\n/>\n\n### Збираємо все разом\n\nУ нас є всі наші компоненти, і тепер настав час зібрати їх усі разом в нашому\n`app_component.dart`.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/github_search/angular_github_search/lib/app_component.dart\"\n\ttitle=\"angular_github_search/lib/app_component.dart\"\n/>\n\n:::note\n\nМи створюємо `GithubRepository` в `AppComponent` та впроваджуємо його в\nкомпонент `SearchForm`.\n\n:::\n\nОсь і все! Ми успішно реалізували додаток пошуку GitHub в AngularDart,\nвикористовуючи пакети `bloc` та `angular_bloc`, і ми успішно відокремили наш шар\nпредставлення від нашої бізнес-логіки.\n\nПовний вихідний код можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search).\n\n## Підсумок\n\nУ цьому посібнику ми створили додаток Flutter та AngularDart, спільно\nвикористовуючи всі моделі, провайдери даних та блоки між ними.\n\nЄдине, що нам дійсно довелося написати двічі -- це шар представлення (UI), що\nчудово з точки зору ефективності та швидкості розробки. Крім того, досить\nпоширено, коли веб-додатки та мобільні додатки мають різний користувацький\nдосвід та стилі, і цей підхід дійсно демонструє, наскільки легко створити два\nдодатки, які виглядають абсолютно по-різному, але спільно використовують ті самі\nшари даних та бізнес-логіки.\n\nПовний вихідний код можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/github_search).\n"
  },
  {
    "path": "docs/src/content/docs/uk/tutorials/ngdart-counter.mdx",
    "content": "---\ntitle: Лічильник AngularDart\ndescription:\n  Детальний посібник зі створення додатку-лічильника AngularDart з використанням\n  bloc.\nsidebar:\n  order: 8\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport ActivateStagehandSnippet from '~/components/tutorials/ngdart-counter/ActivateStagehandSnippet.astro';\nimport StagehandSnippet from '~/components/tutorials/ngdart-counter/StagehandSnippet.astro';\nimport InstallDependenciesSnippet from '~/components/tutorials/ngdart-counter/InstallDependenciesSnippet.astro';\n\n![beginner](https://img.shields.io/badge/level-beginner-green.svg)\n\nУ наступному посібнику ми створимо лічильник в AngularDart, використовуючи\nбібліотеку Bloc.\n\n![demo](~/assets/tutorials/ngdart-counter.gif)\n\n## Налаштування\n\nМи почнемо зі створення нового проєкту AngularDart за допомогою\n[stagehand](https://github.com/dart-lang/stagehand).\n\nЯкщо у вас не встановлено stagehand, активуйте його за допомогою:\n\n<ActivateStagehandSnippet />\n\nПотім згенеруйте новий проєкт за допомогою:\n\n<StagehandSnippet />\n\nПотім ми можемо замінити вміст `pubspec.yaml` на:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\nі потім встановити всі наші залежності\n\n<InstallDependenciesSnippet />\n\nНаш додаток лічильника матиме лише дві кнопки для збільшення/зменшення значення\nлічильника та елемент для відображення поточного значення. Давайте почнемо\nпроєктувати `CounterEvents`.\n\n## Counter Bloc\n\nОскільки стан нашого лічильника може бути представлений цілим числом, нам не\nпотрібно створювати користувацький клас, і ми можемо розмістити події та bloc\nразом.\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_bloc.dart\"\n\ttitle=\"lib/src/counter_page/counter_bloc.dart\"\n/>\n\n:::note\n\nПросто з оголошення класу ми можемо сказати, що наш `CounterBloc` прийматиме\n`CounterEvents` як вхідні дані та виводитиме цілі числа.\n\n:::\n\n## Counter App\n\nТепер, коли наш `CounterBloc` повністю реалізований, ми можемо почати створювати\nнаш компонент AngularDart App.\n\nНаш `app.component.dart` повинен виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.dart\"\n\ttitle=\"lib/app_component.dart\"\n/>\n\nі наш `app.component.html` повинен виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/app_component.html\"\n\ttitle=\"lib/app_component.html\"\n/>\n\n## Counter Page\n\nНарешті, залишилося лише побудувати наш компонент Counter Page.\n\nНаш `counter_page_component.dart` повинен виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.dart\"\n\ttitle=\"lib/src/counter_page/counter_page_component.dart\"\n/>\n\n:::note\n\nМи можемо отримати доступ до екземпляру `CounterBloc`, використовуючи систему\nвпровадження залежностей AngularDart. Оскільки ми зареєстрували його як\n`Provider`, AngularDart може правильно знайти `CounterBloc`.\n\n:::\n\n:::note\n\nМи закриваємо `CounterBloc` у `ngOnDestroy`.\n\n:::\n\n:::note\n\nМи імпортуємо `BlocPipe`, щоб ми могли використовувати його у нашому шаблоні.\n\n:::\n\nНарешті, наш `counter_page_component.html` повинен виглядати так:\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/angular_counter/lib/src/counter_page/counter_page_component.html\"\n\ttitle=\"lib/src/counter_page/counter_page_component.html\"\n/>\n\n:::note\n\nМи використовуємо `BlocPipe`, щоб ми могли відображати стан нашого `CounterBloc`\nпо мірі його оновлення.\n\n:::\n\nОсь і все! Ми відокремили наш шар представлення від нашого шару бізнес-логіки.\nНаш `CounterPageComponent` не має уявлення, що відбувається, коли користувач\nнатискає кнопку; він просто додає подію, щоб сповістити `CounterBloc`. Крім\nтого, наш `CounterBloc` не має уявлення, що відбувається зі станом (значенням\nлічильника); він просто конвертує `CounterEvents` у цілі числа.\n\nМи можемо запустити наш додаток за допомогою `webdev serve` та переглянути його\nлокально.\n\nПовний вихідний код цього прикладу можна знайти\n[тут](https://github.com/felangel/bloc/tree/master/examples/angular_counter).\n"
  },
  {
    "path": "docs/src/content/docs/uk/why-bloc.mdx",
    "content": "---\ntitle: Чому Bloc?\ndescription: Огляд того, що робить Bloc надійним рішенням для керування станом.\nsidebar:\n  order: 1\n---\n\nBloc спрощує відокремлення представлення від бізнес-логіки, роблячи ваш код\n_швидким_, _легким для тестування_ та _придатним для повторного використання_.\n\nПри створенні якісних додатків керування станом стає критично важливим.\n\nЯк розробники ми хочемо:\n\n- знати, в якому стані перебуває наш додаток у будь-який момент часу.\n- легко тестувати кожний випадок, щоб переконатися, що наш додаток реагує\n  належним чином.\n- записувати кожну взаємодію користувача в нашому додатку, щоб ми могли приймати\n  рішення на основі даних.\n- працювати максимально ефективно та повторно використовувати компоненти як\n  всередині нашого додатку, так і в інших додатках.\n- мати можливість багатьом розробникам безперешкодно працювати в одній кодовій\n  базі, дотримуючись однакових шаблонів та угод.\n- розробляти швидкі та чуйні додатки.\n\nBloc був розроблений для задоволення всіх цих потреб та багатьох інших.\n\nІснує безліч рішень для керування станом, і вибір підходящого може стати\nскладним завданням. Не існує одного ідеального рішення для керування станом!\nВажливо обрати те, яке найкраще підходить для вашої команди та вашого проєкту.\n\nBloc був розроблений з урахуванням трьох основних цінностей:\n\n- **Простий:** Легко зрозуміти та може використовуватися розробниками з різним\n  рівнем навичок.\n- **Потужний:** Допомагає створювати дивовижні, складні додатки, компонуючи їх з\n  менших компонентів.\n- **Тестовний:** Легко тестувати кожний аспект додатку, щоб ми могли ітерувати з\n  впевненістю.\n\nЗагалом, Bloc намагається зробити зміни стану передбачуваними, регулюючи, коли\nможе відбутися зміна стану, та забезпечуючи єдиний спосіб зміни стану в усьому\nдодатку.\n"
  },
  {
    "path": "docs/src/content/docs/why-bloc.mdx",
    "content": "---\ntitle: Why Bloc?\ndescription: An overview of what makes Bloc a solid state management solution.\nsidebar:\n  order: 1\n---\n\nBloc makes it easy to separate presentation from business logic, making your\ncode _fast_, _easy to test_, and _reusable_.\n\nWhen building production quality applications, managing state becomes critical.\n\nAs developers we want to:\n\n- know what state our application is in at any point in time.\n- easily test every case to make sure our app is responding appropriately.\n- record every single user interaction in our application so that we can make\n  data-driven decisions.\n- work as efficiently as possible and reuse components both within our\n  application and across other applications.\n- have many developers seamlessly working within a single code base following\n  the same patterns and conventions.\n- develop fast and reactive apps.\n\nBloc was designed to meet all of these needs and many more.\n\nThere are many state management solutions and deciding which one to use can be a\ndaunting task. There is no one perfect state management solution! What's\nimportant is that you pick the one that works best for your team and your\nproject.\n\nBloc was designed with three core values in mind:\n\n- **Simple:** Easy to understand & can be used by developers with varying skill\n  levels.\n- **Powerful:** Help make amazing, complex applications by composing them of\n  smaller components.\n- **Testable:** Easily test every aspect of an application so that we can\n  iterate with confidence.\n\nOverall, Bloc attempts to make state changes predictable by regulating when a\nstate change can occur and enforcing a single way to change state throughout an\nentire application.\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/bloc-concepts.mdx",
    "content": "---\ntitle: Bloc 核心概念\ndescription: package:bloc 核心概念的概述。\nsidebar:\n  order: 1\n---\n\nimport CountStreamSnippet from '~/components/concepts/bloc/CountStreamSnippet.astro';\nimport SumStreamSnippet from '~/components/concepts/bloc/SumStreamSnippet.astro';\nimport StreamsMainSnippet from '~/components/concepts/bloc/StreamsMainSnippet.astro';\nimport CounterCubitSnippet from '~/components/concepts/bloc/CounterCubitSnippet.astro';\nimport CounterCubitInitialStateSnippet from '~/components/concepts/bloc/CounterCubitInitialStateSnippet.astro';\nimport CounterCubitInstantiationSnippet from '~/components/concepts/bloc/CounterCubitInstantiationSnippet.astro';\nimport CounterCubitIncrementSnippet from '~/components/concepts/bloc/CounterCubitIncrementSnippet.astro';\nimport CounterCubitBasicUsageSnippet from '~/components/concepts/bloc/CounterCubitBasicUsageSnippet.astro';\nimport CounterCubitStreamUsageSnippet from '~/components/concepts/bloc/CounterCubitStreamUsageSnippet.astro';\nimport CounterCubitOnChangeSnippet from '~/components/concepts/bloc/CounterCubitOnChangeSnippet.astro';\nimport CounterCubitOnChangeUsageSnippet from '~/components/concepts/bloc/CounterCubitOnChangeUsageSnippet.astro';\nimport CounterCubitOnChangeOutputSnippet from '~/components/concepts/bloc/CounterCubitOnChangeOutputSnippet.astro';\nimport SimpleBlocObserverOnChangeSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeSnippet.astro';\nimport SimpleBlocObserverOnChangeUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeUsageSnippet.astro';\nimport SimpleBlocObserverOnChangeOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnChangeOutputSnippet.astro';\nimport CounterCubitOnErrorSnippet from '~/components/concepts/bloc/CounterCubitOnErrorSnippet.astro';\nimport SimpleBlocObserverOnErrorSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnErrorSnippet.astro';\nimport CounterCubitOnErrorOutputSnippet from '~/components/concepts/bloc/CounterCubitOnErrorOutputSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/bloc/CounterBlocSnippet.astro';\nimport CounterBlocEventHandlerSnippet from '~/components/concepts/bloc/CounterBlocEventHandlerSnippet.astro';\nimport CounterBlocIncrementSnippet from '~/components/concepts/bloc/CounterBlocIncrementSnippet.astro';\nimport CounterBlocUsageSnippet from '~/components/concepts/bloc/CounterBlocUsageSnippet.astro';\nimport CounterBlocStreamUsageSnippet from '~/components/concepts/bloc/CounterBlocStreamUsageSnippet.astro';\nimport CounterBlocOnChangeSnippet from '~/components/concepts/bloc/CounterBlocOnChangeSnippet.astro';\nimport CounterBlocOnChangeUsageSnippet from '~/components/concepts/bloc/CounterBlocOnChangeUsageSnippet.astro';\nimport CounterBlocOnChangeOutputSnippet from '~/components/concepts/bloc/CounterBlocOnChangeOutputSnippet.astro';\nimport CounterBlocOnTransitionSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionSnippet.astro';\nimport CounterBlocOnTransitionOutputSnippet from '~/components/concepts/bloc/CounterBlocOnTransitionOutputSnippet.astro';\nimport SimpleBlocObserverOnTransitionSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionSnippet.astro';\nimport SimpleBlocObserverOnTransitionUsageSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionUsageSnippet.astro';\nimport SimpleBlocObserverOnTransitionOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnTransitionOutputSnippet.astro';\nimport CounterBlocOnEventSnippet from '~/components/concepts/bloc/CounterBlocOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventSnippet.astro';\nimport SimpleBlocObserverOnEventOutputSnippet from '~/components/concepts/bloc/SimpleBlocObserverOnEventOutputSnippet.astro';\nimport CounterBlocOnErrorSnippet from '~/components/concepts/bloc/CounterBlocOnErrorSnippet.astro';\nimport CounterBlocOnErrorOutputSnippet from '~/components/concepts/bloc/CounterBlocOnErrorOutputSnippet.astro';\nimport CounterCubitFullSnippet from '~/components/concepts/bloc/CounterCubitFullSnippet.astro';\nimport CounterBlocFullSnippet from '~/components/concepts/bloc/CounterBlocFullSnippet.astro';\nimport AuthenticationStateSnippet from '~/components/concepts/bloc/AuthenticationStateSnippet.astro';\nimport AuthenticationTransitionSnippet from '~/components/concepts/bloc/AuthenticationTransitionSnippet.astro';\nimport AuthenticationChangeSnippet from '~/components/concepts/bloc/AuthenticationChangeSnippet.astro';\nimport DebounceEventTransformerSnippet from '~/components/concepts/bloc/DebounceEventTransformerSnippet.astro';\n\n:::note\n\n在使用 [`package:bloc`](https://pub.dev/packages/bloc)\n之前请确保已仔细阅读以下内容。\n\n:::\n\n有几个核心概念对于理解如何使用 bloc 包至关重要。\n\n在接下来的部分中，我们将依次详细介绍，并研究如何将它们应用于计数器应用程序。\n\n## Streams （流）\n\n:::note\n\n有关 `Streams` 的更多信息，请参阅官方\n[Dart 文档](https://dart.dev/tutorials/language/streams)。\n\n:::\n\n流是一系列异步数据。\n\n要使用 bloc 库，必须对 `Streams` 及其工作原理有基本的了解。\n\n如果您不熟悉 `Streams` ，那么可以想象一下有水流过的管道。管道是\n`Stream`，水是异步数据。\n\n我们可以通过编写 `async*`（异步生成器）函数在 Dart 中创建一个 `Stream`。\n\n<CountStreamSnippet />\n\n通过将函数标记为 `async*` ，我们可以使用 `yield` 关键字并返回 `Stream`\n数据。在上面的例子中，我们返回一个 `Stream` 整数，它的最大值是 `max` 参数。\n\n每次我们在 `async*` 函数中 `yield` 时，我们都会通过 `Stream` 推送该部分数据。\n\n我们可以用多种方式使用上述 `Stream` 。如果我们想编写一个函数来返回整数 `Stream`\n的总和，它看起来可能像这样：\n\n<SumStreamSnippet />\n\n通过将上述函数标记为 `async` ，我们可以使用 `await` 关键字并返回一个 `Future`\n整数。在此示例中，我们正在等待流中的每个值并返回流中所有整数的总和。\n\n我们可以合并上面的代码如下：\n\n<StreamsMainSnippet />\n\n现在我们对 Dart 中 `Streams`\n的原理有了一个基本的了解。我们可以学习关于 bloc 包的核心组件：`Cubit` 了。\n\n## Cubit\n\n`Cubit` 是扩展自 `BlocBase` 的类并可以扩展用于管理任何类型的状态。\n\n![Cubit 架构](~/assets/concepts/cubit_architecture_full.png)\n\n`Cubit` 可以公开可调用函数来触发状态的改变。\n\n状态是 `Cubit`\n的输出，代表应用程序状态的一部分。UI 组件可以收到状态通知，并根据当前状态进行部分重绘。\n\n:::note\n\n有关 `Cubit` 的起源的更多信息，请查看\n[Github Issue \\#69](https://github.com/felangel/cubit/issues/69)。\n\n:::\n\n### 创建一个 Cubit\n\n我们可以像这样创建一个 `CounterCubit` ：\n\n<CounterCubitSnippet />\n\n创建 `Cubit` 时，我们需要定义 `Cubit` 管理的状态类型。以上面的 `CounterCubit`\n为例，状态类型是 `int` ，但在更复杂的情况下，可能需要使用 `class` 而不是值类型。\n\n其次在创建 `Cubit` 的时候要指定初始状态。我们可以通过调用 `super`\n并赋初始值来实现。在上面的代码片段中，我们在内部将初始状态设置为 `0`\n，但我们也可以通过构造函数参数使 `Cubit` 更加灵活：\n\n<CounterCubitInitialStateSnippet />\n\n这样我们就可以创建具有不同初始值的 `CounterCubit` 实例，像这样：\n\n<CounterCubitInstantiationSnippet />\n\n### Cubit 状态变更\n\n每一个 `Cubit` 都可以通过 `emit` 输出一个新的状态。\n\n<CounterCubitIncrementSnippet />\n\n在上面的代码片段中， `CounterCubit` 公开了一个名为 `increment`\n的公共方法，可以被外部调用以通知 `CounterCubit` 增加它的状态值。当调用\n`increment` 时，我们可以通过 `状态` 的getter访问 `Cubit`\n的当前状态，并通过在当前状态上 `+1` 来 `emit` 一个新状态。\n\n:::caution\n\n`emit` 是保护方法，意味着它只能在 `Cubit` 内部被调用。\n\n:::\n\n### 使用 Cubit\n\n我们现在可以使用我们实现的 `CounterCubit` 了。\n\n#### 基本用法\n\n<CounterCubitBasicUsageSnippet />\n\n在上面的代码片段中，我们从创建一个 `CounterCubit`\n开始。然后我们打印了当前 cubit 的初始状态（由于尚未发出任何新的状态）。接下来，我们调用\n`increment` 函数来触发状态变化。最后，我们再次打印 `Cubit` 的状态，从 `0` 变为\n`1` ，并在 `Cubit` 上调用 `close` 来关闭内部状态流。\n\n#### 流的用法\n\n`Cubit` 公开了一个 `Stream` 可以用于接收实时的状态更新：\n\n<CounterCubitStreamUsageSnippet />\n\n在上面的代码片段中，我们订阅了 `CounterCubit`\n并且在每次状态变化时打印出来。我们调用了 `increment`\n函数来触发新的状态。最后，当我们不再需要接收时关闭了这个 `Cubit`， 并且在\n`subscription` 上调用了 `cancel` 。\n\n:::note\n\n`await Future.delayed(Duration.zero)` 在这个示例里是为了避免 `subscription`\n被立即取消。\n\n:::\n\n:::caution\n\n只有在 `Cubit` 上调用 `listen` 时才会收到后续状态变化。\n\n:::\n\n### 观察 Cubit\n\n当 `Cubit` 发出一个新的状态时，一个 `Change` 发生了。我们可以通过重写 `onChange`\n来观察 `Cubit` 的所有变化。\n\n<CounterCubitOnChangeSnippet />\n\n然后我们可以与 `Cubit` 交互并观察输出到控制台的所有更改。\n\n<CounterCubitOnChangeUsageSnippet />\n\n上面的示例将会输出：\n\n<CounterCubitOnChangeOutputSnippet />\n\n:::note\n\n`Change` 发生在 `Cubit` 状态更新之前。`Change` 包括 `currentState` 和\n`nextState`。\n\n:::\n\n#### BlocObserver\n\n使用 bloc 库的一个额外好处是我们可以在一个位置访问所有的\n`Changes`。尽管在这个应用里我们只有一个 `Cubit`，但是在大型应用程序中使用多个\n`Cubits` 来管理应用程序状态的不同部分是相当常见的。\n\n如果我们想对所有的 `Changes` 做出一些反应，仅需创建我们自己的 `BlocObserver`\n即可。\n\n<SimpleBlocObserverOnChangeSnippet />\n\n:::note\n\n我们要做的仅仅是扩展 `BlocObserver` 并且重写 `onChange` 方法。\n\n:::\n\n要使用 `SimpleBlocObserver`，我们只需要对 `main` 函数做少许的变更：\n\n<SimpleBlocObserverOnChangeUsageSnippet />\n\n上面的代码将会输出：\n\n<SimpleBlocObserverOnChangeOutputSnippet />\n\n:::note\n\n内部重写的 `onChange` 先会被调用，它再调用 `super.onChange` 通知 `BlocObserver`\n的 `onChange`。\n\n:::\n\n:::tip\n\n在 `BlocObserver` 中，除了 `Change` 本身之外，我们还可以访问 `Cubit` 实例。\n\n:::\n\n### Cubit 的错误处理\n\n每一个 `Cubit` 都有一个 `addError` 方法，可以用于指示发生了错误。\n\n<CounterCubitOnErrorSnippet />\n\n:::note\n\n可以在 `Cubit` 中覆盖 `onError` 来处理特定 `Cubit` 的所有错误。\n\n:::\n\n也可以在 `BlocObserver` 中重写 `onError`，以全局处理所有报告的错误。\n\n<SimpleBlocObserverOnErrorSnippet />\n\n如果我们重新运行这个程序，我们会看到下面的输出：\n\n<CounterCubitOnErrorOutputSnippet />\n\n## Bloc\n\n相对于函数来说，`Bloc` 是一个依赖 `事件` 触发 `状态` 变更的更高级的类。`Bloc`\n同样扩展了 `BlocBase`，这意味着它和 `Cubit` 一样拥有类似的公共 API，`Blocs`\n不是调用 `Bloc` 上的 `函数` 并直接发出新的 `状态`，而是接收 `事件` 并将传入的\n`事件` 转换为传出的 `状态`。\n\n![Bloc 架构](~/assets/concepts/bloc_architecture_full.png)\n\n### 创建一个 Bloc\n\n创建 `Bloc` 跟创建 `Cubit`\n类似，不过除了定义我们要管理的状态以外，我们还必须定义 `Bloc` 能够处理的事件。\n\n事件是 Bloc 的输入。通常情况下这些事件用于响应用户的交互，类似按下按钮或者页面加载的生命周期事件等等。\n\n<CounterBlocSnippet />\n\n和创建 `CounterCubit` 一样，我们必须通过基类的 `super` 来传入一个初始状态。\n\n### Bloc 状态变更\n\n跟 `Cubit` 里的函数相反，`Bloc` 必须通过 `on<Event>`\nAPI注册事件处理程序。事件处理程序负责将任何传入事件转换为零个或多个传出状态。\n\n<CounterBlocEventHandlerSnippet />\n\n:::tip\n\n`EventHandler` 可以访问添加的事件以及 `Emitter`，`Emitter`\n可以对输入的事件发出零个或者多个状态。\n\n:::\n\n然后我们可以更新 `EventHandler` 来处理 `CounterIncrementPressed` 事件：\n\n<CounterBlocIncrementSnippet />\n\n在上面的代码片中，我们注册了一个 `EventHandler` 来管理所有的\n`CounterIncrementPressed`。针对每个输入的 `CounterIncrementPressed`\n事件我们都可以通过 `状态` 的 getter 来访问当前的状态并且 `emit(state + 1)`。\n\n:::note\n\n因为 `Bloc` 扩展了 `BlocBase`，所以我们可以像在 `Cubit` 中一样通过 `状态`\n的 getter 随时访问 bloc 的当前状态。\n\n:::\n\n:::caution\n\nBlocs 任何时候都不应该 `emit` 新的状态。相反，每次状态变更都必须在\n`EventHandler` 里响应并输出。\n\n:::\n\n:::caution\n\nblocs 和 cubits 都会忽略重复的状态。如果 `state == nextState` 时我们发出\n`State nextState` ，不会有状态变更发生。\n\n:::\n\n### 使用 Bloc\n\n至此，我们可以创建一个我们的 `CounterBloc` 实例并使用它了！\n\n#### 基本用法\n\n<CounterBlocUsageSnippet />\n\n在上面的代码片段中，我们先创建了一个 `CounterBloc`。然后我们打印了 `Bloc`\n的当前状态（因为还没有新的状态发出）。接下来我们添加了一个\n`CounterIncrementPressed` 事件来出发状态变更。最后，我们再次打印了 `Bloc`\n的状态，从 `0` 变成了 `1`，并且在 `Bloc` 上调用 `close` 关闭了内部的状态流。\n\n:::note\n\n`await Future.delayed(Duration.zero)` 是用来确保我们等待下一个事件循环周期（以便\n`EventHandler` 处理这个事件）。\n\n:::\n\n#### Stream的用法\n\n和 `Cubit` 一样，`Bloc` 是一个特殊的 `Stream` 类型，这意味着我们也可以订阅\n`Bloc` 以实时更新其状态：\n\n<CounterBlocStreamUsageSnippet />\n\n在上面的代码片中，我们订阅了 `CounterBloc`\n并且在每次状态变更时进行打印。然后我们添加了 `CounterIncrementPressed` 事件触发\n`on<CounterIncrementPressed>` 这个 `EventHandler`\n并且发出新的状态。最后，当我们不想再接受更新时，我们在这个订阅上调用了 `cancel`\n并且 `close` 了这个 `Bloc`。\n\n:::note\n\n上面示例里的 await Future.delayed(Duration.zero)` 仅用于防止订阅被立即取消。\n\n:::\n\n### 观察 Bloc\n\n由于 `Bloc` 扩展了 `BlocBase`，我们可以用 `onChange` 观察 `Bloc`\n的所有状态变更。\n\n<CounterBlocOnChangeSnippet />\n\n然后我们可以更新 `main.dart` 如下：\n\n<CounterBlocOnChangeUsageSnippet />\n\n现在如果我们运行上面的代码片段，输出将会是：\n\n<CounterBlocOnChangeOutputSnippet />\n\n`Bloc` 和 `Cubit` 之间的一个关键区别因素是，由于 `Bloc`\n是事件驱动的，我们还能够捕获有关触发状态变化的信息。\n\n我们可以通过重写 `onTransition` 来实现。\n\n从一个状态变成另一个状态称为 `过渡`。一个 `过渡`\n包含了当前状态，触发事件以及下一个状态。\n\n<CounterBlocOnTransitionSnippet />\n\n如果我们重新运行之前相同的 `main.dart` 代码片段，我们应该看到以下输出：\n\n<CounterBlocOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` 在 `onChange` 之前被调用并且包含了触发 `currentState` 到\n`nextState`的事件。\n\n:::\n\n#### BlocObserver\n\n综前所述，我们可以在一个自定义的 `BlocObserver` 里重写 `onTransition`\n以实现在一个位置观察所有的过渡。\n\n<SimpleBlocObserverOnTransitionSnippet />\n\n我们可以像前面一样初始化 `SimpleBlocObserver`:\n\n<SimpleBlocObserverOnTransitionUsageSnippet />\n\n现在如果我们重新运行上面的代码片段，输出应该如下：\n\n<SimpleBlocObserverOnTransitionOutputSnippet />\n\n:::note\n\n`onTransition` 会被先调用（先本地再全局），然后 `onChange` 被调用。\n\n:::\n\n另一个 `Bloc` 实例的特有功能是：它允许我们重写 `onEvent`\n方法，无论什么时候有新的事件被添加到 `Bloc`，这个方法都会被调用。和 `onChange`\n和 `onTransition` 方法一样，`onEvent` 也可以在本地或者全局被重写。\n\n<CounterBlocOnEventSnippet />\n\n<SimpleBlocObserverOnEventSnippet />\n\n我们可以像前面一样运行同样的 `main.dart` 而且应该能看到如下输出：\n\n<SimpleBlocObserverOnEventOutputSnippet />\n\n:::note\n\n当事件被添加时，`onEvent` 会被立即调用。本地的 `onEvent` 会在 `BlocObserver`\n的全局 `onEvent` 之前被调用。\n\n:::\n\n### Bloc 的错误处理\n\n跟 `Cubit` 一样，每个 `Bloc` 都有 `addError` 和 `onError` 方法。我们可以在\n`Bloc` 里的任何地方调用 `addError` 来指示发生了错误。跟 `Cubit` 里 `onError`\n方法一样，我们可以重写它来响应所有发生的错误。\n\n<CounterBlocOnErrorSnippet />\n\n如果我们重新运行之前的 `main.dart`，我们可以看到当错误发生时，输出时下面这样：\n\n<CounterBlocOnErrorOutputSnippet />\n\n:::note\n\n本地的 `onError` 方法会先被调用，然后时 `BlocObserver` 里的全局 `onError` 方法。\n\n:::\n\n:::note\n\n`Bloc` 和 `Cubit` 实例里的 `onError` 和 `onChange` 工作方式完全相同。\n\n:::\n\n:::caution\n\n`EventHandler` 里未被处理的异常同样触发 `onError`。\n\n:::\n\n## Cubit 和 Bloc 对比\n\n现在我们了解了 `Cubit` 和 `Bloc` 类的基本信息，你可能会想：什么时候应该用\n`Cubit`，什么时候则应该用 `Bloc`呢？\n\n### Cubit 的优势\n\n#### 简单\n\n`Cubit` 最大优势之一是简单。当创建 `Cubit`\n时，我们只需要定义状态以及公开改变状态的函数。作为对比，当我们创建 `Bloc`\n时，我们要定义状态，事件以及 `EventHandler` 实现。这样来看，`Cubit`\n更易于理解并且需要写更少的代码。\n\n现在咱们来看看两个计数器的实现：\n\n##### CounterCubit\n\n<CounterCubitFullSnippet />\n\n##### CounterBloc\n\n<CounterBlocFullSnippet />\n\n`Cubit` 的实现更加简洁，跟单独定义事件相比，函数更像事件。此外，使用 `Cubit`\n时，我们可以简单的从任何地方调用 `emit` 来触发状态变更。\n\n### Bloc 的优势\n\n#### 可追溯性\n\n使用 `Bloc`\n的最大优势之一是了解状态变化的顺序以及触发这些变化的确切原因。处理对应用程序功能至关重要的状态，使用更事件驱动的方法来捕获除状态变化之外的所有事件可能会非常有用。\n\n一个常见的用例就是管理 `AuthenticationState`。为了简化，我们用 `enum` 来表示\n`AuthenticationState` ：\n\n<AuthenticationStateSnippet />\n\n应用程序的状态从 `authenticated` 到 `unauthenticated`\n的变更可能有很多种原因。比如：用户可能点击了登出来注销。再比如，用户的 access\ntoken 被收回了并且他们被强制注销了。使用 `Bloc`\n时，我们可以清楚的追溯应用的状态是如何变更为特定状态的。\n\n<AuthenticationTransitionSnippet />\n\n上面的 `过渡 ` 提供了让我们理解状态变更的所有信息。如果我们用 `Cubit` 来管理\n`AuthenticationState`，我们的日志则如下：\n\n<AuthenticationChangeSnippet />\n\n这告诉我们用户已登出，但没有解释为什么，这使得调试和理解应用程序状态随时间的变化变得异常困难。\n\n#### 高级事件转换\n\n`Bloc` 优于 `Cubit` 的另一个领域是当我们需要利用响应式操作符（例如\n`buffer`、`debounceTime`、`throttle` 等）时。\n\n:::tip\n\n查看 [`package:stream_transform`](https://pub.dev/packages/stream_transform) 和\n[`package:rxdart`](https://pub.dev/packages/rxdart) 来了解 `Stream` 转换的细节。\n\n:::\n\n`Bloc` 有一个 event 池允许我们控制和转换输入的事件。\n\n例如，如果我们要构建一个实时搜索，我们可能想要实现后端请求的去抖动来避免速率限制抑或是降低后端的成本/负载。\n\n使用 `Bloc` 的话我们可以提供一个自定义的 `EventTransformer` 来改变 `Bloc`\n对输入事件的处理。\n\n<DebounceEventTransformerSnippet />\n\n通过上面的代码，我们只要添加一点点代码就可以很容易的实现对输入事件的去抖动。\n\n:::tip\n\n查看 [`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency)\n了解一组预定义的事件转换器。\n\n:::\n\n如果你不确定应该用哪一种，先用 `Cubit`，后面根据需要你可以再重构或者升级为\n`Bloc`。\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/flutter-bloc-concepts.mdx",
    "content": "---\ntitle: Flutter Bloc 核心概念\ndescription: package:flutter_bloc 的核心概念概览\nsidebar:\n  order: 2\n---\n\nimport BlocBuilderSnippet from '~/components/concepts/flutter-bloc/BlocBuilderSnippet.astro';\nimport BlocBuilderExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocBuilderExplicitBlocSnippet.astro';\nimport BlocBuilderConditionSnippet from '~/components/concepts/flutter-bloc/BlocBuilderConditionSnippet.astro';\nimport BlocSelectorSnippet from '~/components/concepts/flutter-bloc/BlocSelectorSnippet.astro';\nimport BlocProviderSnippet from '~/components/concepts/flutter-bloc/BlocProviderSnippet.astro';\nimport BlocProviderEagerSnippet from '~/components/concepts/flutter-bloc/BlocProviderEagerSnippet.astro';\nimport BlocProviderValueSnippet from '~/components/concepts/flutter-bloc/BlocProviderValueSnippet.astro';\nimport BlocProviderLookupSnippet from '~/components/concepts/flutter-bloc/BlocProviderLookupSnippet.astro';\nimport NestedBlocProviderSnippet from '~/components/concepts/flutter-bloc/NestedBlocProviderSnippet.astro';\nimport MultiBlocProviderSnippet from '~/components/concepts/flutter-bloc/MultiBlocProviderSnippet.astro';\nimport BlocListenerSnippet from '~/components/concepts/flutter-bloc/BlocListenerSnippet.astro';\nimport BlocListenerExplicitBlocSnippet from '~/components/concepts/flutter-bloc/BlocListenerExplicitBlocSnippet.astro';\nimport BlocListenerConditionSnippet from '~/components/concepts/flutter-bloc/BlocListenerConditionSnippet.astro';\nimport NestedBlocListenerSnippet from '~/components/concepts/flutter-bloc/NestedBlocListenerSnippet.astro';\nimport MultiBlocListenerSnippet from '~/components/concepts/flutter-bloc/MultiBlocListenerSnippet.astro';\nimport BlocConsumerSnippet from '~/components/concepts/flutter-bloc/BlocConsumerSnippet.astro';\nimport BlocConsumerConditionSnippet from '~/components/concepts/flutter-bloc/BlocConsumerConditionSnippet.astro';\nimport RepositoryProviderSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderSnippet.astro';\nimport RepositoryProviderLookupSnippet from '~/components/concepts/flutter-bloc/RepositoryProviderLookupSnippet.astro';\nimport NestedRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/NestedRepositoryProviderSnippet.astro';\nimport MultiRepositoryProviderSnippet from '~/components/concepts/flutter-bloc/MultiRepositoryProviderSnippet.astro';\nimport CounterBlocSnippet from '~/components/concepts/flutter-bloc/CounterBlocSnippet.astro';\nimport CounterMainSnippet from '~/components/concepts/flutter-bloc/CounterMainSnippet.astro';\nimport CounterPageSnippet from '~/components/concepts/flutter-bloc/CounterPageSnippet.astro';\nimport WeatherRepositorySnippet from '~/components/concepts/flutter-bloc/WeatherRepositorySnippet.astro';\nimport WeatherMainSnippet from '~/components/concepts/flutter-bloc/WeatherMainSnippet.astro';\nimport WeatherAppSnippet from '~/components/concepts/flutter-bloc/WeatherAppSnippet.astro';\nimport WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSnippet.astro';\n\n:::note\n\n在开始 [`package:flutter_bloc`](https://pub.dev/packages/flutter_bloc)\n请详细阅读以下文档\n\n:::\n\n:::note\n\n所有 `flutter_bloc` 套件导出的Widget都与 Cubit 和 Bloc 实例集成。\n\n:::\n\n## Bloc 组件\n\n### BlocBuilder\n\n**BlocBuilder** 是一个 Flutter 的 Widget，它需要一个 `Bloc` 和一个 `builder`\n函数。`BlocBuilder` 处理于构建响应新状态时构建的 Widget 。`BlocBuilder` 与\n`StreamBuilder` 非常相似，但是\n`StreamBuilder`具有更简单的 API，以减少所需的样板代码量。`builder`\n函数可能会被多次调用，并且必须是一个根据状态返回一个widget的[纯函数](https://en.wikipedia.org/wiki/Pure_function)。\n\n如果你希望在状态变化时执行一些操作，比如导航、显示对话框等...，请查看\n`BlocListener`\n\n在BlocBuilder中，如果省略了 `bloc` 参数，则 `BlocBuilder` 会自动通过\n`BlocProvider` 和当前的 `BuildContext` 进行查找。\n\n<BlocBuilderSnippet />\n\n只有在希望提供一个仅作用于单个 widget 且无法透过父级 `BlocProvider` 和当前的\n`BuildContext` 访问的 `bloc` 时，才需要明确指定 `bloc`。\n\n<BlocBuilderExplicitBlocSnippet />\n\n在使用 BlocBuilder 时，如果要达到更细緻地控制 widget 的更新，可以使用\n`buildWhen` 参数来实现何时调用 `builder` 函数。`buildWhen`\n接受前一个 bloc 状态和当前 bloc 状态，并返回一个布尔值。如果 `buildWhen`\n返回 true，则 `builder` 函数将被调用并使用当前 `state`\n进行widget重建。如果 buildWhen 返回 false，则 `builder` 将不会被使用当前 `state`\n调用，且不会进行重建。\n\n<BlocBuilderConditionSnippet />\n\n### BlocSelector\n\n**BlocSelector** 是一个 Flutter\nWidget，类似于 BlocBuilder，但允许开发者通过根据当前 bloc 状态选择新值来过滤更新。如果选择的值不变，则防止不必要的构建。选择的值必须是 immutable 的，以便 BlocSelector 可以准确地确定是否应该再次调用 builder。\n\n如果`bloc` 被省略没有传入， `BlocSelector` 将自动使用 `BlocProvider` 和当前的\n`BuildContext` 执行查找。\n\n<BlocSelectorSnippet />\n\n### BlocProvider\n\n**BlocProvider** 是一个 Flutter widget ，透过 `BlocProvider.of<T>(context)`\n将一个 bloc 提供给它的children。`BlocProvider`\n用作于依赖注入（DI）widget ，以便在子树 (subtree) 中可以提供单个 bloc 实例给多个widget 使用。\n\n通常情况下，应该使用 `BlocProvider`\n来创建新的 bloc，这样可以使该 bloc 对于子树中的其他 widget 使用。在这种情况下，由于\n`BlocProvider` 负责创建 bloc，它将自动处理关闭该 bloc。\n\n<BlocProviderSnippet />\n\n默认情况下，`BlocProvider` 将延迟创建 bloc 实例，这意味着当通过\n`BlocProvider.of<BlocA>(context)` 查找该 bloc 时，`create` 方法才会被执行。\n\n为了复盖这种行为并强制立即运行 `create` 方法，可以将 `lazy` 设置为 `false`。\n\n<BlocProviderEagerSnippet />\n\n在某些情况下，可以使用 `BlocProvider`\n来将一个已存在的 Bloc 提供给 Widget 树中的新部分。这通常发生在需要将一个已存在的 Bloc 提供给新的路由（route）时。在这种情况下，`BlocProvider`\n不会自动关闭 Bloc，因为它并非 Bloc 的创建者。\n\n<BlocProviderValueSnippet />\n\n如此，从 `ChildA` 或者 `ScreenA` 中，我们可以获取 `BlocA`\n\n<BlocProviderLookupSnippet />\n\n### MultiBlocProvider\n\n**MultiBlocProvider** 是一个 Flutter Widget 用来将多个 `BlocProvider`\nWidgets 合併为一个。\n\n`MultiBlocProvider` 可以提高代码的可读性，消除了需要嵌套多个 `BlocProvider`\n的情况。\n\n通过使用了 `MultiBlocProvider` 我们可以从\n\n<NestedBlocProviderSnippet />\n\n转为\n\n<MultiBlocProviderSnippet />\n\n### BlocListener\n\n**BlocListener** 是一个 Flutter Widget，它接受一个 `BlocWidgetListener`\n和一个可选的 `Bloc`，并在 bloc 状态发生变化时调用\n`listener`。它应该用于需要每个状态变化仅执行一次的功能，例如导航、显示\n`SnackBar`、显示 `Dialog` 等等。\n\n与 `BlocBuilder` 中的 `builder` 不同的是，`listener`\n对于每个状态变化（初始状态除外）只会被调用一次，并且是一个 `void` 函数。\n\n如果`bloc` 被省略没有传入， `BlocListener` 将自动使用 `BlocProvider` 和当前的\n`BuildContext` 执行查找。\n\n<BlocListenerSnippet />\n\n只有在希望提供一个通过 `BlocProvider` 和当前 `BuildContext`\n否则无法访问的 bloc 的情况时，才指定 bloc。\n\n<BlocListenerExplicitBlocSnippet />\n\n要对 `listener` 函数何时调用进行更细緻地控制，可以提供一个可选的\n`listenWhen`。`listenWhen`\n接受先前的 bloc 状态和当前的 bloc 状态，并返回一个布尔值。如果 `listenWhen`\n返回 true，则会用 `state` 调用 `listener`。如果 `listenWhen`\n返回 false，则不会用 `state` 调用 `listener`。\n\n<BlocListenerConditionSnippet />\n\n### MultiBlocListener\n\n**MultiBlocListener** 是 Flutter 中的一个 Widget，用来将多个 `BlocListener`\nWidgets 合併为一个。`MultiBlocListener`\n可以提高代码的可读性，消除了需要嵌套多个 BlocListener 的情况。\n\n通过使用 `MultiBlocListener`，我们可以从以下代码转变为：\n\n<NestedBlocListenerSnippet />\n\n转为\n\n<MultiBlocListenerSnippet />\n\n### BlocConsumer\n\n**BlocConsumer** 提供了 `builder` 和\n`listener`，以便对新的状态做出反应。`BlocConsumer` 类似于嵌套的 `BlocListener`\n和 `BlocBuilder`，但可以减少所需的样板代码量。只有在需要重建 UI 并执行其他对\n`bloc` 状态变化做出反应时，才应该使用 `BlocConsumer`。`BlocConsumer`\n接受`BlocWidgetBuilder` (必传参数) 和 `BlocWidgetListener` (必传参数) ，以及\n`bloc` (可选参数)、`BlocBuilderCondition` (可选参数) 和 `BlocListenerCondition`\n(可选参数)。\n\n如果`bloc` 被省略没有传入， `BlocConsumer` 将自动使用 `BlocProvider` 和当前的\n`BuildContext` 进行查找。\n\n<BlocConsumerSnippet />\n\n可选参数 `listenWhen` 和 `buildWhen` 可以用来更精细地控制 `listener` 和\n`builder` 何时被调用。`listenWhen` 和 `buildWhen` 将在每个 `bloc` 的 `state`\n更改时被调用。它们都接受先前的 `state` 和当前的\n`state`，且必须返回一个布尔值，用于确定是否调用 `builder` 和/或 `listener`\n函数。当 `BlocConsumer` 初始化时，先前的 `state` 将初始化为 `bloc` 的\n`state`。由于 `listenWhen` 和 `buildWhen` 是可选的，因此不实现它们时，默认为\n`true`。\n\n<BlocConsumerConditionSnippet />\n\n### RepositoryProvider\n\n**MultiBlocListener** 是 Flutter 中的一个 Widget，透过\n`RepositoryProvider.of<T>(context)`\n将 repository 提供给其子节点。它被用作依赖注入（DI）小部件，以便在子树中提供一个仓库的单个实例给多个 Widgets。`BlocProvider`\n应该用于提供 bloc，而 `RepositoryProvider` 应该只用于提供 repositories。\n\n<RepositoryProviderSnippet />\n\n那么从 `ChildA`，我们可以通过以下代码获取 `Repository` 实例：\n\n<RepositoryProviderLookupSnippet />\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** 是一个 Flutter Widget 用来将多个\n`RepositoryProvider` Widgets 合併为一个。\n\n`MultiRepositoryProvider` 可以提高代码的可读性，消除了需要嵌套多个\n`RepositoryProvider` 的情况。\n\n通过使用了 `MultiRepositoryProvider` 我们可以从\n\n<NestedRepositoryProviderSnippet />\n\n转为\n\n<MultiRepositoryProviderSnippet />\n\n## BlocProvider 用法\n\n接着来看看如何使用 `BlocProvider` 来提供一个 `CounterBloc` 给一个 `CounterPage`\n并且使用`BlocBuilder` 来对状态变化做出反应.\n\n<CounterBlocSnippet />\n\n<CounterMainSnippet />\n\n<CounterPageSnippet />\n\n到目前为止，我们成功地将我们的表示层 ( presentational layer\n) 与我们的业务逻辑层 (business logic layer) 分开了。注意，`CounterPage`\nwidget 不知道当用户点击按钮时会发生什么。该 widget 只是告诉 `CounterBloc`\n用户按下了增加或减少按钮。\n\n## RepositoryProvider 用法\n\n接下来，我们将利用 flutter_weather 范例，来查看如何使用 RepositoryProvider。\n\n<WeatherRepositorySnippet />\n\n由于应用程式明确依赖于\n`WeatherRepository`，我们通过建构函式注入一个实例。这使我们能够根据构建版本或环境注入不同的\n`WeatherRepository` 实例。\n\n<WeatherMainSnippet />\n\n由于在这个例子中，我们的应用程序只有一个 repository，我们将通过\n`RepositoryProvider.value` 将其注入到我们的 widget\ntree 中。如果您有多个 repository，则可以使用 `MultiRepositoryProvider`\n将多个 repository 实例提供给 subtree。\n\n<WeatherAppSnippet />\n\n在大多数情况下，应用程序的顶层 widgets 将透过 `RepositoryProvider`\n向 subtree 公开一个或多个repository。\n\n<WeatherPageSnippet />\n\n在实例化一个 bloc 时，我们可以通过 `context.read`\n访问存储库的实例，并通过构造函数将存储库注入到 bloc 中。\n\n[flutter_weather_link]:\n\thttps://github.com/felangel/bloc/blob/master/examples/flutter_weather\n\n## 扩展方法\n\n[ Extension methods](https://dart.dev/guides/language/extension-methods)，在 Dart\n2.7 中引入，是一种向现有库添加功能的方法。在本节中，我们将看一下\n`package:flutter_bloc` 中包含的扩展方法以及它们的使用方式。\n\n`flutter_bloc` 依赖于\n[package:provider](https://pub.dev/packages/provider)，它简化了对\n[`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)\n的使用。\n\n在内部，`package:flutter_bloc` 使用 `package:provider` 实现了\n`BlocProvider`、`MultiBlocProvider`、`RepositoryProvider` 和\n`MultiRepositoryProvider` widgets。 `package:flutter_bloc` 从 `package:provider`\n中导出了 `ReadContext`、`WatchContext` 和 `SelectContext` 扩展。\n\n:::note\n\n查询更多关于 [`package:provider`](https://pub.dev/packages/provider).\n\n:::\n\n### context.read\n\n`context.read<T>()` 查找最接近的类型为 T 的祖先实例，并在功能上等同于\n`BlocProvider.of<T>(context)`。`context.read` 最常用于在 `onPressed`\n回调中查找 bloc 实例以添加事件。\n\n:::note\n\n`context.read<T>()` 不会监听 `T` —— 如果提供的类型为 `T`\n的对象发生变化，`context.read` 不会触发 widget重建。\n\n:::\n\n#### Usage\n\n✅ **建议** 在回调函数中使用`context.read` 方法来添加事件。\n\n```dart\nonPressed() {\n  context.read<CounterBloc>().add(CounterIncrementPressed()),\n}\n```\n\n❌ **避免** 在 `build` 方法当中使用`context.read` 查找状态\n\n```dart\n@override\nWidget build(BuildContext context) {\n  final state = context.read<MyBloc>().state;\n  return Text('$state');\n}\n```\n\n以上的使用可能会导致UI 不会反应最新的状态变化的错误，因为即使状态改变了 `Text`\nwidget 并不会被重新建立（rebuild），。\n\n:::caution\n\n使用 `BlocBuilder` 或者 `context.watch`\n以确保当状态改变时，UI 可以被正确地重新构建。\n\n:::\n\n### context.watch\n\n如同 `context.watch<T>()` , `context.watch<T>()` 提供最接近祖先类型为 `T`\n的实例，并且同时还会监听该实例的变化。它的功能等同于\n`BlocProvider.of<T>(context, listen: true)`。\n\n如果提供类型 T 的对象发生变化，context.watch 将会触发重新构建（rebuild）。\n\n:::caution\n\n`context.watch` 只能在 `StatelessWidget` 或 `State` 类的 build 方法内部被访问。\n\n:::\n\n#### Usage\n\n✅ **建议** 使用 `BlocBuilder` 而不是 `context.watch` 来明确地范围化重新构建。\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocBuilder<MyBloc, MyState>(\n        builder: (context, state) {\n          // 当状态变化时，只会重新构建 Text。\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n作为选择，建议使用 `Builder`来范围化（或限定）重新构建的范围。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // 当状态变化时，只会重新构建 Text。\n          final state = context.watch<MyBloc>().state;\n          return Text(state.value);\n        },\n      ),\n    ),\n  );\n}\n```\n\n✅ **建议** 将 `Builder` 和 `context.watch` 一起使用，类似于 `MultiBlocBuilder`\n的效果.\n\n```dart\nBuilder(\n  builder: (context) {\n    final stateA = context.watch<BlocA>().state;\n    final stateB = context.watch<BlocB>().state;\n    final stateC = context.watch<BlocC>().state;\n\n    // 返回一个依赖于 BlocA 、BlocB, 和 BlocC 状态的 Widget。\n  }\n);\n```\n\n❌ **避免** 使用 `context.watch` 当父 Widget 的 build 方法不依赖于状态\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // 无论状态如何变化，都会重新构建 MaterialApp，\n  // 即使它只用于 Text Widget 中。\n  final state = context.watch<MyBloc>().state;\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(state.value),\n    ),\n  );\n}\n```\n\n:::caution\n\n如果在 `build` 方法的根部使用 `context.watch`\n，当 bloc 的状态发生变化时，整个 Widget 都将被重新构建。\n\n:::\n\n### context.select\n\n如同 `context.watch<T>()` , `context.select<T, R>(R function(T value))`\n提供最接近祖先类型为 `T` 的实例 且同时监听该实例的变化。不同于 `context.watch`\n的是，`context.select` 允许你监听状态对象中的特定部分\n\n```dart\nWidget build(BuildContext context) {\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return Text(name);\n}\n```\n\n以上实例将在属性 `ProfileBloc` 内中的 `name` 属性变化时重新构建 Widget。\n\n#### Usage\n\n✅ **建议**\n使用 BlocSelector 而不是 context.select，以明确地限定重新构建的范围。\n\n```dart\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: BlocSelector<ProfileBloc, ProfileState, String>(\n        selector: (state) => state.name,\n        builder: (context, name) {\n          // 当 state.name 变化时，只有 Text 会重新构建。\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n作为选择，建议使用 `Builder`来范围化（或限定）重新构建的范围。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return MaterialApp(\n    home: Scaffold(\n      body: Builder(\n        builder: (context) {\n          // 当 state.name 变化时，只有 Text 会重新构建。\n          final name = context.select((ProfileBloc bloc) => bloc.state.name);\n          return Text(name);\n        },\n      ),\n    ),\n  );\n}\n```\n\n❌ **避免**\n在父 Widget 的 build 方法中使用 context.select，但父 Widget 不依赖于状态。\n\n```dart\n@override\nWidget build(BuildContext context) {\n  // 当 state.value 变化时，即使它只在 Text widget 中使用，也会重新构建 MaterialApp。\n  final name = context.select((ProfileBloc bloc) => bloc.state.name);\n  return MaterialApp(\n    home: Scaffold(\n      body: Text(name),\n    ),\n  );\n}\n```\n\n:::caution\n\n在 `build` 方法的根部使用 `context.select`\n将导致在选择范围发生变化时重新构建整个 Widget。\n\n:::\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/getting-started.mdx",
    "content": "---\ntitle: 快速入门\ndescription: 用 Bloc 开始构建的前提\n---\n\nimport InstallationTabs from '~/components/getting-started/InstallationTabs.astro';\nimport ImportTabs from '~/components/getting-started/ImportTabs.astro';\n\n## Bloc 包\n\nBloc 生态包括了以下几个包：\n\n| Package                                                                                    | Description        | Link                                                                                                           |\n| ------------------------------------------------------------------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------- |\n| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc)         | AngularDart 组件   | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc)         |\n| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc)                         | 核心 Dart API      | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc)                         |\n| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | 事件转换器         | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) |\n| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint)               | Custom Linter      | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint)               |\n| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test)               | 测试 API           | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test)               |\n| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools)             | Command-line Tools | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools)             |\n| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc)         | Flutter 组件       | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc)         |\n| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc)       | 缓存/持久化 支持库 | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc)       |\n| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc)           | 撤销/重做 支持库   | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc)           |\n\n## 安装\n\n<InstallationTabs />\n\n:::note\n\n在开始使用 bloc 之前请确保你在机器上已经安装了\n[Dart SDK](https://dart.dev/get-dart)。\n\n:::\n\n## 导入\n\n现在我们已经成功安装了 bloc，我们可以创建 `main.dart` 并且导入对应的 `bloc`\n包了。\n\n<ImportTabs />\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/index.mdx",
    "content": "---\ntemplate: splash\ntitle: Bloc State Management Library\ndescription:\n  Official documentation for the bloc state management library. Support for\n  Dart, Flutter, and AngularDart. Includes examples and tutorials.\nbanner:\n  content: |\n    ✨ 访问\n    <a href=\"https://shop.bloclibrary.dev\">Bloc 商店</a> ✨\neditUrl: false\nlastUpdated: false\nhero:\n  title: Bloc <sup><span style=\"font-size:0.4em\">v9.2.0</span></sup>\n  tagline: 基于 Dart 的可预测的状态管理库。\n  image:\n    alt: Bloc logo\n    file: ~/assets/bloc.svg\n  actions:\n    - text: 快速入门\n      link: /zh-cn/getting-started/\n      variant: primary\n      icon: rocket\n    - text: GitHub\n      link: https://github.com/felangel/bloc\n      icon: github\n      variant: secondary\n---\n\nimport { CardGrid } from '@astrojs/starlight/components';\nimport SponsorsGrid from '~/components/landing/SponsorsGrid.astro';\nimport Card from '~/components/landing/Card.astro';\nimport ListCard from '~/components/landing/ListCard.astro';\nimport SplitCard from '~/components/landing/SplitCard.astro';\nimport Discord from '~/components/landing/Discord.astro';\n\n<SponsorsGrid sponsoredBy=\"💖的赞助商们\" becomeASponsor=\"成为我们的赞助商\" />\n\n<hr />\n\n<CardGrid>\n\n<SplitCard title=\"快速入门\" icon=\"rocket\">\n\t```sh\n\t# 将bloc添加到你的项目。\n\tdart pub add bloc\n\t```\n\n我们的 [快速入门](/zh-cn/getting-started)\n提供了如何在几分钟内开始使用 Bloc 的详细指引。\n\n</SplitCard>\n\n<Card title=\"查看向导\" icon=\"star\">\n\t完成 [官方向导](/zh-cn/tutorials/flutter-counter) 来了解最佳实践以及构建基于\n\tBloc 的各种不同应用。\n</Card>\n\n<Card title=\"使用 Bloc 构建\" icon=\"laptop\">\n\t浏览优质并充分测试过的\n\t[示例应用](https://github.com/felangel/bloc/tree/master/examples) 例如\n\t计数器，定时器，无限列表，天气，待办事项以及更多其他示例！\n</Card>\n\n<ListCard title=\"学习\" icon=\"open-book\">\n\n    - [为什么用 Bloc？](/zh-cn/why-bloc)\n    - [核心概念](/zh-cn/bloc-concepts)\n    - [架构](/zh-cn/architecture)\n    - [测试](/zh-cn/testing)\n    - [命名约定](/zh-cn/naming-conventions)\n    - [FAQs](/zh-cn/faqs)\n\n</ListCard>\n\n  <ListCard title=\"集成\" icon=\"puzzle\">\n    - [VSCode 集成](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n    - [IntelliJ 集成](https://plugins.jetbrains.com/plugin/12129-bloc)\n    - [Neovim 集成](https://github.com/wa11breaker/flutter-bloc.nvim)\n    - [Mason CLI 集成](https://github.com/felangel/bloc/blob/master/bricks/README.md)\n    - [自定义模板](https://brickhub.dev/search?q=bloc)\n    - [开发者工具](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md)\n  </ListCard>\n</CardGrid>\n\n<Discord joinDiscord=\"加入 Discord\" />\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/tutorials/flutter-counter.mdx",
    "content": "---\ntitle: Flutter 计数器\ndescription: 关于如何使用 bloc 构建 Flutter 计数器应用程序的深入指南。\nsidebar:\n  order: 1\n---\n\nimport RemoteCode from '~/components/code/RemoteCode.astro';\nimport FlutterCreateSnippet from '~/components/tutorials/flutter-counter/FlutterCreateSnippet.astro';\nimport FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.astro';\n\n![初级](https://img.shields.io/badge/level-beginner-green.svg)\n\n在下面的教程中，我们将使用 Bloc 库在 Flutter 中构建一个计数器。\n\n![demo](~/assets/tutorials/flutter-counter.gif)\n\n## 关键主题\n\n- 使用 [BlocObserver](/zh-cn/bloc-concepts#blocobserver) 观察状态变更。\n- [BlocProvider](/zh-cn/flutter-bloc-concepts#blocprovider)，为子组件提供 bloc 的 Flutter 部件。\n- [BlocBuilder](/zh-cn/flutter-bloc-concepts#blocbuilder)，用于响应新状态来构建小部件的 Flutter 部件。\n- 用 Cubit 替代 Bloc。[有什么不同？](/zh-cn/bloc-concepts#cubit-和-bloc-对比)。\n- 使用 [context.read](/zh-cn/flutter-bloc-concepts#contextread) 添加事件。\n\n## 设置\n\n我们从创建一个全新的 Flutter 项目开始\n\n<FlutterCreateSnippet />\n\n用下面的代码替换 `pubspec.yaml` 的内容：\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/pubspec.yaml\"\n\ttitle=\"pubspec.yaml\"\n/>\n\n然后安装所有的依赖\n\n<FlutterPubGetSnippet />\n\n## 项目结构\n\n```\n├── lib\n│   ├── app.dart\n│   ├── counter\n│   │   ├── counter.dart\n│   │   ├── cubit\n│   │   │   └── counter_cubit.dart\n│   │   └── view\n│   │       ├── counter_page.dart\n│   │       ├── counter_view.dart\n│   │       └── view.dart\n│   ├── counter_observer.dart\n│   └── main.dart\n├── pubspec.lock\n├── pubspec.yaml\n```\n\n这个应用采用了功能驱动的目录结构。这种项目结构以便于我们按照独立的功能对项目进行扩展。这个示例项目里只有一个功能（计数器），但是在更加复杂的应用里我们可以包含数以百计的功能。\n\n## BlocObserver\n\n我们要做的第一件事是看看如何创建一个 `BlocObserver` 来观察应用里所有的状态变更。\n\n我们先创建一个 `lib/counter_observer.dart`：\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter_observer.dart\"\n\ttitle=\"lib/counter_observer.dart\"\n/>\n\n目前，我们仅重写了 `onChange` 来查看所有发生的状态变更。\n\n:::note\n\n`Bloc` 和 `Cubit` 的 `onChange` 工作方式是相同的。\n\n:::\n\n## main.dart\n\n下一步，我们替换 `lib/main.dart` 的代码如下：\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/main.dart\"\n\ttitle=\"lib/main.dart\"\n/>\n\n我们初始化了我们创建的 `CounterObserver` 并且在 `runApp` 里添加了 `CounterApp`\n组件。\n\n## Counter App\n\n创建一个 `lib/app.dart`:\n\n`CounterApp` 是一个 `MaterialApp` 并且指定了 `CounterPage` 作为主页。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/app.dart\"\n\ttitle=\"lib/app.dart\"\n/>\n\n:::note\n\n我们扩展了 `MaterialApp` 因为 `CounterApp` _是_ 一个\n`MaterialApp`。在大多数情况下，我们会创建 `StatelessWidget` 或者\n`StatefulWidget` 实例并且在 `build`\n里进行组合。但是在这里没有部件需要组合，所以最简单的方式就是直接扩展\n`MaterialApp`。\n\n:::\n\n接下来咱们看看 `CounterPage` ！\n\n## Counter Page\n\n创建 `lib/counter/view/counter_page.dart` 如下：\n\n`CounterPage` 部件负责创建 `CounterCubit` （下面会讲到）并且提供给\n`CounterView`。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_page.dart\"\n\ttitle=\"lib/counter/view/counter_page.dart\"\n/>\n\n:::note\n\n将 `Cubit` 的创建和消费分开并解耦非常重要，这样可以让代码更加的易于测试和重用。\n\n:::\n\n## Counter Cubit\n\n创建 `lib/counter/cubit/counter_cubit.dart` 如下：\n\n`CounterCubit` 会公开以下方法：\n\n- `increment`: 当前状态 +1\n- `decrement`: 当前状态 -1\n\n`CounterCubit` 管理的是 `int` 型的状态，初始值为 `0`。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/cubit/counter_cubit.dart\"\n\ttitle=\"lib/counter/cubit/counter_cubit.dart\"\n/>\n\n:::tip\n\n使用\n[VSCode 插件](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)\n或者 [IntelliJ 插件](https://plugins.jetbrains.com/plugin/12129-bloc)\n可以自动创建新的 cubits。:::\n\n接下来，我们看看负责消费并且与 `CounterCubit` 进行交互的 `CounterView`。\n\n## Counter View\n\n创建 `lib/counter/view/counter_view.dart` 如下：\n\n`CounterView`\n负责渲染当前的数值，以及两个 FloatingActionButtons 来负责增/减计数。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/counter_view.dart\"\n\ttitle=\"lib/counter/view/counter_view.dart\"\n/>\n\n`BlocBuilder` 用来包装 `Text` 部件，这样 `CounterCubit`\n任何的状态变化都会更新文本。此外， `context.read<CounterCubit>()` 用于查找最近的\n`CounterCubit` 实例。\n\n:::note\n\n只有 `Text` 部件被包装在 `BlocBuilder` 里，因为它是 `CounterCubit`\n状态变更时需要更新的唯一部件。避免包装不需要根据状态变更重新构建的部件。\n\n:::\n\n## Barrel\n\n创建 `lib/counter/view/view.dart`:\n\n添加 `view.dart` 来导出所有 counter 视图的公共部分。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/view/view.dart\"\n\ttitle=\"lib/counter/view/view.dart\"\n/>\n\n创建 `lib/counter/counter.dart`:\n\n添加 `counter.dart` 来导出 counter 功能的所有公共部分。\n\n<RemoteCode\n\turl=\"https://raw.githubusercontent.com/felangel/bloc/master/examples/flutter_counter/lib/counter/counter.dart\"\n\ttitle=\"lib/counter/counter.dart\"\n/>\n\n以上就是全部！我们将业务逻辑层从展现层种分离了出来。 `CounterView`\n不知道用户按下按钮以后会发生什么；它只是通知\n`CounterCubit`。此外，`CounterCubit`\n也不知道状态（计数器的值）发生了什么；它只是响应调用的方法发出新状态。\n\n我们可以运行 `flutter run` 来在设备或者模拟器上查看运行的效果。\n\n可以在\n[这里](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter)\n找到这个例子的完整代码（包括单元和部件测试）。\n"
  },
  {
    "path": "docs/src/content/docs/zh-cn/why-bloc.mdx",
    "content": "---\ntitle: 为什么用 Bloc？\ndescription: 关于 Bloc 如何成为杰出的状态管理解决方案的概述。\nsidebar:\n  order: 1\n---\n\nBloc 让展现与业务逻辑分离变得更加容易，使你的代码 _更快_, _易于测试_, 并且\n_易于复用_。\n\n当构建产品级别应用时，管理状态变得至关重要。\n\n作为开发者，我们想要：\n\n- 在任何时间点都能知道我们的应用处于什么状态。\n- 方便的测试任何场景以确保我们的应用保持正确的响应。\n- 记录我们程序中的每个用户交互，以便我们做出基于数据驱动的决策。\n- 尽可能高效地工作，并在我们的应用程序和其他应用程序内重复使用组件。\n- 让许多开发人员在遵循相同模式和约定的单一代码库内无缝地工作。\n- 开发快速且反应灵敏的应用程序。\n\nBloc 的设计初衷就是满足所有这些需求，甚至更多。\n\n状态管理解决方案有很多，决定使用哪一种可能是一项艰巨的任务。没有一个完美的状态管理解决方案！重要的是，您要选择最适合您的团队和项目的解决方案。\n\nBloc 的设计秉承了以下三个核心价值观：\n\n- **简单**：易于理解，不同技能水平的开发人员都能轻松上手。\n- **强大**：通过将应用程序组合成更小的组件，帮助创建令人惊叹的复杂应用程序。\n- **易于测试**：轻松测试应用程序的各个方面，以便我们可以放心地进行迭代。\n\n总体而言，Bloc 尝试通过规范状态变更发生的时间以及在整个应用程序中强制采用单一方式来改变状态，从而使状态变更变得可预测。\n"
  },
  {
    "path": "docs/src/env.d.ts",
    "content": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n"
  },
  {
    "path": "docs/src/styles/landing.css",
    "content": ":root {\n\t--green-hsl: 200, 60%, 60%;\n\t--overlay-green: hsla(var(--green-hsl), 0.5);\n}\n\n[data-has-hero] .page {\n\tbackground: linear-gradient(\n\t\tto right,\n\t\t#c3fcfc,\n\t\t#c6fbf6,\n\t\t#cafaf0,\n\t\t#cef9eb,\n\t\t#d2f7e6,\n\t\t#d1f6e1,\n\t\t#d1f5dc,\n\t\t#d1f4d7,\n\t\t#ccf3d0,\n\t\t#c8f2c9,\n\t\t#c4f1c1,\n\t\t#c1f0b9\n\t);\n}\n\n[data-theme='dark'][data-has-hero] .page {\n\tbackground:\n\t\tlinear-gradient(215deg, var(--overlay-green), transparent 40%),\n\t\tradial-gradient(var(--overlay-green), transparent 65%) no-repeat 50% calc(100% + 20rem) / 60rem\n\t\t\t30rem;\n}\n\n[data-has-hero] header {\n\tborder-bottom: 1px solid transparent;\n\tbackground-color: transparent;\n\t-webkit-backdrop-filter: blur(16px);\n\tbackdrop-filter: blur(16px);\n}\n\n[data-has-hero] .card {\n\tborder-radius: 25px;\n\tbackground-color: transparent;\n\tborder: none;\n}\n\n@media screen and (min-width: 50rem) {\n\t[data-has-hero] .hero {\n\t\tpadding-block: clamp(2.5rem, calc(1rem + 10vmin), 2.5rem);\n\t}\n\n\t[data-has-hero] .hero > img {\n\t\tfilter: drop-shadow(0 0 5rem #3982bc);\n\t}\n\n\t[data-theme='dark'][data-has-hero] .hero > img {\n\t\tfilter: drop-shadow(0 0 5rem #81d9ef);\n\t}\n}\n\n/* Secondary Button Styles */\n.sl-link-button.secondary {\n\tborder: 1px solid var(--sl-color-white) !important;\n}\n"
  },
  {
    "path": "docs/src/tailwind.css",
    "content": "@layer base, starlight, theme, components, utilities;\n\n@import '@astrojs/starlight-tailwind';\n@import 'tailwindcss/theme.css' layer(theme);\n@import 'tailwindcss/utilities.css' layer(utilities);\n@import '@fontsource-variable/figtree';\n\n@theme {\n\t--font-sans: 'Figtree Variable', sans-serif;\n\t--color-accent-200: #a8d3c9;\n\t--color-accent-600: #007d6d;\n\t--color-accent-900: #003c33;\n\t--color-accent-950: #002b25;\n\n\t--color-gray-100: #f5f6f8;\n\t--color-gray-200: #eceef2;\n\t--color-gray-300: #c0c2c7;\n\t--color-gray-400: #888b96;\n\t--color-gray-500: #545861;\n\t--color-gray-700: #353841;\n\t--color-gray-800: #24272f;\n\t--color-gray-900: #17181c;\n}\n\n.badges {\n\tmargin-bottom: 1.5em;\n\tdisplay: flex;\n\tflex-direction: row;\n\tgap: 0.5em;\n}\n\n.warning .highlight {\n\tbackground: none;\n}\n\n.info .highlight {\n\tbackground: none;\n}\n\n.warning .mark .code {\n\tborder-left-color: #bda036;\n}\n\n.warning .highlight > .code > span:not(.indent) {\n\tborder-bottom: 2px #bda036 dashed;\n\tpadding: 0;\n}\n\n.info .mark .code {\n\tborder-left-color: #4c81d7;\n}\n\n.info .highlight > .code > span:not(.indent) {\n\tborder-bottom: 2px #4c81d7 dashed;\n\tpadding: 0;\n}\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "examples/angular_counter/.gitignore",
    "content": "# Files and directories created by pub\n.dart_tool/\n.packages\n# Remove the following pattern if you wish to check in your lock file\npubspec.lock\n\n# Conventional directory for build outputs\nbuild/\n\n# Directory created by dartdoc\ndoc/api/\n"
  },
  {
    "path": "examples/angular_counter/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# example\n\nA web app that uses [AngularDart](https://angulardart.dev/).\n\nCreated from templates made available by Stagehand under a BSD-style\n[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE).\n"
  },
  {
    "path": "examples/angular_counter/analysis_options.yaml",
    "content": "include: ../../analysis_options.yaml\n\nanalyzer:\n  exclude: [build/**]\n  errors:\n    uri_has_not_been_generated: ignore\n"
  },
  {
    "path": "examples/angular_counter/lib/app_component.css",
    "content": ":host {\n    /* This is equivalent of the 'body' selector of a page. */\n}\n"
  },
  {
    "path": "examples/angular_counter/lib/app_component.dart",
    "content": "import 'package:angular_counter/src/counter_page/counter_page_component.dart';\nimport 'package:ngdart/angular.dart';\n\n/// Top level application component.\n@Component(\n  selector: 'my-app',\n  templateUrl: 'app_component.html',\n  directives: [CounterPageComponent],\n)\nclass AppComponent {}\n"
  },
  {
    "path": "examples/angular_counter/lib/app_component.html",
    "content": "<counter-page></counter-page>"
  },
  {
    "path": "examples/angular_counter/lib/src/counter_page/counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\n/// Base counter event.\nsealed class CounterEvent {}\n\n/// Notifies bloc to increment state.\nfinal class CounterIncrementPressed extends CounterEvent {}\n\n/// Notifies bloc to decrement state.\nfinal class CounterDecrementPressed extends CounterEvent {}\n\n/// {@template counter_bloc}\n/// A simple [Bloc] that manages an `int` as its state.\n/// {@endtemplate}\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  /// {@macro counter_bloc}\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n"
  },
  {
    "path": "examples/angular_counter/lib/src/counter_page/counter_page_component.css",
    "content": ".counter-page-container {\n  text-align: center;\n}\n\n.counter-button {\n  background: lightskyblue;\n  color: black;\n  padding: 24px;\n  border-radius: 50%;\n  font-size: 24px;\n}\n"
  },
  {
    "path": "examples/angular_counter/lib/src/counter_page/counter_page_component.dart",
    "content": "import 'package:angular_bloc/angular_bloc.dart';\nimport 'package:angular_counter/src/counter_page/counter_bloc.dart';\nimport 'package:ngdart/angular.dart';\n\n/// {@template counter_page}\n/// Counter page component which renders a counter\n/// and allows users to increment/decrement the counter.\n/// {@endtemplate}\n@Component(\n  selector: 'counter-page',\n  templateUrl: 'counter_page_component.html',\n  styleUrls: ['counter_page_component.css'],\n  providers: [ClassProvider(CounterBloc)],\n  pipes: [BlocPipe],\n)\nclass CounterPageComponent implements OnDestroy {\n  /// {@macro counter_page}\n  const CounterPageComponent(this.counterBloc);\n\n  /// The associated [CounterBloc] which manages the count.\n  final CounterBloc counterBloc;\n\n  @override\n  void ngOnDestroy() {\n    counterBloc.close();\n  }\n\n  /// Increment the count.\n  void increment() => counterBloc.add(CounterIncrementPressed());\n\n  /// Decrement the count.\n  void decrement() => counterBloc.add(CounterDecrementPressed());\n}\n"
  },
  {
    "path": "examples/angular_counter/lib/src/counter_page/counter_page_component.html",
    "content": "<div class=\"counter-page-container\">\n  <h1>Counter App</h1>\n  <h2>Current Count: {{ $pipe.bloc(counterBloc) }}</h2>\n  <button class=\"counter-button\" (click)=\"increment()\">➕</button>\n  <button class=\"counter-button\" (click)=\"decrement()\">➖</button>\n</div>\n"
  },
  {
    "path": "examples/angular_counter/pubspec.yaml",
    "content": "name: angular_counter\ndescription: A web app that uses angular_bloc\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  angular_bloc: ^10.0.0-dev.5\n  bloc: ^9.0.0\n  ngdart: ^8.0.0-dev.4\n\ndev_dependencies:\n  build_daemon: ^4.0.0\n  build_runner: ^2.0.0\n  build_web_compilers: ^4.0.0\n"
  },
  {
    "path": "examples/angular_counter/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  angular_bloc:\n    path: ../../packages/angular_bloc\n  build_modules: ^5.0.0\n  build_web_compilers: ^4.0.0\n"
  },
  {
    "path": "examples/angular_counter/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>example</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"styles.css\">\n    <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\">\n    <script defer src=\"main.dart.js\"></script>\n  </head>\n  <body>\n    <my-app>Loading...</my-app>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/angular_counter/web/main.dart",
    "content": "// ignore_for_file: avoid_print\n\nimport 'package:angular_counter/app_component.template.dart' as ng;\nimport 'package:bloc/bloc.dart';\nimport 'package:ngdart/angular.dart';\n\nclass SimpleBlocObserver extends BlocObserver {\n  const SimpleBlocObserver();\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    print(event);\n    super.onEvent(bloc, event);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    print(transition);\n    super.onTransition(bloc, transition);\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    print(error);\n    super.onError(bloc, error, stackTrace);\n  }\n}\n\nvoid main() {\n  Bloc.observer = const SimpleBlocObserver();\n  runApp(ng.AppComponentNgFactory);\n}\n"
  },
  {
    "path": "examples/angular_counter/web/styles.css",
    "content": "@import url(https://fonts.googleapis.com/css?family=Roboto);\n@import url(https://fonts.googleapis.com/css?family=Material+Icons);\n\nbody {\n  max-width: 600px;\n  margin: 0 auto;\n  padding: 5vw;\n}\n\n* {\n  font-family: Roboto, Helvetica, Arial, sans-serif;\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"edada7c56edf4a183c1735310e123c7f923584f1\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: android\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: ios\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: linux\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: macos\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: web\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n    - platform: windows\n      create_revision: edada7c56edf4a183c1735310e123c7f923584f1\n      base_revision: edada7c56edf4a183c1735310e123c7f923584f1\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/README.md",
    "content": "# bloc_concurrency visualizer\n\nA Flutter app to help visualize `bloc_concurrency` transformers\n\n![Demo](https://raw.githubusercontent.com/felangel/bloc/master/examples/bloc_concurrency_visualizer/assets/demo.gif)\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/main.dart",
    "content": "import 'package:bloc_concurrency_visualizer/timeline/timeline.dart';\nimport 'package:flutter/material.dart';\n\nvoid main() => runApp(const MyApp());\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Bloc Concurrency Visualizer',\n      theme: ThemeData.from(\n        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),\n      ),\n      home: const TimelinePage(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/bloc/timeline_bloc.dart",
    "content": "import 'dart:math';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:bloc_concurrency_visualizer/timeline/timeline.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'timeline_event.dart';\npart 'timeline_state.dart';\n\nclass TimelineBloc extends Bloc<TimelineEvent, TimelineState> {\n  TimelineBloc({\n    required Transformer transformer,\n    required double Function() now,\n  }) : _now = now,\n       _transformer = transformer,\n       super(const TimelineState()) {\n    on<TimelineTaskQueued>(_onTaskQueued);\n    on<_TimelineTaskAdded>(_onTaskAdded, transformer: transformer.create());\n    on<TimelineCompleted>((event, emit) => emit(const TimelineState()));\n    on<TimelineReset>((event, emit) => emit(const TimelineState()));\n  }\n\n  final Transformer _transformer;\n\n  final double Function() _now;\n\n  Future<void> _onTaskQueued(\n    TimelineTaskQueued event,\n    Emitter<TimelineState> emit,\n  ) async {\n    final cancel =\n        _transformer.isDroppable &&\n        state.tasks.values.any((task) => task.isRunning);\n\n    final duration = Duration(seconds: Random().nextInt(1) + 1);\n    final now = _now();\n\n    final task = cancel\n        ? Task.canceled(start: now, end: now, duration: duration)\n        : Task.queued(start: now, duration: duration);\n\n    emit(TimelineState(tasks: {...state.tasks, task.id: task}));\n    add(_TimelineTaskAdded(task: task));\n  }\n\n  Future<void> _onTaskAdded(\n    _TimelineTaskAdded event,\n    Emitter<TimelineState> emit,\n  ) async {\n    emit(\n      TimelineState(\n        tasks: {\n          ...state.tasks.map((id, task) {\n            if (id == event.task.id) return MapEntry(id, task.run());\n            return MapEntry(\n              id,\n              _transformer.isRestartable && task.isRunning\n                  ? task.cancel(end: _now())\n                  : task,\n            );\n          }),\n        },\n      ),\n    );\n\n    await Future<void>.delayed(event.task.duration);\n\n    if (!state.tasks.containsKey(event.task.id)) return;\n\n    emit(\n      TimelineState(\n        tasks: {...state.tasks}\n          ..update(\n            event.task.id,\n            (task) => emit.isDone\n                ? task.cancel(end: _now())\n                : task.finish(end: _now()),\n          ),\n      ),\n    );\n  }\n}\n\nextension on Transformer {\n  bool get isDroppable => this == Transformer.droppable;\n  bool get isRestartable => this == Transformer.restartable;\n\n  EventTransformer<_TimelineTaskAdded> create() {\n    return switch (this) {\n      Transformer.concurrent => concurrent(),\n      Transformer.sequential => sequential(),\n      Transformer.droppable => droppable(),\n      Transformer.restartable => restartable(),\n    };\n  }\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/bloc/timeline_event.dart",
    "content": "part of 'timeline_bloc.dart';\n\nsealed class TimelineEvent {\n  const TimelineEvent();\n}\n\nclass TimelineTaskQueued extends TimelineEvent {\n  const TimelineTaskQueued();\n}\n\nclass _TimelineTaskAdded extends TimelineEvent {\n  const _TimelineTaskAdded({required this.task});\n\n  final Task task;\n}\n\nclass TimelineCompleted extends TimelineEvent {\n  const TimelineCompleted();\n}\n\nclass TimelineReset extends TimelineEvent {\n  const TimelineReset();\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/bloc/timeline_state.dart",
    "content": "part of 'timeline_bloc.dart';\n\nclass TimelineState extends Equatable {\n  const TimelineState({this.tasks = const {}});\n\n  final Map<String, Task> tasks;\n\n  @override\n  List<Object> get props => [tasks];\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/models/models.dart",
    "content": "export './task.dart';\nexport './transformer.dart';\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/models/task.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:uuid/uuid.dart';\n\nenum TaskStatus { queued, running, finished, canceled }\n\nclass Task extends Equatable {\n  Task.queued({required double start, required Duration duration})\n    : this._(start: start, duration: duration);\n\n  Task.canceled({\n    required double start,\n    required double end,\n    required Duration duration,\n  }) : this._(\n         start: start,\n         end: end,\n         duration: duration,\n         status: TaskStatus.canceled,\n       );\n\n  Task._({\n    required this.start,\n    required this.duration,\n    this.status = TaskStatus.queued,\n    this.end,\n    String? id,\n  }) : id = id ?? const Uuid().v4();\n\n  final double start;\n  final Duration duration;\n  final TaskStatus status;\n  final double? end;\n  final String id;\n\n  bool get isRunning => status == TaskStatus.running;\n  bool get isCanceled => status == TaskStatus.canceled;\n\n  Task run() => _copyWith(status: TaskStatus.running);\n\n  Task finish({required double end}) {\n    return _copyWith(status: TaskStatus.finished, end: end);\n  }\n\n  Task cancel({required double end}) {\n    return _copyWith(status: TaskStatus.canceled, end: end);\n  }\n\n  Task _copyWith({TaskStatus? status, double? end}) {\n    return Task._(\n      id: id,\n      duration: duration,\n      start: start,\n      end: end ?? this.end,\n      status: status ?? this.status,\n    );\n  }\n\n  @override\n  List<Object?> get props => [start, end, status];\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/models/transformer.dart",
    "content": "enum Transformer { concurrent, sequential, droppable, restartable }\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/timeline.dart",
    "content": "export './bloc/timeline_bloc.dart';\nexport './models/models.dart';\nexport './view/timeline_page.dart';\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/lib/timeline/view/timeline_page.dart",
    "content": "import 'package:bloc_concurrency_visualizer/timeline/timeline.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass TimelinePage extends StatelessWidget {\n  const TimelinePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('Bloc Concurrency Visualizer'),\n        forceMaterialTransparency: true,\n      ),\n      body: ListView.builder(\n        itemCount: Transformer.values.length,\n        itemBuilder: (_, i) => TimelineView(transformer: Transformer.values[i]),\n      ),\n    );\n  }\n}\n\nclass TimelineView extends StatefulWidget {\n  const TimelineView({required this.transformer, super.key});\n\n  final Transformer transformer;\n\n  @override\n  State<TimelineView> createState() => _TimelineViewState();\n}\n\nclass _TimelineViewState extends State<TimelineView>\n    with SingleTickerProviderStateMixin {\n  static const _duration = Duration(seconds: 10);\n  static const _spacing = 8.0;\n\n  late final TimelineBloc _bloc;\n  late final AnimationController _timelineController;\n\n  @override\n  void initState() {\n    super.initState();\n    _timelineController = AnimationController(vsync: this, duration: _duration)\n      ..addListener(_onAnimation);\n\n    _bloc = TimelineBloc(\n      transformer: widget.transformer,\n      now: () => _timelineController.value,\n    );\n  }\n\n  void _onAnimation() {\n    if (_timelineController.isCompleted) {\n      _bloc.add(const TimelineCompleted());\n      _timelineController.reset();\n    }\n    setState(() {});\n  }\n\n  @override\n  void dispose() {\n    _timelineController.dispose();\n    _bloc.close();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n\n    final buttonStyle = OutlinedButton.styleFrom(\n      shape: const BeveledRectangleBorder(),\n      side: const BorderSide(width: 0.5),\n      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),\n    );\n\n    final title = Text(\n      widget.transformer.title,\n      style: theme.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),\n    );\n\n    final description = Text(\n      widget.transformer.description,\n      style: theme.textTheme.bodyMedium,\n    );\n\n    final addTaskButton = OutlinedButton(\n      style: buttonStyle,\n      onPressed: () {\n        _bloc.add(const TimelineTaskQueued());\n        if (!_timelineController.isAnimating) {\n          _timelineController.forward();\n        }\n      },\n      child: const Text('add task'),\n    );\n\n    final resetButton = OutlinedButton(\n      style: buttonStyle,\n      onPressed: () {\n        _bloc.add(const TimelineReset());\n        _timelineController.reset();\n      },\n      child: const Text('reset'),\n    );\n\n    final actions = Row(\n      spacing: _spacing,\n      children: [addTaskButton, resetButton],\n    );\n\n    final timeline = BlocBuilder<TimelineBloc, TimelineState>(\n      bloc: _bloc,\n      builder: (context, state) => Timeline(\n        cursorPosition: _timelineController.value,\n        tasks: [...state.tasks.values],\n      ),\n    );\n\n    const gap = SizedBox(height: _spacing);\n\n    return Padding(\n      padding: const EdgeInsets.all(_spacing),\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.start,\n        mainAxisSize: MainAxisSize.min,\n        children: [title, description, gap, actions, gap, timeline],\n      ),\n    );\n  }\n}\n\nclass Timeline extends StatelessWidget {\n  const Timeline({\n    required this.tasks,\n    required this.cursorPosition,\n    super.key,\n  });\n\n  final double cursorPosition;\n  final List<Task> tasks;\n\n  static const _height = 100.0;\n  static const _barCount = 4;\n  static const _barSpacing = 6.5;\n  static const _barHeight = _height / 5;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    final width = MediaQuery.of(context).size.width;\n\n    final background = Positioned.fill(\n      child: SizedBox(\n        width: width,\n        height: _height,\n        child: DecoratedBox(decoration: BoxDecoration(border: Border.all())),\n      ),\n    );\n\n    final cursor = Positioned(\n      left: width * cursorPosition,\n      child: SizedBox(\n        width: 1,\n        height: _height,\n        child: DecoratedBox(\n          decoration: BoxDecoration(border: Border.all(), color: Colors.black),\n        ),\n      ),\n    );\n\n    final instructions = Align(\n      child: Column(\n        mainAxisSize: MainAxisSize.min,\n        children: [\n          Text(\n            'timeline is empty',\n            style: theme.textTheme.bodyLarge?.copyWith(\n              fontWeight: FontWeight.bold,\n            ),\n          ),\n          const Text('tap \"add task\" to enqueue tasks'),\n        ],\n      ),\n    );\n\n    return SizedBox(\n      width: width,\n      height: _height,\n      child: Stack(\n        children: [\n          background,\n          cursor,\n          if (tasks.isEmpty) instructions,\n          for (final (index, task) in tasks.indexed) ...[\n            _BarBackground(\n              left: width * task.start,\n              top: (index % _barCount) * (_barHeight + _barSpacing),\n              height: _barHeight,\n              value: cursorPosition,\n              task: task,\n            ),\n            _BarForeground(\n              left: width * task.start,\n              top: (index % _barCount) * (_barHeight + _barSpacing),\n              height: _barHeight,\n              width: width,\n              task: task,\n              text: Text(\n                '#${index + 1} ${task.status.text}',\n                style: theme.textTheme.bodyMedium?.copyWith(\n                  color: task.status.color,\n                  decoration: task.isCanceled\n                      ? TextDecoration.lineThrough\n                      : null,\n                ),\n              ),\n            ),\n          ],\n        ],\n      ),\n    );\n  }\n}\n\nclass _BarBackground extends StatelessWidget {\n  const _BarBackground({\n    required this.left,\n    required this.top,\n    required this.height,\n    required this.value,\n    required this.task,\n  });\n\n  final double left;\n  final double top;\n  final double height;\n  final double value;\n  final Task task;\n\n  @override\n  Widget build(BuildContext context) {\n    final width = MediaQuery.of(context).size.width;\n\n    return Positioned(\n      left: left,\n      top: top,\n      child: SizedBox(\n        width: (((task.end ?? value) - task.start) * width).abs(),\n        height: height,\n        child: DecoratedBox(\n          decoration: BoxDecoration(\n            border: task.isCanceled\n                ? Border(left: BorderSide(color: task.status.borderColor))\n                : Border.all(color: task.status.borderColor),\n            color: task.status.fillColor,\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass _BarForeground extends StatelessWidget {\n  const _BarForeground({\n    required this.left,\n    required this.top,\n    required this.height,\n    required this.width,\n    required this.text,\n    required this.task,\n  });\n\n  final double left;\n  final double top;\n  final double height;\n  final double width;\n  final Text text;\n  final Task task;\n\n  @override\n  Widget build(BuildContext context) {\n    return Positioned(\n      left: left,\n      top: top,\n      child: SizedBox(\n        width: 100,\n        height: height,\n        child: Padding(padding: const EdgeInsets.only(left: 4), child: text),\n      ),\n    );\n  }\n}\n\nextension on TaskStatus {\n  String get text {\n    return switch (this) {\n      TaskStatus.queued => 'Queued',\n      TaskStatus.running => 'Running',\n      TaskStatus.finished => 'Finished',\n      TaskStatus.canceled => 'Canceled',\n    };\n  }\n\n  Color get borderColor {\n    return switch (this) {\n      TaskStatus.queued => Colors.orange.shade800,\n      TaskStatus.running => Colors.lightBlue.shade800,\n      TaskStatus.finished => Colors.greenAccent.shade700,\n      TaskStatus.canceled => Colors.grey.shade800,\n    };\n  }\n\n  Color get fillColor {\n    return switch (this) {\n      TaskStatus.queued => Colors.orange.shade300,\n      TaskStatus.running => Colors.lightBlue.shade300,\n      TaskStatus.finished => Colors.greenAccent.shade200,\n      TaskStatus.canceled => Colors.transparent,\n    };\n  }\n\n  Color get color {\n    return switch (this) {\n      TaskStatus.queued => Colors.black,\n      TaskStatus.running => Colors.black,\n      TaskStatus.finished => Colors.black,\n      TaskStatus.canceled => Colors.grey.shade700,\n    };\n  }\n}\n\nextension on Transformer {\n  String get title {\n    return switch (this) {\n      Transformer.concurrent => 'Concurrent',\n      Transformer.sequential => 'Sequential',\n      Transformer.droppable => 'Droppable',\n      Transformer.restartable => 'Restartable',\n    };\n  }\n\n  String get description {\n    return switch (this) {\n      Transformer.concurrent => 'Process all tasks in parallel.',\n      Transformer.sequential => 'Process tasks in order, one at a time.',\n      Transformer.restartable => 'Only process the most recent task.',\n      Transformer.droppable => 'Ignore new tasks if a task is processing.',\n    };\n  }\n}\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/pubspec.yaml",
    "content": "name: bloc_concurrency_visualizer\ndescription: A Flutter app to help visualize bloc_concurrency transformers\nversion: 1.0.0+1\npublish_to: \"none\"\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  bloc_concurrency: ^0.3.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.0.0\n  uuid: ^4.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n    <base href=\"$FLUTTER_BASE_HREF\" />\n\n    <meta charset=\"UTF-8\" />\n    <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\" />\n    <meta name=\"description\" content=\"A new Flutter project.\" />\n\n    <!-- iOS meta tags & icons -->\n    <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\" />\n    <meta\n      name=\"apple-mobile-web-app-title\"\n      content=\"Bloc Concurrency Visualizer\"\n    />\n    <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\" />\n\n    <!-- Favicon -->\n    <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\" />\n\n    <title>Bloc Concurrency Visualizer</title>\n    <link rel=\"manifest\" href=\"manifest.json\" />\n  </head>\n  <body>\n    <script src=\"flutter_bootstrap.js\" async></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/bloc_concurrency_visualizer/web/manifest.json",
    "content": "{\n    \"name\": \"example\",\n    \"short_name\": \"example\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_bloc_with_stream\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3"
  },
  {
    "path": "examples/flutter_bloc_with_stream/lib/bloc/ticker_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc_with_stream/ticker/ticker.dart';\n\npart 'ticker_event.dart';\npart 'ticker_state.dart';\n\n/// {@template ticker_bloc}\n/// Bloc which manages the current [TickerState]\n/// and depends on a [Ticker] instance.\n/// {@endtemplate}\nclass TickerBloc extends Bloc<TickerEvent, TickerState> {\n  /// {@macro ticker_bloc}\n  TickerBloc(Ticker ticker) : super(TickerInitial()) {\n    on<TickerStarted>(\n      (event, emit) async {\n        await emit.onEach<int>(\n          ticker.tick(),\n          onData: (tick) => add(_TickerTicked(tick)),\n        );\n        emit(const TickerComplete());\n      },\n      transformer: restartable(),\n    );\n\n    on<_TickerTicked>((event, emit) => emit(TickerTickSuccess(event.tick)));\n  }\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/lib/bloc/ticker_event.dart",
    "content": "part of 'ticker_bloc.dart';\n\n/// {@template ticker_event}\n/// Base class for all [TickerEvent]s which are\n/// handled by the [TickerBloc].\n/// {@endtemplate}\nsealed class TickerEvent extends Equatable {\n  /// {@macro ticker_event}\n  const TickerEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\n/// {@template ticker_started}\n/// Signifies to the [TickerBloc] that the user\n/// has requested to start the [Ticker].\n/// {@endtemplate}\nfinal class TickerStarted extends TickerEvent {\n  /// {@macro ticker_started}\n  const TickerStarted();\n}\n\nfinal class _TickerTicked extends TickerEvent {\n  const _TickerTicked(this.tick);\n\n  /// The current tick count.\n  final int tick;\n\n  @override\n  List<Object> get props => [tick];\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/lib/bloc/ticker_state.dart",
    "content": "part of 'ticker_bloc.dart';\n\n/// {@template ticker_state}\n/// Base class for all [TickerState]s which are\n/// managed by the [TickerBloc].\n/// {@endtemplate}\nsealed class TickerState extends Equatable {\n  /// {@macro ticker_state}\n  const TickerState();\n\n  @override\n  List<Object> get props => [];\n}\n\n/// The initial state of the [TickerBloc].\nfinal class TickerInitial extends TickerState {}\n\n/// {@template ticker_tick_success}\n/// The state of the [TickerBloc] after a [Ticker]\n/// has been started and includes the current tick count.\n/// {@endtemplate}\nfinal class TickerTickSuccess extends TickerState {\n  /// {@macro ticker_tick_success}\n  const TickerTickSuccess(this.count);\n\n  /// The current tick count.\n  final int count;\n\n  @override\n  List<Object> get props => [count];\n}\n\n/// {@template ticker_complete}\n/// The state of the [TickerBloc] after the [Ticker] has completed.\n/// {@endtemplate}\nfinal class TickerComplete extends TickerState {\n  /// {@macro ticker_complete}\n  const TickerComplete();\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/lib/main.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_bloc_with_stream/bloc/ticker_bloc.dart';\nimport 'package:flutter_bloc_with_stream/ticker/ticker.dart';\n\nvoid main() => runApp(TickerApp());\n\n/// {@template ticker_app}\n/// [MaterialApp] which sets the [TickerPage] as the `home`.\n/// [TickerApp] also provides an instance of [TickerBloc] to\n/// the [TickerPage].\n/// {@endtemplate}\nclass TickerApp extends MaterialApp {\n  /// {@macro ticker_app}\n  TickerApp({super.key})\n    : super(\n        home: BlocProvider(\n          create: (_) => TickerBloc(Ticker()),\n          child: const TickerPage(),\n        ),\n      );\n}\n\n/// {@template ticker_page}\n/// [StatelessWidget] which consumes a [TickerBloc]\n/// and responds to changes in the [TickerState].\n/// [TickerPage] also notifies the [TickerBloc] when\n/// the user taps on the start button.\n/// {@endtemplate}\nclass TickerPage extends StatelessWidget {\n  /// {@macro ticker_page}\n  const TickerPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocBuilder<TickerBloc, TickerState>(\n          builder: (context, state) {\n            return switch (state) {\n              TickerInitial() => const Text(\n                'Press the floating button to start.',\n              ),\n              TickerTickSuccess() => Text('Tick #${state.count}'),\n              TickerComplete() => const Text(\n                'Complete! Press the floating button to restart.',\n              ),\n            };\n          },\n        ),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: () => context.read<TickerBloc>().add(const TickerStarted()),\n        tooltip: 'Start',\n        child: const Icon(Icons.timer),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/lib/ticker/ticker.dart",
    "content": "import 'dart:async';\n\n/// Class which exposes a `tick` method to emit values periodically.\nclass Ticker {\n  /// Emits a new `int` up to 10 every second.\n  Stream<int> tick() {\n    return Stream.periodic(const Duration(seconds: 1), (x) => x).take(10);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/pubspec.yaml",
    "content": "name: flutter_bloc_with_stream\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  bloc_concurrency: ^0.3.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_concurrency:\n    path: ../../packages/bloc_concurrency\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/test/app_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc_with_stream/main.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(TickerApp, () {\n    testWidgets('is a MaterialApp', (tester) async {\n      expect(TickerApp(), isA<MaterialApp>());\n    });\n\n    testWidgets('renders $TickerPage', (tester) async {\n      await tester.pumpWidget(TickerApp());\n      expect(find.byType(TickerPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/test/bloc/ticker_bloc_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_bloc_with_stream/bloc/ticker_bloc.dart';\nimport 'package:flutter_bloc_with_stream/ticker/ticker.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockTicker extends Mock implements Ticker {}\n\nvoid main() {\n  group(TickerBloc, () {\n    late Ticker ticker;\n\n    setUp(() {\n      ticker = _MockTicker();\n      when(ticker.tick).thenAnswer(\n        (_) => Stream<int>.fromIterable([1, 2, 3]),\n      );\n    });\n\n    test('initial state is $TickerInitial', () {\n      expect(TickerBloc(ticker).state, TickerInitial());\n    });\n\n    blocTest<TickerBloc, TickerState>(\n      'emits [] when ticker has not started',\n      build: () => TickerBloc(ticker),\n      expect: () => <TickerState>[],\n    );\n\n    blocTest<TickerBloc, TickerState>(\n      'emits $TickerTickSuccess from 1 to 3',\n      build: () => TickerBloc(ticker),\n      act: (bloc) => bloc.add(TickerStarted()),\n      expect: () => <TickerState>[\n        TickerTickSuccess(1),\n        TickerTickSuccess(2),\n        TickerTickSuccess(3),\n        TickerComplete(),\n      ],\n    );\n\n    blocTest<TickerBloc, TickerState>(\n      'emits $TickerTickSuccess '\n      'from 1 to 3 and cancels previous subscription',\n      build: () => TickerBloc(ticker),\n      act: (bloc) => bloc\n        ..add(TickerStarted())\n        ..add(TickerStarted()),\n      expect: () => <TickerState>[\n        TickerTickSuccess(1),\n        TickerTickSuccess(2),\n        TickerTickSuccess(3),\n        TickerComplete(),\n      ],\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/test/bloc/ticker_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_bloc_with_stream/bloc/ticker_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(TickerEvent, () {\n    group(TickerStarted, () {\n      test('supports value comparison', () {\n        expect(TickerStarted(), equals(TickerStarted()));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/test/bloc/ticker_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_bloc_with_stream/bloc/ticker_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(TickerState, () {\n    group(TickerInitial, () {\n      test('supports value comparison', () {\n        expect(TickerInitial(), TickerInitial());\n      });\n    });\n\n    group(TickerTickSuccess, () {\n      test('supports value comparison', () {\n        expect(TickerTickSuccess(1), TickerTickSuccess(1));\n        expect(\n          TickerTickSuccess(1),\n          isNot(equals(TickerTickSuccess(2))),\n        );\n      });\n    });\n\n    group(TickerComplete, () {\n      test('supports value comparison', () {\n        expect(TickerComplete(), equals(TickerComplete()));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/test/ticker_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_bloc_with_stream/bloc/ticker_bloc.dart';\nimport 'package:flutter_bloc_with_stream/main.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockTickerBloc extends MockBloc<TickerEvent, TickerState>\n    implements TickerBloc {}\n\nextension on WidgetTester {\n  Future<void> pumpTickerPage(TickerBloc tickerBloc) {\n    return pumpWidget(\n      MaterialApp(\n        home: BlocProvider.value(value: tickerBloc, child: TickerPage()),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  late TickerBloc tickerBloc;\n\n  setUp(() {\n    tickerBloc = _MockTickerBloc();\n  });\n\n  tearDown(() => reset(tickerBloc));\n\n  group(TickerPage, () {\n    testWidgets('renders initial state', (tester) async {\n      when(() => tickerBloc.state).thenReturn(TickerInitial());\n      await tester.pumpTickerPage(tickerBloc);\n\n      expect(find.text('Press the floating button to start.'), findsOneWidget);\n    });\n\n    testWidgets('renders tick count ', (tester) async {\n      const tickCount = 5;\n      when(() => tickerBloc.state).thenReturn(TickerTickSuccess(tickCount));\n      await tester.pumpTickerPage(tickerBloc);\n\n      expect(find.text('Tick #$tickCount'), findsOneWidget);\n    });\n\n    testWidgets('adds ticker started '\n        'when start ticker floating action button is pressed', (tester) async {\n      when(() => tickerBloc.state).thenReturn(TickerInitial());\n      await tester.pumpTickerPage(tickerBloc);\n\n      await tester.tap(find.byType(FloatingActionButton));\n      verify(() => tickerBloc.add(TickerStarted())).called(1);\n    });\n\n    testWidgets('tick count periodically increments '\n        'every 1 second', (tester) async {\n      whenListen(\n        tickerBloc,\n        Stream.periodic(Duration(seconds: 1), TickerTickSuccess.new).take(3),\n        initialState: TickerInitial(),\n      );\n\n      await tester.pumpTickerPage(tickerBloc..add(TickerStarted()));\n\n      await tester.pump(Duration(seconds: 1));\n      expect(find.text('Tick #0'), findsOneWidget);\n      await tester.pump(Duration(seconds: 1));\n      expect(find.text('Tick #1'), findsOneWidget);\n      await tester.pump(Duration(seconds: 1));\n      expect(find.text('Tick #2'), findsOneWidget);\n    });\n\n    testWidgets('renders complete state', (tester) async {\n      when(() => tickerBloc.state).thenReturn(TickerComplete());\n\n      await tester.pumpTickerPage(tickerBloc);\n      await tester.pumpAndSettle();\n\n      expect(\n        find.text('Complete! Press the floating button to restart.'),\n        findsOneWidget,\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_bloc_with_stream\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_bloc_with_stream</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_bloc_with_stream/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_bloc_with_stream\",\n    \"short_name\": \"flutter_bloc_with_stream\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_complex_list/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_complex_list/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_complex_list\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our \n[online documentation](https://flutter.dev/docs), which offers tutorials, \nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_complex_list/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_complex_list/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3"
  },
  {
    "path": "examples/flutter_complex_list/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\n\nclass App extends MaterialApp {\n  App({required Repository repository, super.key})\n    : super(\n        home: RepositoryProvider.value(\n          value: repository,\n          child: const ComplexListPage(),\n        ),\n      );\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/complex_list.dart",
    "content": "export 'cubit/complex_list_cubit.dart';\nexport 'models/models.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/cubit/complex_list_cubit.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\n\npart 'complex_list_state.dart';\n\nclass ComplexListCubit extends Cubit<ComplexListState> {\n  ComplexListCubit({required Repository repository})\n    : _repository = repository,\n      super(const ComplexListState.loading());\n\n  final Repository _repository;\n\n  Future<void> fetchList() async {\n    try {\n      final items = await _repository.fetchItems();\n      emit(ComplexListState.success(items));\n    } on Exception {\n      emit(const ComplexListState.failure());\n    }\n  }\n\n  Future<void> deleteItem(String id) async {\n    final deleteInProgress = state.items.map((item) {\n      return item.id == id ? item.copyWith(isDeleting: true) : item;\n    }).toList();\n\n    emit(ComplexListState.success(deleteInProgress));\n\n    unawaited(\n      _repository.deleteItem(id).then((_) {\n        final deleteSuccess = List.of(state.items)\n          ..removeWhere((element) => element.id == id);\n        emit(ComplexListState.success(deleteSuccess));\n      }),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/cubit/complex_list_state.dart",
    "content": "part of 'complex_list_cubit.dart';\n\nenum ListStatus { loading, success, failure }\n\nfinal class ComplexListState extends Equatable {\n  const ComplexListState._({\n    this.status = ListStatus.loading,\n    this.items = const <Item>[],\n  });\n\n  const ComplexListState.loading() : this._();\n\n  const ComplexListState.success(List<Item> items)\n    : this._(status: ListStatus.success, items: items);\n\n  const ComplexListState.failure() : this._(status: ListStatus.failure);\n\n  final ListStatus status;\n  final List<Item> items;\n\n  @override\n  List<Object> get props => [status, items];\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/models/item.dart",
    "content": "import 'package:equatable/equatable.dart';\n\nclass Item extends Equatable {\n  const Item({\n    required this.id,\n    required this.value,\n    this.isDeleting = false,\n  });\n\n  final String id;\n  final String value;\n  final bool isDeleting;\n\n  Item copyWith({String? id, String? value, bool? isDeleting}) {\n    return Item(\n      id: id ?? this.id,\n      value: value ?? this.value,\n      isDeleting: isDeleting ?? this.isDeleting,\n    );\n  }\n\n  @override\n  List<Object> get props => [id, value, isDeleting];\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/models/models.dart",
    "content": "export 'item.dart';\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/view/complex_list_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\n\nclass ComplexListPage extends StatelessWidget {\n  const ComplexListPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: BlocProvider(\n        create: (_) => ComplexListCubit(\n          repository: context.read<Repository>(),\n        )..fetchList(),\n        child: const ComplexListView(),\n      ),\n    );\n  }\n}\n\nclass ComplexListView extends StatelessWidget {\n  const ComplexListView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<ComplexListCubit>().state;\n    switch (state.status) {\n      case ListStatus.failure:\n        return const Center(child: Text('Oops something went wrong!'));\n      case ListStatus.success:\n        return ItemView(items: state.items);\n      case ListStatus.loading:\n        return const Center(child: CircularProgressIndicator());\n    }\n  }\n}\n\nclass ItemView extends StatelessWidget {\n  const ItemView({required this.items, super.key});\n\n  final List<Item> items;\n\n  @override\n  Widget build(BuildContext context) {\n    return items.isEmpty\n        ? const Center(child: Text('No Content'))\n        : ListView.builder(\n            itemBuilder: (BuildContext context, int index) {\n              return ItemTile(\n                item: items[index],\n                onDeletePressed: (id) {\n                  context.read<ComplexListCubit>().deleteItem(id);\n                },\n              );\n            },\n            itemCount: items.length,\n          );\n  }\n}\n\nclass ItemTile extends StatelessWidget {\n  const ItemTile({\n    required this.item,\n    required this.onDeletePressed,\n    super.key,\n  });\n\n  final Item item;\n  final ValueSetter<String> onDeletePressed;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Material(\n      child: ListTile(\n        title: Text(item.value),\n        trailing: item.isDeleting\n            ? const CircularProgressIndicator()\n            : IconButton(\n                icon: Icon(Icons.delete, color: theme.colorScheme.error),\n                onPressed: () => onDeletePressed(item.id),\n              ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/complex_list/view/view.dart",
    "content": "export 'complex_list_page.dart';\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/main.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_complex_list/app.dart';\nimport 'package:flutter_complex_list/repository.dart';\nimport 'package:flutter_complex_list/simple_bloc_observer.dart';\n\nvoid main() {\n  Bloc.observer = const SimpleBlocObserver();\n  runApp(App(repository: Repository()));\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/repository.dart",
    "content": "import 'dart:async';\nimport 'dart:math';\n\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\n\nclass Repository {\n  final _random = Random();\n\n  int _randomRange(int min, int max) => min + _random.nextInt(max - min);\n\n  Future<List<Item>> fetchItems() async {\n    await Future<void>.delayed(Duration(seconds: _randomRange(1, 5)));\n    return List.of(_generateItemsList(10));\n  }\n\n  List<Item> _generateItemsList(int length) {\n    return List.generate(\n      length,\n      (index) => Item(id: '$index', value: 'Item $index'),\n    );\n  }\n\n  Future<void> deleteItem(String id) async {\n    await Future<void>.delayed(Duration(seconds: _randomRange(1, 5)));\n  }\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/lib/simple_bloc_observer.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass SimpleBlocObserver extends BlocObserver {\n  const SimpleBlocObserver();\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    // ignore: avoid_print\n    print(error);\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    // ignore: avoid_print\n    print(change);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/pubspec.yaml",
    "content": "name: flutter_complex_list\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_complex_list/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_complex_list/test/app_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_complex_list/app.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockRepository extends Mock implements Repository {}\n\nvoid main() {\n  late Repository repository;\n\n  setUp(() {\n    repository = MockRepository();\n  });\n\n  group(App, () {\n    testWidgets('is a $MaterialApp', (tester) async {\n      expect(App(repository: repository), isA<MaterialApp>());\n    });\n\n    testWidgets('renders $ComplexListPage', (tester) async {\n      when(repository.fetchItems).thenAnswer((_) async => []);\n      await tester.pumpWidget(App(repository: repository));\n      expect(find.byType(ComplexListPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/test/complex_list/cubit/complex_list_cubit_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockRepository extends Mock implements Repository {}\n\nvoid main() {\n  const mockItems = [\n    Item(id: '1', value: '1'),\n    Item(id: '2', value: '2'),\n    Item(id: '3', value: '3'),\n  ];\n\n  group(ComplexListCubit, () {\n    late Repository repository;\n\n    setUp(() {\n      repository = _MockRepository();\n    });\n\n    test('initial state is ${ComplexListState.loading}', () {\n      expect(\n        ComplexListCubit(repository: repository).state,\n        const ComplexListState.loading(),\n      );\n    });\n\n    group('fetchList', () {\n      blocTest<ComplexListCubit, ComplexListState>(\n        'emits ${ComplexListState.success} after fetching list',\n        setUp: () {\n          when(repository.fetchItems).thenAnswer((_) async => mockItems);\n        },\n        build: () => ComplexListCubit(repository: repository),\n        act: (cubit) => cubit.fetchList(),\n        expect: () => [\n          const ComplexListState.success(mockItems),\n        ],\n        verify: (_) => verify(repository.fetchItems).called(1),\n      );\n\n      blocTest<ComplexListCubit, ComplexListState>(\n        'emits ${ComplexListState.failure} after failing to fetch list',\n        setUp: () {\n          when(repository.fetchItems).thenThrow(Exception('Error'));\n        },\n        build: () => ComplexListCubit(repository: repository),\n        act: (cubit) => cubit.fetchList(),\n        expect: () => [\n          const ComplexListState.failure(),\n        ],\n        verify: (_) => verify(repository.fetchItems).called(1),\n      );\n    });\n\n    group('deleteItem', () {\n      blocTest<ComplexListCubit, ComplexListState>(\n        'emits corrects states when deleting an item',\n        setUp: () {\n          when(() => repository.deleteItem('2')).thenAnswer((_) async {});\n        },\n        build: () => ComplexListCubit(repository: repository),\n        seed: () => const ComplexListState.success(mockItems),\n        act: (cubit) => cubit.deleteItem('2'),\n        expect: () => [\n          const ComplexListState.success([\n            Item(id: '1', value: '1'),\n            Item(id: '2', value: '2', isDeleting: true),\n            Item(id: '3', value: '3'),\n          ]),\n          const ComplexListState.success([\n            Item(id: '1', value: '1'),\n            Item(id: '3', value: '3'),\n          ]),\n        ],\n        verify: (_) => verify(() => repository.deleteItem('2')).called(1),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/test/complex_list/cubit/complex_list_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(ComplexListState, () {\n    const mockItems = [Item(id: '1', value: '1')];\n    test('support value comparisons', () {\n      expect(ComplexListState.loading(), ComplexListState.loading());\n      expect(ComplexListState.failure(), ComplexListState.failure());\n      expect(\n        ComplexListState.success(mockItems),\n        ComplexListState.success(mockItems),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/test/complex_list/models/item_test.dart",
    "content": "import 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(Item, () {\n    const mockItem = Item(id: '1', value: 'Item 1');\n    test('supports value comparisons', () {\n      expect(mockItem, mockItem);\n    });\n\n    test('supports copyWith comparisons', () {\n      expect(mockItem.copyWith(), mockItem);\n      expect(\n        mockItem.copyWith(id: '2', value: 'Item 2'),\n        isNot(equals(mockItem)),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/test/complex_list/view/complex_list_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockRepository extends Mock implements Repository {}\n\nclass _MockComplexListCubit extends MockCubit<ComplexListState>\n    implements ComplexListCubit {}\n\nextension on WidgetTester {\n  Future<void> pumpListPage(Repository repository) {\n    return pumpWidget(\n      MaterialApp(\n        home: RepositoryProvider.value(\n          value: repository,\n          child: const ComplexListPage(),\n        ),\n      ),\n    );\n  }\n\n  Future<void> pumpListView(ComplexListCubit listCubit) {\n    return pumpWidget(\n      MaterialApp(\n        home: BlocProvider.value(\n          value: listCubit,\n          child: const ComplexListView(),\n        ),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  const mockItems = [\n    Item(id: '1', value: 'Item 1'),\n    Item(id: '2', value: 'Item 2'),\n    Item(id: '3', value: 'Item 3'),\n  ];\n\n  late Repository repository;\n  late ComplexListCubit listCubit;\n\n  setUp(() {\n    repository = _MockRepository();\n    listCubit = _MockComplexListCubit();\n  });\n\n  group(ComplexListPage, () {\n    testWidgets('renders $ComplexListView', (tester) async {\n      when(() => repository.fetchItems()).thenAnswer((_) async => []);\n      await tester.pumpListPage(repository);\n      expect(find.byType(ComplexListView), findsOneWidget);\n    });\n  });\n\n  group(ComplexListView, () {\n    testWidgets('renders $CircularProgressIndicator while '\n        'waiting for items to load', (tester) async {\n      when(() => listCubit.state).thenReturn(const ComplexListState.loading());\n      await tester.pumpListView(listCubit);\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n\n    testWidgets('renders error text '\n        'when items fail to load', (tester) async {\n      when(() => listCubit.state).thenReturn(const ComplexListState.failure());\n      await tester.pumpListView(listCubit);\n      expect(find.text('Oops something went wrong!'), findsOneWidget);\n    });\n\n    testWidgets('renders $ComplexListView after items '\n        'are finished loading', (tester) async {\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success(mockItems),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.byType(ComplexListView), findsOneWidget);\n    });\n    testWidgets('renders \"No Content\" text when '\n        'no items are present', (tester) async {\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success([]),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.text('No Content'), findsOneWidget);\n    });\n\n    testWidgets('renders three ${ItemTile}s', (tester) async {\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success(mockItems),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.byType(ItemTile), findsNWidgets(3));\n    });\n\n    testWidgets('deletes first item', (tester) async {\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success(mockItems),\n      );\n      when(() => listCubit.deleteItem('1')).thenAnswer((_) async {});\n      await tester.pumpListView(listCubit);\n      await tester.tap(find.byIcon(Icons.delete).first);\n      verify(() => listCubit.deleteItem('1')).called(1);\n    });\n  });\n\n  group(ItemTile, () {\n    testWidgets('renders value text', (tester) async {\n      const mockItem = Item(id: '1', value: 'Item 1');\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success([mockItem]),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.text('Item 1'), findsOneWidget);\n    });\n\n    testWidgets('renders delete icon button '\n        'when item is not being deleted', (tester) async {\n      const mockItem = Item(id: '1', value: 'Item 1');\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success([mockItem]),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.byIcon(Icons.delete), findsOneWidget);\n    });\n\n    testWidgets('renders $CircularProgressIndicator '\n        'when item is being deleting', (tester) async {\n      const mockItem = Item(id: '1', value: 'Item 1', isDeleting: true);\n      when(() => listCubit.state).thenReturn(\n        const ComplexListState.success([mockItem]),\n      );\n      await tester.pumpListView(listCubit);\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/test/repository_test.dart",
    "content": "import 'package:flutter_complex_list/complex_list/complex_list.dart';\nimport 'package:flutter_complex_list/repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(Repository, () {\n    late Repository repository;\n\n    setUp(() {\n      repository = Repository();\n    });\n\n    group('fetchItems', () {\n      test('returns list of items', () {\n        final items = List<Item>.generate(\n          10,\n          (index) => Item(id: '$index', value: 'Item $index'),\n        );\n        expect(\n          repository.fetchItems(),\n          completion(equals(items)),\n        );\n      });\n    });\n\n    group('deleteItem', () {\n      test('return null when deleting item', () {\n        expect(\n          repository.deleteItem('2'),\n          completion(equals(null)),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_complex_list/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_complex_list\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_complex_list</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_complex_list/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_complex_list\",\n    \"short_name\": \"flutter_complex_list\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_counter/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_counter/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: android\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_counter/README.md",
    "content": "# flutter_counter\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_counter/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_counter/integration_test/app_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_counter/main.dart' as app;\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:integration_test/integration_test.dart';\n\nvoid main() {\n  IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n\n  group('CounterApp', () {\n    testWidgets('renders correct initial count', (tester) async {\n      await tester.pumpApp();\n\n      expect(find.text('0'), findsOneWidget);\n    });\n\n    testWidgets('tapping increment button updates the count', (tester) async {\n      await tester.pumpApp();\n\n      await tester.incrementCounter();\n      expect(find.text('1'), findsOneWidget);\n\n      await tester.incrementCounter();\n      expect(find.text('2'), findsOneWidget);\n\n      await tester.incrementCounter();\n      expect(find.text('3'), findsOneWidget);\n    });\n\n    testWidgets('tapping decrement button updates the count', (tester) async {\n      await tester.pumpApp();\n\n      await tester.decrementCounter();\n      expect(find.text('-1'), findsOneWidget);\n\n      await tester.decrementCounter();\n      expect(find.text('-2'), findsOneWidget);\n\n      await tester.decrementCounter();\n      expect(find.text('-3'), findsOneWidget);\n    });\n  });\n}\n\nextension on WidgetTester {\n  Future<void> pumpApp() async {\n    app.main();\n    await pumpAndSettle();\n  }\n\n  Future<void> incrementCounter() async {\n    await tap(\n      find.byKey(const Key('counterView_increment_floatingActionButton')),\n    );\n    await pump();\n  }\n\n  Future<void> decrementCounter() async {\n    await tap(\n      find.byKey(const Key('counterView_decrement_floatingActionButton')),\n    );\n    await pump();\n  }\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_counter/counter/counter.dart';\n\n/// {@template counter_app}\n/// A [MaterialApp] which sets the `home` to [CounterPage].\n/// {@endtemplate}\nclass CounterApp extends MaterialApp {\n  /// {@macro counter_app}\n  const CounterApp({super.key}) : super(home: const CounterPage());\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter/counter.dart",
    "content": "export 'cubit/counter_cubit.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter/cubit/counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\n/// {@template counter_cubit}\n/// A [Cubit] which manages an [int] as its state.\n/// {@endtemplate}\nclass CounterCubit extends Cubit<int> {\n  /// {@macro counter_cubit}\n  CounterCubit() : super(0);\n\n  /// Add 1 to the current state.\n  void increment() => emit(state + 1);\n\n  /// Subtract 1 from the current state.\n  void decrement() => emit(state - 1);\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter/view/counter_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_counter/counter/counter.dart';\n\n/// {@template counter_page}\n/// A [StatelessWidget] which is responsible for providing a\n/// [CounterCubit] instance to the [CounterView].\n/// {@endtemplate}\nclass CounterPage extends StatelessWidget {\n  /// {@macro counter_page}\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => CounterCubit(),\n      child: const CounterView(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter/view/counter_view.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_counter/counter/counter.dart';\n\n/// {@template counter_view}\n/// A [StatelessWidget] which reacts to the provided\n/// [CounterCubit] state and notifies it in response to user input.\n/// {@endtemplate}\nclass CounterView extends StatelessWidget {\n  /// {@macro counter_view}\n  const CounterView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    return Scaffold(\n      body: Center(\n        child: BlocBuilder<CounterCubit, int>(\n          builder: (context, state) {\n            return Text('$state', style: textTheme.displayMedium);\n          },\n        ),\n      ),\n      floatingActionButton: Column(\n        mainAxisAlignment: MainAxisAlignment.end,\n        crossAxisAlignment: CrossAxisAlignment.end,\n        children: <Widget>[\n          FloatingActionButton(\n            key: const Key('counterView_increment_floatingActionButton'),\n            child: const Icon(Icons.add),\n            onPressed: () => context.read<CounterCubit>().increment(),\n          ),\n          const SizedBox(height: 8),\n          FloatingActionButton(\n            key: const Key('counterView_decrement_floatingActionButton'),\n            child: const Icon(Icons.remove),\n            onPressed: () => context.read<CounterCubit>().decrement(),\n          ),\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter/view/view.dart",
    "content": "export 'counter_page.dart';\nexport 'counter_view.dart';\n"
  },
  {
    "path": "examples/flutter_counter/lib/counter_observer.dart",
    "content": "import 'package:bloc/bloc.dart';\n\n/// {@template counter_observer}\n/// [BlocObserver] for the counter application which\n/// observes all state changes.\n/// {@endtemplate}\nclass CounterObserver extends BlocObserver {\n  /// {@macro counter_observer}\n  const CounterObserver();\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    // ignore: avoid_print\n    print('${bloc.runtimeType} $change');\n  }\n}\n"
  },
  {
    "path": "examples/flutter_counter/lib/main.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_counter/app.dart';\nimport 'package:flutter_counter/counter_observer.dart';\n\nvoid main() {\n  Bloc.observer = const CounterObserver();\n  runApp(const CounterApp());\n}\n"
  },
  {
    "path": "examples/flutter_counter/pubspec.yaml",
    "content": "name: flutter_counter\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  integration_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_counter/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_counter/test/app_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter/material.dart';\nimport 'package:flutter_counter/app.dart';\nimport 'package:flutter_counter/counter/counter.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(CounterApp, () {\n    testWidgets('is a $MaterialApp', (tester) async {\n      expect(CounterApp(), isA<MaterialApp>());\n    });\n\n    testWidgets('home is $CounterPage', (tester) async {\n      expect(CounterApp().home, isA<CounterPage>());\n    });\n\n    testWidgets('renders $CounterPage', (tester) async {\n      await tester.pumpWidget(CounterApp());\n      expect(find.byType(CounterPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_counter/test/counter/cubit/counter_cubit_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_counter/counter/counter.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(CounterCubit, () {\n    test('initial state is 0', () {\n      expect(CounterCubit().state, 0);\n    });\n\n    group('increment', () {\n      blocTest<CounterCubit, int>(\n        'emits [1] when state is 0',\n        build: CounterCubit.new,\n        act: (cubit) => cubit.increment(),\n        expect: () => const <int>[1],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [1, 2] when state is 0 and invoked twice',\n        build: CounterCubit.new,\n        act: (cubit) => cubit\n          ..increment()\n          ..increment(),\n        expect: () => const <int>[1, 2],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [42] when state is 41',\n        build: CounterCubit.new,\n        seed: () => 41,\n        act: (cubit) => cubit.increment(),\n        expect: () => const <int>[42],\n      );\n    });\n\n    group('decrement', () {\n      blocTest<CounterCubit, int>(\n        'emits [-1] when state is 0',\n        build: CounterCubit.new,\n        act: (cubit) => cubit.decrement(),\n        expect: () => const <int>[-1],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [-1, -2] when state is 0 and invoked twice',\n        build: CounterCubit.new,\n        act: (cubit) => cubit\n          ..decrement()\n          ..decrement(),\n        expect: () => const <int>[-1, -2],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [42] when state is 43',\n        build: CounterCubit.new,\n        seed: () => 43,\n        act: (cubit) => cubit.decrement(),\n        expect: () => const <int>[42],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_counter/test/counter/view/counter_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter/material.dart';\nimport 'package:flutter_counter/counter/counter.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group(CounterPage, () {\n    testWidgets('renders $CounterView', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: CounterPage()));\n      expect(find.byType(CounterView), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_counter/test/counter/view/counter_view_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_counter/counter/counter.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n\nconst _incrementButtonKey = Key('counterView_increment_floatingActionButton');\nconst _decrementButtonKey = Key('counterView_decrement_floatingActionButton');\n\nvoid main() {\n  late CounterCubit counterCubit;\n\n  setUp(() {\n    counterCubit = _MockCounterCubit();\n  });\n\n  group(CounterView, () {\n    testWidgets('renders current $CounterCubit state', (tester) async {\n      when(() => counterCubit.state).thenReturn(42);\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocProvider.value(\n            value: counterCubit,\n            child: CounterView(),\n          ),\n        ),\n      );\n      expect(find.text('42'), findsOneWidget);\n    });\n\n    testWidgets('tapping increment button invokes increment', (tester) async {\n      when(() => counterCubit.state).thenReturn(0);\n      when(() => counterCubit.increment()).thenReturn(null);\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocProvider.value(\n            value: counterCubit,\n            child: CounterView(),\n          ),\n        ),\n      );\n      await tester.tap(find.byKey(_incrementButtonKey));\n      verify(() => counterCubit.increment()).called(1);\n    });\n\n    testWidgets('tapping decrement button invokes decrement', (tester) async {\n      when(() => counterCubit.state).thenReturn(0);\n      when(() => counterCubit.decrement()).thenReturn(null);\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocProvider.value(\n            value: counterCubit,\n            child: CounterView(),\n          ),\n        ),\n      );\n      final decrementFinder = find.byKey(_decrementButtonKey);\n      await tester.ensureVisible(decrementFinder);\n      await tester.tap(decrementFinder);\n      verify(() => counterCubit.decrement()).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_counter/test_driver/integration_test.dart",
    "content": "import 'package:integration_test/integration_test_driver.dart';\n\nFuture<void> main() => integrationDriver();\n"
  },
  {
    "path": "examples/flutter_counter/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_counter\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_counter</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_counter/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_counter\",\n    \"short_name\": \"flutter_counter\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_dynamic_form/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_dynamic_form/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_dynamic_form\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_dynamic_form/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_dynamic_form/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\n\nclass MyApp extends StatelessWidget {\n  const MyApp({required this.newCarRepository, super.key});\n\n  final NewCarRepository newCarRepository;\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider.value(\n      value: newCarRepository,\n      child: const MaterialApp(home: NewCarPage()),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/main.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_dynamic_form/app.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\n\nvoid main() => runApp(MyApp(newCarRepository: NewCarRepository()));\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car/bloc/new_car_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\n\npart 'new_car_event.dart';\npart 'new_car_state.dart';\n\nclass NewCarBloc extends Bloc<NewCarEvent, NewCarState> {\n  NewCarBloc({required NewCarRepository newCarRepository})\n    : _newCarRepository = newCarRepository,\n      super(const NewCarState.initial()) {\n    on<NewCarEvent>(_onEvent, transformer: sequential());\n  }\n\n  final NewCarRepository _newCarRepository;\n\n  Future<void> _onEvent(NewCarEvent event, Emitter<NewCarState> emit) async {\n    return switch (event) {\n      final NewCarFormLoaded e => _onNewCarFormLoaded(e, emit),\n      final NewCarBrandChanged e => _onNewCarBrandChanged(e, emit),\n      final NewCarModelChanged e => _onNewCarModelChanged(e, emit),\n      final NewCarYearChanged e => _onNewCarYearChanged(e, emit),\n    };\n  }\n\n  Future<void> _onNewCarFormLoaded(\n    NewCarFormLoaded event,\n    Emitter<NewCarState> emit,\n  ) async {\n    emit(const NewCarState.brandsLoadInProgress());\n    final brands = await _newCarRepository.fetchBrands();\n    emit(NewCarState.brandsLoadSuccess(brands: brands));\n  }\n\n  Future<void> _onNewCarBrandChanged(\n    NewCarBrandChanged event,\n    Emitter<NewCarState> emit,\n  ) async {\n    emit(\n      NewCarState.modelsLoadInProgress(\n        brands: state.brands,\n        brand: event.brand,\n      ),\n    );\n    final models = await _newCarRepository.fetchModels(brand: event.brand);\n    emit(\n      NewCarState.modelsLoadSuccess(\n        brands: state.brands,\n        brand: event.brand,\n        models: models,\n      ),\n    );\n  }\n\n  Future<void> _onNewCarModelChanged(\n    NewCarModelChanged event,\n    Emitter<NewCarState> emit,\n  ) async {\n    emit(\n      NewCarState.yearsLoadInProgress(\n        brands: state.brands,\n        brand: state.brand,\n        models: state.models,\n        model: event.model,\n      ),\n    );\n    final years = await _newCarRepository.fetchYears(\n      brand: state.brand,\n      model: event.model,\n    );\n    emit(\n      NewCarState.yearsLoadSuccess(\n        brands: state.brands,\n        brand: state.brand,\n        models: state.models,\n        model: event.model,\n        years: years,\n      ),\n    );\n  }\n\n  Future<void> _onNewCarYearChanged(\n    NewCarYearChanged event,\n    Emitter<NewCarState> emit,\n  ) async {\n    emit(state.copyWith(year: event.year));\n  }\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car/bloc/new_car_event.dart",
    "content": "part of 'new_car_bloc.dart';\n\nsealed class NewCarEvent extends Equatable {\n  const NewCarEvent();\n\n  @override\n  List<Object?> get props => [];\n}\n\nfinal class NewCarFormLoaded extends NewCarEvent {\n  const NewCarFormLoaded();\n}\n\nfinal class NewCarBrandChanged extends NewCarEvent {\n  const NewCarBrandChanged({this.brand});\n\n  final String? brand;\n\n  @override\n  List<Object?> get props => [brand];\n}\n\nfinal class NewCarModelChanged extends NewCarEvent {\n  const NewCarModelChanged({this.model});\n\n  final String? model;\n\n  @override\n  List<Object?> get props => [model];\n}\n\nfinal class NewCarYearChanged extends NewCarEvent {\n  const NewCarYearChanged({this.year});\n\n  final String? year;\n\n  @override\n  List<Object?> get props => [year];\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car/bloc/new_car_state.dart",
    "content": "part of 'new_car_bloc.dart';\n\nfinal class NewCarState extends Equatable {\n  const NewCarState._({\n    this.brands = const <String>[],\n    this.brand,\n    this.models = const <String>[],\n    this.model,\n    this.years = const <String>[],\n    this.year,\n  });\n\n  const NewCarState.initial() : this._();\n\n  const NewCarState.brandsLoadInProgress() : this._();\n\n  const NewCarState.brandsLoadSuccess({required List<String> brands})\n    : this._(brands: brands);\n\n  const NewCarState.modelsLoadInProgress({\n    required List<String> brands,\n    String? brand,\n  }) : this._(brands: brands, brand: brand);\n\n  const NewCarState.modelsLoadSuccess({\n    required List<String> brands,\n    required String? brand,\n    required List<String> models,\n  }) : this._(brands: brands, brand: brand, models: models);\n\n  const NewCarState.yearsLoadInProgress({\n    required List<String> brands,\n    required String? brand,\n    required List<String> models,\n    required String? model,\n  }) : this._(brands: brands, brand: brand, models: models, model: model);\n\n  const NewCarState.yearsLoadSuccess({\n    required List<String> brands,\n    required String? brand,\n    required List<String> models,\n    required String? model,\n    required List<String> years,\n  }) : this._(\n         brands: brands,\n         brand: brand,\n         models: models,\n         model: model,\n         years: years,\n       );\n\n  NewCarState copyWith({\n    List<String>? brands,\n    String? brand,\n    List<String>? models,\n    String? model,\n    List<String>? years,\n    String? year,\n  }) {\n    return NewCarState._(\n      brands: brands ?? this.brands,\n      brand: brand ?? this.brand,\n      models: models ?? this.models,\n      model: model ?? this.model,\n      years: years ?? this.years,\n      year: year ?? this.year,\n    );\n  }\n\n  final List<String> brands;\n  final String? brand;\n\n  final List<String> models;\n  final String? model;\n\n  final List<String> years;\n  final String? year;\n\n  bool get isComplete => brand != null && model != null && year != null;\n\n  @override\n  List<Object?> get props => [brands, brand, models, model, years, year];\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car/new_car.dart",
    "content": "export 'bloc/new_car_bloc.dart';\nexport 'view/new_car_page.dart';\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car/view/new_car_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\n\nclass NewCarPage extends StatelessWidget {\n  const NewCarPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: BlocProvider(\n        create: (_) => NewCarBloc(\n          newCarRepository: context.read<NewCarRepository>(),\n        )..add(const NewCarFormLoaded()),\n        child: const NewCarForm(),\n      ),\n    );\n  }\n}\n\nclass NewCarForm extends StatelessWidget {\n  const NewCarForm({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return const Align(\n      alignment: Alignment(0, -3 / 4),\n      child: Padding(\n        padding: EdgeInsets.all(8),\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.stretch,\n          mainAxisSize: MainAxisSize.min,\n          children: <Widget>[\n            _BrandDropdownButton(),\n            _ModelDropdownButton(),\n            _YearDropdownButton(),\n            _FormSubmitButton(),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass _BrandDropdownButton extends StatelessWidget {\n  const _BrandDropdownButton();\n\n  @override\n  Widget build(BuildContext context) {\n    final brands = context.select((NewCarBloc bloc) => bloc.state.brands);\n    final brand = context.select((NewCarBloc bloc) => bloc.state.brand);\n    return Material(\n      child: DropdownButton<String>(\n        key: const Key('newCarForm_brand_dropdownButton'),\n        items: brands.isNotEmpty\n            ? brands.map((brand) {\n                return DropdownMenuItem(value: brand, child: Text(brand));\n              }).toList()\n            : const [],\n        value: brand,\n        hint: const Text('Select a Brand'),\n        onChanged: (brand) {\n          context.read<NewCarBloc>().add(NewCarBrandChanged(brand: brand));\n        },\n      ),\n    );\n  }\n}\n\nclass _ModelDropdownButton extends StatelessWidget {\n  const _ModelDropdownButton();\n\n  @override\n  Widget build(BuildContext context) {\n    final models = context.select((NewCarBloc bloc) => bloc.state.models);\n    final model = context.select((NewCarBloc bloc) => bloc.state.model);\n    return Material(\n      child: DropdownButton<String>(\n        key: const Key('newCarForm_model_dropdownButton'),\n        items: models.isNotEmpty\n            ? models.map((model) {\n                return DropdownMenuItem(value: model, child: Text(model));\n              }).toList()\n            : const [],\n        value: model,\n        hint: const Text('Select a Model'),\n        onChanged: (model) {\n          context.read<NewCarBloc>().add(NewCarModelChanged(model: model));\n        },\n      ),\n    );\n  }\n}\n\nclass _YearDropdownButton extends StatelessWidget {\n  const _YearDropdownButton();\n\n  @override\n  Widget build(BuildContext context) {\n    final years = context.select((NewCarBloc bloc) => bloc.state.years);\n    final year = context.select((NewCarBloc bloc) => bloc.state.year);\n    return Material(\n      child: DropdownButton<String>(\n        key: const Key('newCarForm_year_dropdownButton'),\n        items: years.isNotEmpty\n            ? years.map((year) {\n                return DropdownMenuItem(value: year, child: Text(year));\n              }).toList()\n            : const [],\n        value: year,\n        hint: const Text('Select a Year'),\n        onChanged: (year) {\n          context.read<NewCarBloc>().add(NewCarYearChanged(year: year));\n        },\n      ),\n    );\n  }\n}\n\nclass _FormSubmitButton extends StatelessWidget {\n  const _FormSubmitButton();\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<NewCarBloc>().state;\n\n    void onFormSubmitted() {\n      ScaffoldMessenger.of(context)\n        ..hideCurrentSnackBar()\n        ..showSnackBar(\n          SnackBar(\n            content: Text(\n              'Submitted ${state.brand} ${state.model} ${state.year}',\n            ),\n          ),\n        );\n    }\n\n    return ElevatedButton(\n      onPressed: state.isComplete ? onFormSubmitted : null,\n      child: const Text('Submit'),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/lib/new_car_repository.dart",
    "content": "const _delay = Duration(milliseconds: 300);\nFuture<void> wait() => Future.delayed(_delay);\n\nclass NewCarRepository {\n  Future<List<String>> fetchBrands() async {\n    await wait();\n    return ['Chevy', 'Toyota', 'Honda'];\n  }\n\n  Future<List<String>> fetchModels({String? brand}) async {\n    await wait();\n    switch (brand) {\n      case 'Chevy':\n        return ['Malibu', 'Impala'];\n      case 'Toyota':\n        return ['Corolla', 'Supra'];\n      case 'Honda':\n        return ['Civic', 'Accord'];\n      default:\n        return [];\n    }\n  }\n\n  Future<List<String>> fetchYears({String? brand, String? model}) async {\n    await wait();\n    switch (brand) {\n      case 'Chevy':\n        switch (model) {\n          case 'Malibu':\n            return ['2019', '2018'];\n          case 'Impala':\n            return ['2017', '2016'];\n          default:\n            return [];\n        }\n      case 'Toyota':\n        switch (model) {\n          case 'Corolla':\n            return ['2015', '2014'];\n          case 'Supra':\n            return ['2013', '2012'];\n          default:\n            return [];\n        }\n      case 'Honda':\n        switch (model) {\n          case 'Civic':\n            return ['2011', '2010'];\n          case 'Accord':\n            return ['2009', '2008'];\n          default:\n            return [];\n        }\n      default:\n        return [];\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/pubspec.yaml",
    "content": "name: flutter_dynamic_form\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  bloc_concurrency: ^0.3.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_dynamic_form/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/app_test.dart",
    "content": "import 'package:flutter_dynamic_form/app.dart';\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockNewCarRepository extends Mock implements NewCarRepository {}\n\nvoid main() {\n  group('MyApp', () {\n    late NewCarRepository newCarRepository;\n\n    setUp(() {\n      newCarRepository = MockNewCarRepository();\n    });\n\n    testWidgets('renders NewCarPage', (tester) async {\n      when(() => newCarRepository.fetchBrands()).thenAnswer(\n        (_) async => ['honda'],\n      );\n      await tester.pumpWidget(MyApp(newCarRepository: newCarRepository));\n      expect(find.byType(NewCarPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/new_car/bloc/new_car_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockNewCarRepository extends Mock implements NewCarRepository {}\n\nvoid main() {\n  const mockBrands = ['Chevy', 'Toyota', 'Honda'];\n  final mockBrand = mockBrands[0];\n  const mockModels = ['Malibu', 'Impala'];\n  final mockModel = mockModels[0];\n  const mockYears = ['2008', '2020'];\n  final mockYear = mockYears[0];\n\n  group('NewCarBloc', () {\n    late NewCarRepository newCarRepository;\n\n    setUp(() {\n      newCarRepository = MockNewCarRepository();\n    });\n\n    test('initial state is NewCarState.initial', () {\n      expect(\n        NewCarBloc(newCarRepository: newCarRepository).state,\n        const NewCarState.initial(),\n      );\n    });\n\n    blocTest<NewCarBloc, NewCarState>(\n      'emits brands loading in progress and brands load success',\n      setUp: () {\n        when(newCarRepository.fetchBrands).thenAnswer((_) async => mockBrands);\n      },\n      build: () => NewCarBloc(newCarRepository: newCarRepository),\n      act: (bloc) => bloc.add(const NewCarFormLoaded()),\n      expect: () => [\n        const NewCarState.brandsLoadInProgress(),\n        const NewCarState.brandsLoadSuccess(brands: mockBrands),\n      ],\n      verify: (_) => verify(newCarRepository.fetchBrands).called(1),\n    );\n\n    blocTest<NewCarBloc, NewCarState>(\n      'emits models loading in progress and models load success',\n      setUp: () {\n        when(\n          () => newCarRepository.fetchModels(brand: mockBrand),\n        ).thenAnswer((_) async => mockModels);\n      },\n      build: () => NewCarBloc(newCarRepository: newCarRepository),\n      act: (bloc) => bloc.add(NewCarBrandChanged(brand: mockBrand)),\n      expect: () => [\n        NewCarState.modelsLoadInProgress(brands: const [], brand: mockBrand),\n        NewCarState.modelsLoadSuccess(\n          brands: const [],\n          brand: mockBrand,\n          models: mockModels,\n        ),\n      ],\n      verify: (_) {\n        verify(() => newCarRepository.fetchModels(brand: mockBrand)).called(1);\n      },\n    );\n\n    blocTest<NewCarBloc, NewCarState>(\n      'emits years loading in progress and year load success',\n      setUp: () {\n        when(\n          () => newCarRepository.fetchYears(model: mockModel),\n        ).thenAnswer((_) async => mockYears);\n      },\n      build: () => NewCarBloc(newCarRepository: newCarRepository),\n      act: (bloc) => bloc.add(NewCarModelChanged(model: mockModel)),\n      expect: () => [\n        NewCarState.yearsLoadInProgress(\n          brands: const [],\n          brand: null,\n          models: const [],\n          model: mockModel,\n        ),\n        NewCarState.yearsLoadSuccess(\n          brands: const [],\n          brand: null,\n          models: const [],\n          model: mockModel,\n          years: mockYears,\n        ),\n      ],\n      verify: (_) {\n        verify(\n          () => newCarRepository.fetchYears(model: mockModel),\n        ).called(1);\n      },\n    );\n\n    blocTest<NewCarBloc, NewCarState>(\n      'changes year when NewCarYearChanged is added',\n      build: () => NewCarBloc(newCarRepository: newCarRepository),\n      act: (bloc) => bloc.add(NewCarYearChanged(year: mockYear)),\n      expect: () => [const NewCarState.initial().copyWith(year: mockYear)],\n    );\n\n    blocTest<NewCarBloc, NewCarState>(\n      'emits correct states when complete flow is executed',\n      setUp: () {\n        when(\n          newCarRepository.fetchBrands,\n        ).thenAnswer((_) => Future.value(mockBrands));\n        when(\n          () => newCarRepository.fetchModels(brand: mockBrand),\n        ).thenAnswer((_) => Future.value(mockModels));\n        when(\n          () => newCarRepository.fetchYears(brand: mockBrand, model: mockModel),\n        ).thenAnswer((_) => Future.value(mockYears));\n      },\n      build: () => NewCarBloc(newCarRepository: newCarRepository),\n      act: (bloc) => bloc\n        ..add(const NewCarFormLoaded())\n        ..add(NewCarBrandChanged(brand: mockBrand))\n        ..add(NewCarModelChanged(model: mockModel))\n        ..add(NewCarYearChanged(year: mockYear)),\n      expect: () => [\n        const NewCarState.brandsLoadInProgress(),\n        const NewCarState.brandsLoadSuccess(brands: mockBrands),\n        NewCarState.modelsLoadInProgress(brands: mockBrands, brand: mockBrand),\n        NewCarState.modelsLoadSuccess(\n          brands: mockBrands,\n          brand: mockBrand,\n          models: mockModels,\n        ),\n        NewCarState.yearsLoadInProgress(\n          brands: mockBrands,\n          brand: mockBrand,\n          models: mockModels,\n          model: mockModel,\n        ),\n        NewCarState.yearsLoadSuccess(\n          brands: mockBrands,\n          brand: mockBrand,\n          models: mockModels,\n          model: mockModel,\n          years: mockYears,\n        ),\n        NewCarState.yearsLoadSuccess(\n          brands: mockBrands,\n          brand: mockBrand,\n          models: mockModels,\n          model: mockModel,\n          years: mockYears,\n        ).copyWith(year: mockYear),\n      ],\n      verify: (_) => verifyInOrder([\n        newCarRepository.fetchBrands,\n        () => newCarRepository.fetchModels(brand: mockBrand),\n        () => newCarRepository.fetchYears(brand: mockBrand, model: mockModel),\n      ]),\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/new_car/bloc/new_car_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('NewCarEvent', () {\n    group('NewCarFormLoaded', () {\n      test('supports value comparison', () {\n        expect(NewCarFormLoaded(), NewCarFormLoaded());\n      });\n    });\n\n    group('NewCarBrandChanged', () {\n      const mockCarBrand = 'Chevy';\n      test('supports value comparison', () {\n        expect(NewCarBrandChanged(), NewCarBrandChanged());\n        expect(\n          NewCarBrandChanged(brand: mockCarBrand),\n          NewCarBrandChanged(brand: mockCarBrand),\n        );\n      });\n    });\n\n    group('NewCarModelChanged', () {\n      const mockCarModel = 'Malibu';\n      test('supports value comparison', () {\n        expect(NewCarModelChanged(), NewCarModelChanged());\n        expect(\n          NewCarModelChanged(model: mockCarModel),\n          NewCarModelChanged(model: mockCarModel),\n        );\n      });\n    });\n\n    group('NewCarYearChanged', () {\n      const mockYear = '2021';\n      test('supports value comparison', () {\n        expect(NewCarYearChanged(), NewCarYearChanged());\n        expect(\n          NewCarYearChanged(year: mockYear),\n          NewCarYearChanged(year: mockYear),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/new_car/bloc/new_car_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('NewCarState', () {\n    const mockBrands = ['Chevy', 'Toyota', 'Honda'];\n    final mockBrand = mockBrands[0];\n    const mockModels = ['Malibu', 'Impala'];\n    final mockModel = mockModels[0];\n    const mockYears = ['2008', '2020'];\n\n    group('NewCarState', () {\n      test('supports value comparison', () {\n        expect(NewCarState.initial(), NewCarState.initial());\n        expect(\n          NewCarState.brandsLoadInProgress(),\n          NewCarState.brandsLoadInProgress(),\n        );\n        expect(\n          NewCarState.brandsLoadSuccess(brands: mockBrands),\n          NewCarState.brandsLoadSuccess(brands: mockBrands),\n        );\n        expect(\n          NewCarState.modelsLoadInProgress(brands: mockBrands),\n          NewCarState.modelsLoadInProgress(brands: mockBrands),\n        );\n        expect(\n          NewCarState.modelsLoadSuccess(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n          ),\n          NewCarState.modelsLoadSuccess(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n          ),\n        );\n        expect(\n          NewCarState.yearsLoadInProgress(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n            model: mockModel,\n          ),\n          NewCarState.yearsLoadInProgress(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n            model: mockModel,\n          ),\n        );\n        expect(\n          NewCarState.yearsLoadSuccess(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n            model: mockModel,\n            years: mockYears,\n          ),\n          NewCarState.yearsLoadSuccess(\n            brands: mockBrands,\n            brand: mockBrand,\n            models: mockModels,\n            model: mockModel,\n            years: mockYears,\n          ),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/new_car/view/new_car_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_dynamic_form/new_car/new_car.dart';\nimport 'package:flutter_dynamic_form/new_car_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockNewCarRepository extends Mock implements NewCarRepository {}\n\nclass MockNewCarBloc extends MockBloc<NewCarEvent, NewCarState>\n    implements NewCarBloc {}\n\nextension on WidgetTester {\n  Future<void> pumpNewCarPage(NewCarRepository newCarRepository) {\n    return pumpWidget(\n      MaterialApp(\n        home: Scaffold(\n          body: RepositoryProvider.value(\n            value: newCarRepository,\n            child: const NewCarPage(),\n          ),\n        ),\n      ),\n    );\n  }\n\n  Future<void> pumpNewCarForm(NewCarBloc newCarBloc) {\n    return pumpWidget(\n      MaterialApp(\n        home: Scaffold(\n          body: BlocProvider.value(\n            value: newCarBloc,\n            child: const NewCarForm(),\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  const brandDropdownButtonKey = Key('newCarForm_brand_dropdownButton');\n  const modelDropdownButtonKey = Key('newCarForm_model_dropdownButton');\n  const yearDropdownButtonKey = Key('newCarForm_year_dropdownButton');\n\n  late NewCarRepository newCarRepository;\n  late NewCarBloc newCarBloc;\n\n  const mockBrands = ['Chevy', 'Toyota', 'Honda'];\n  final mockBrand = mockBrands[0];\n  const mockModels = ['Malibu', 'Impala'];\n  final mockModel = mockModels[0];\n  const mockYears = ['2008', '2020'];\n  final mockYear = mockYears[0];\n\n  setUp(() {\n    newCarRepository = MockNewCarRepository();\n    newCarBloc = MockNewCarBloc();\n  });\n\n  tearDown(resetMocktailState);\n\n  group('NewCarPage', () {\n    testWidgets('renders NewCarForm', (tester) async {\n      when(() => newCarRepository.fetchBrands()).thenAnswer(\n        (_) async => ['honda'],\n      );\n      await tester.pumpNewCarPage(newCarRepository);\n      expect(find.byType(NewCarForm), findsOneWidget);\n      verify(() => newCarRepository.fetchBrands()).called(1);\n    });\n  });\n\n  group('NewCarForm', () {\n    testWidgets('displays SnackBar after a submission', (tester) async {\n      when(() => newCarBloc.state).thenReturn(\n        const NewCarState.initial().copyWith(\n          brand: mockBrand,\n          model: mockModel,\n          year: mockYear,\n        ),\n      );\n      await tester.pumpNewCarForm(newCarBloc);\n      await tester.tap(find.byType(ElevatedButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SnackBar), findsOneWidget);\n      expect(\n        find.text('Submitted $mockBrand $mockModel $mockYear'),\n        findsOneWidget,\n      );\n    });\n\n    testWidgets('cannot submit blank form', (tester) async {\n      when(() => newCarBloc.state).thenReturn(const NewCarState.initial());\n      await tester.pumpNewCarForm(newCarBloc);\n      await tester.tap(find.byType(ElevatedButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SnackBar), findsNothing);\n    });\n\n    testWidgets('can select a brand via DropdownButton', (tester) async {\n      when(() => newCarBloc.state).thenReturn(\n        const NewCarState.initial().copyWith(brands: mockBrands),\n      );\n      await tester.pumpNewCarForm(newCarBloc..add(const NewCarFormLoaded()));\n      await tester.tap(find.byKey(brandDropdownButtonKey));\n      await tester.pumpAndSettle();\n      await tester.tap(find.text(mockBrand).last);\n      verify(\n        () => newCarBloc.add(NewCarBrandChanged(brand: mockBrand)),\n      ).called(1);\n    });\n\n    testWidgets('can select a model via DropdownButton', (tester) async {\n      when(() => newCarBloc.state).thenReturn(\n        const NewCarState.initial().copyWith(models: mockModels),\n      );\n      await tester.pumpNewCarForm(newCarBloc);\n      await tester.tap(find.byKey(modelDropdownButtonKey));\n      await tester.pumpAndSettle();\n      await tester.tap(find.text(mockModel).last);\n      verify(\n        () => newCarBloc.add(NewCarModelChanged(model: mockModel)),\n      ).called(1);\n    });\n\n    testWidgets('can select a year via DropdownButton', (tester) async {\n      when(() => newCarBloc.state).thenReturn(\n        const NewCarState.initial().copyWith(years: mockYears),\n      );\n      await tester.pumpNewCarForm(newCarBloc);\n      await tester.tap(find.byKey(yearDropdownButtonKey));\n      await tester.pumpAndSettle();\n      await tester.tap(find.text(mockYear).last);\n      verify(() => newCarBloc.add(NewCarYearChanged(year: mockYear))).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/test/new_car_repository_test.dart",
    "content": "import 'package:flutter_dynamic_form/new_car_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('NewCarRepository', () {\n    late NewCarRepository newCarRepository;\n\n    setUp(() {\n      newCarRepository = NewCarRepository();\n    });\n\n    group('fetchBrands', () {\n      test('returns car brands', () async {\n        expect(\n          newCarRepository.fetchBrands(),\n          completion(\n            equals(['Chevy', 'Toyota', 'Honda']),\n          ),\n        );\n      });\n    });\n\n    group('fetchModels', () {\n      test('returns Chevy models', () async {\n        expect(\n          newCarRepository.fetchModels(brand: 'Chevy'),\n          completion(equals(['Malibu', 'Impala'])),\n        );\n      });\n      test('returns Toyota models', () async {\n        expect(\n          newCarRepository.fetchModels(brand: 'Toyota'),\n          completion(equals(['Corolla', 'Supra'])),\n        );\n      });\n      test('returns Honda models', () async {\n        expect(\n          newCarRepository.fetchModels(brand: 'Honda'),\n          completion(equals(['Civic', 'Accord'])),\n        );\n      });\n      test('returns no models', () async {\n        expect(\n          newCarRepository.fetchModels(brand: 'Fake-Brand'),\n          completion(equals(<String>[])),\n        );\n      });\n    });\n\n    group('fetchYears', () {\n      group('Chevy brand', () {\n        const brand = 'Chevy';\n        test('returns years on Malibu model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Malibu'),\n            completion(equals(['2019', '2018'])),\n          );\n        });\n        test('returns years on Impala model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Impala'),\n            completion(equals(['2017', '2016'])),\n          );\n        });\n        test('returns no years on non-existent model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Fake-Model'),\n            completion(equals(<String>[])),\n          );\n        });\n      });\n\n      group('Toyota brand', () {\n        const brand = 'Toyota';\n        test('returns years on Corolla model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Corolla'),\n            completion(equals(['2015', '2014'])),\n          );\n        });\n        test('returns years on Supra model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Supra'),\n            completion(equals(['2013', '2012'])),\n          );\n        });\n        test('returns no years on non-existent model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Fake-Model'),\n            completion(equals(<String>[])),\n          );\n        });\n      });\n\n      group('Honda brand', () {\n        const brand = 'Honda';\n        test('returns years on Civic model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Civic'),\n            completion(equals(['2011', '2010'])),\n          );\n        });\n        test('returns years on Accord model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Accord'),\n            completion(equals(['2009', '2008'])),\n          );\n        });\n        test('returns no years on non-existent model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Fake-Model'),\n            completion(equals(<String>[])),\n          );\n        });\n      });\n      group('No brand', () {\n        const brand = 'Fake-Brand';\n        test('returns no years on non-existent model', () {\n          expect(\n            newCarRepository.fetchYears(brand: brand, model: 'Fake-Model'),\n            completion(equals(<String>[])),\n          );\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_dynamic_form/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_dynamic_form\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_dynamic_form</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_dynamic_form/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_dynamic_form\",\n    \"short_name\": \"flutter_dynamic_form\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_firebase_login/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"d693b4b9dbac2acd4477aea4555ca6dcbea44ba2\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2\n      base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2\n    - platform: android\n      create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2\n      base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_firebase_login/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_firebase_login\n\nExample Flutter app built with `flutter_bloc` to implement login using Firebase.\n\n## Features\n\n- Sign in with Google\n- Sign up with email and password\n- Sign in with email and password\n\n## Getting Started\n\n### Firebase\n\n1. Create your project\n2. Enable desired authentication options\n\n### iOS\n\n1. Replace `./ios/Runner/GoogleService-Info.plist` with your own\n2. Update `./ios/Runner/info.plist`\n   - Paste the `REVERSED_CLIENT_ID` from `GoogleService-Info.plist` to key `CFBundleURLSchemes` in `info.plist`\n\n### Android\n\n1. Replace `./android/app/google-services.json` with your own\n2. Update `./android/app/build.gradle`\n   - Replace `\"com.example.flutter_firebase_login\"` with the `package_name` from `google-services.json`\n\n### Run the project\n\n1. `flutter run`\n"
  },
  {
    "path": "examples/flutter_firebase_login/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/.gitignore",
    "content": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remember to never publicly share your keystore.\n# See https://flutter.dev/to/reference-keystore\nkey.properties\n**/*.keystore\n**/*.jks\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n    id(\"com.google.gms.google-services\")\n    id(\"kotlin-android\")\n    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.\n    id(\"dev.flutter.flutter-gradle-plugin\")\n}\n\nandroid {\n    namespace = \"com.example.flutter_firebase_login\"\n    compileSdk = flutter.compileSdkVersion\n    ndkVersion = flutter.ndkVersion\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_11\n        targetCompatibility = JavaVersion.VERSION_11\n    }\n\n    kotlinOptions {\n        jvmTarget = JavaVersion.VERSION_11.toString()\n    }\n\n    defaultConfig {\n        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).\n        applicationId = \"com.example.flutter_firebase_login\"\n        // You can update the following values to match your application needs.\n        // For more information, see: https://flutter.dev/to/review-gradle-config.\n        minSdk = flutter.minSdkVersion\n        targetSdk = flutter.targetSdkVersion\n        versionCode = flutter.versionCode\n        versionName = flutter.versionName\n    }\n\n    buildTypes {\n        release {\n            // TODO: Add your own signing config for the release build.\n            // Signing with the debug keys for now, so `flutter run --release` works.\n            signingConfig = signingConfigs.getByName(\"debug\")\n        }\n    }\n}\n\nflutter {\n    source = \"../..\"\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"979633879366\",\n    \"firebase_url\": \"https://flutter-firebase-auth-2b1c3.firebaseio.com\",\n    \"project_id\": \"flutter-firebase-auth-2b1c3\",\n    \"storage_bucket\": \"flutter-firebase-auth-2b1c3.appspot.com\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:979633879366:android:91dc73f56b484d46d98d47\",\n        \"android_client_info\": {\n          \"package_name\": \"com.example.flutter_firebase_login\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"979633879366-5f9f4h5o9bf741b22ahggarebl9iiktq.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"com.example.flutter_firebase_login\",\n            \"certificate_hash\": \"d16ccecc133cd0d68b93ed0d06b0ff98cf49de9a\"\n          }\n        },\n        {\n          \"client_id\": \"979633879366-lucrjde7auulec5l2pouo6aa34u4576d.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyCUqu6Uggf0FRSPljkCT8GNlgPO9OzSa2g\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"979633879366-lucrjde7auulec5l2pouo6aa34u4576d.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"979633879366-7fh9utsod6e5pfjh2l01fh6dk2chjc8d.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.bloclibrary.flutterFirebaseLogin\"\n              }\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:979633879366:android:cd6c9969cbe46460\",\n        \"android_client_info\": {\n          \"package_name\": \"com.example.flutterfirebaselogin\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"979633879366-lucrjde7auulec5l2pouo6aa34u4576d.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyCUqu6Uggf0FRSPljkCT8GNlgPO9OzSa2g\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"979633879366-lucrjde7auulec5l2pouo6aa34u4576d.apps.googleusercontent.com\",\n              \"client_type\": 3\n            },\n            {\n              \"client_id\": \"979633879366-7fh9utsod6e5pfjh2l01fh6dk2chjc8d.apps.googleusercontent.com\",\n              \"client_type\": 2,\n              \"ios_info\": {\n                \"bundle_id\": \"com.bloclibrary.flutterFirebaseLogin\"\n              }\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/debug/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- The INTERNET permission is required for development. Specifically,\n         the Flutter tool needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <application android:label=\"flutter_firebase_login\" android:name=\"${applicationName}\" android:icon=\"@mipmap/ic_launcher\">\n    <activity android:name=\".MainActivity\" android:exported=\"true\" android:launchMode=\"singleTop\" android:taskAffinity=\"\" android:theme=\"@style/LaunchTheme\" android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode\" android:hardwareAccelerated=\"true\" android:windowSoftInputMode=\"adjustResize\">\n      <!-- Specifies an Android theme to apply to this Activity as soon as\n                 the Android process has started. This theme is visible to the user\n                 while the Flutter UI initializes. After that, this theme continues\n                 to determine the Window background behind the Flutter UI. -->\n      <meta-data android:name=\"io.flutter.embedding.android.NormalTheme\" android:resource=\"@style/NormalTheme\"/>\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\"/>\n        <category android:name=\"android.intent.category.LAUNCHER\"/>\n      </intent-filter>\n    </activity>\n    <!-- Don't delete the meta-data below.\n             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->\n    <meta-data android:name=\"flutterEmbedding\" android:value=\"2\"/>\n  </application>\n  <!-- Required to query activities that can process text, see:\n         https://developer.android.com/training/package-visibility and\n         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.\n\n         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->\n  <queries>\n    <intent>\n      <action android:name=\"android.intent.action.PROCESS_TEXT\"/>\n      <data android:mimeType=\"text/plain\"/>\n    </intent>\n  </queries>\n  <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/kotlin/com/example/flutter_firebase_login/MainActivity.kt",
    "content": "package com.example.flutter_firebase_login\n\nimport io.flutter.embedding.android.FlutterActivity\n\nclass MainActivity: FlutterActivity()\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/res/drawable/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@android:color/white\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/res/drawable-v21/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"?android:colorBackground\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Light.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             the Flutter engine draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n    <!-- Theme applied to the Android Window as soon as the process has started.\n         This theme determines the color of the Android Window while your\n         Flutter UI initializes, as well as behind your Flutter UI while its\n         running.\n\n         This Theme is only used starting with V2 of Flutter's Android embedding. -->\n    <style name=\"NormalTheme\" parent=\"@android:style/Theme.Light.NoTitleBar\">\n        <item name=\"android:windowBackground\">?android:colorBackground</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/main/res/values-night/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             the Flutter engine draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n    <!-- Theme applied to the Android Window as soon as the process has started.\n         This theme determines the color of the Android Window while your\n         Flutter UI initializes, as well as behind your Flutter UI while its\n         running.\n\n         This Theme is only used starting with V2 of Flutter's Android embedding. -->\n    <style name=\"NormalTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <item name=\"android:windowBackground\">?android:colorBackground</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/app/src/profile/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- The INTERNET permission is required for development. Specifically,\n         the Flutter tool needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/build.gradle.kts",
    "content": "allprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nval newBuildDir: Directory =\n    rootProject.layout.buildDirectory\n        .dir(\"../../build\")\n        .get()\nrootProject.layout.buildDirectory.value(newBuildDir)\n\nsubprojects {\n    val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)\n    project.layout.buildDirectory.value(newSubprojectBuildDir)\n}\nsubprojects {\n    project.evaluationDependsOn(\":app\")\n}\n\ntasks.register<Delete>(\"clean\") {\n    delete(rootProject.layout.buildDirectory)\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.12-all.zip"
  },
  {
    "path": "examples/flutter_firebase_login/android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError\nandroid.useAndroidX=true\nandroid.enableJetifier=true"
  },
  {
    "path": "examples/flutter_firebase_login/android/settings.gradle.kts",
    "content": "pluginManagement {\n    val flutterSdkPath =\n        run {\n            val properties = java.util.Properties()\n            file(\"local.properties\").inputStream().use { properties.load(it) }\n            val flutterSdkPath = properties.getProperty(\"flutter.sdk\")\n            require(flutterSdkPath != null) { \"flutter.sdk not set in local.properties\" }\n            flutterSdkPath\n        }\n\n    includeBuild(\"$flutterSdkPath/packages/flutter_tools/gradle\")\n\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\nplugins {\n    id(\"dev.flutter.flutter-plugin-loader\") version \"1.0.0\"\n    id(\"com.android.application\") version \"8.9.1\" apply false\n    id (\"com.google.gms.google-services\") version \"4.3.15\" apply false\n    id(\"org.jetbrains.kotlin.android\") version \"2.1.0\" apply false\n}\n\ninclude(\":app\")\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Flutter/AppFrameworkInfo.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>en</string>\n  <key>CFBundleExecutable</key>\n  <string>App</string>\n  <key>CFBundleIdentifier</key>\n  <string>io.flutter.flutter.app</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundleName</key>\n  <string>App</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0</string>\n  <key>MinimumOSVersion</key>\n  <string>13.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Flutter/Debug.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Flutter/Release.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '12.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/AppDelegate.swift",
    "content": "import UIKit\nimport Flutter\n\n@main\n@objc class AppDelegate: FlutterAppDelegate {\n  override func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n  ) -> Bool {\n    GeneratedPluginRegistrant.register(with: self)\n    return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"83.5x83.5\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-83.5x83.5@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"1024x1024\",\n      \"idiom\" : \"ios-marketing\",\n      \"filename\" : \"Icon-App-1024x1024@1x.png\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@3x.png\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "content": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images."
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"12121\" systemVersion=\"16G29\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"12089\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"Ydg-fD-yQy\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"xbc-2k-c8Z\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView opaque=\"NO\" clipsSubviews=\"YES\" multipleTouchEnabled=\"YES\" contentMode=\"center\" image=\"LaunchImage\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"YRO-k0-Ey4\">\n                            </imageView>\n                        </subviews>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                        <constraints>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"1a2-6s-vTC\"/>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"4X2-HB-R7a\"/>\n                        </constraints>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"LaunchImage\" width=\"168\" height=\"185\"/>\n    </resources>\n</document>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"10117\" systemVersion=\"15F34\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"10085\"/>\n    </dependencies>\n    <scenes>\n        <!--Flutter View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"FlutterViewController\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"y3c-jy-aDJ\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"wfy-db-euE\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"600\" height=\"600\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"calibratedWhite\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/GoogleService-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CLIENT_ID</key>\n\t<string>979633879366-ctkb3hh1kse9ljd4c3lhs9djigaukmu2.apps.googleusercontent.com</string>\n\t<key>REVERSED_CLIENT_ID</key>\n\t<string>com.googleusercontent.apps.979633879366-ctkb3hh1kse9ljd4c3lhs9djigaukmu2</string>\n\t<key>API_KEY</key>\n\t<string>AIzaSyDVqdUKRfDx2AJbcbGp9MDMq_GdYqlOBXA</string>\n\t<key>GCM_SENDER_ID</key>\n\t<string>979633879366</string>\n\t<key>PLIST_VERSION</key>\n\t<string>1</string>\n\t<key>BUNDLE_ID</key>\n\t<string>com.example.flutterFirebaseLogin</string>\n\t<key>PROJECT_ID</key>\n\t<string>flutter-firebase-auth-2b1c3</string>\n\t<key>STORAGE_BUCKET</key>\n\t<string>flutter-firebase-auth-2b1c3.appspot.com</string>\n\t<key>IS_ADS_ENABLED</key>\n\t<false></false>\n\t<key>IS_ANALYTICS_ENABLED</key>\n\t<false></false>\n\t<key>IS_APPINVITE_ENABLED</key>\n\t<true></true>\n\t<key>IS_GCM_ENABLED</key>\n\t<true></true>\n\t<key>IS_SIGNIN_ENABLED</key>\n\t<true></true>\n\t<key>GOOGLE_APP_ID</key>\n\t<string>1:979633879366:ios:45c728c386ca2b9bd98d47</string>\n\t<key>DATABASE_URL</key>\n\t<string>https://flutter-firebase-auth-2b1c3.firebaseio.com</string>\n</dict>\n</plist>"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>Flutter Firebase Login</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>flutter_firebase_login</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(FLUTTER_BUILD_NAME)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>com.googleusercontent.apps.979633879366-ctkb3hh1kse9ljd4c3lhs9djigaukmu2</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>$(FLUTTER_BUILD_NUMBER)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n\t<key>CADisableMinimumFrameDurationOnPhone</key>\n\t<true/>\n\t<key>UIApplicationSupportsIndirectInputEvents</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner/Runner-Bridging-Header.h",
    "content": "#import \"GeneratedPluginRegistrant.h\"\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t101895F97E16CE730BB23D30 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7D9CF32C2591DC1A9362B866 /* GoogleService-Info.plist */; };\n\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };\n\t\t331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };\n\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };\n\t\t6B35C96F2FDC4A10A2082240 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F907645AC58FE1557878117 /* Pods_RunnerTests.framework */; };\n\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };\n\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };\n\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };\n\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };\n\t\tF583EFC4D4CF51F4978597AC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F570C26B5F42E37708B7BD /* Pods_Runner.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 97C146E61CF9000F007C117D /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 97C146ED1CF9000F007C117D;\n\t\t\tremoteInfo = Runner;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t9705A1C41CF9048500538489 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = \"<group>\"; };\n\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = \"<group>\"; };\n\t\t15F570C26B5F42E37708B7BD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t272CEB1E0934A2ADD5D44C56 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.debug.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = \"<group>\"; };\n\t\t331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t36211D31DD83A06C480B7933 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-RunnerTests.debug.xcconfig\"; path = \"Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t3810D845A293D96D307AF84D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-RunnerTests.profile.xcconfig\"; path = \"Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = \"<group>\"; };\n\t\t47257ACFBA77E1966ABB77F1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.profile.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"Runner-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = \"<group>\"; };\n\t\t7D9CF32C2591DC1A9362B866 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = \"GoogleService-Info.plist\"; path = \"Runner/GoogleService-Info.plist\"; sourceTree = \"<group>\"; };\n\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = \"<group>\"; };\n\t\t97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t9F907645AC58FE1557878117 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB2D2646CC85C5457B8D0C536 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.release.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tE6B39746540F83CD989EDC9B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-RunnerTests.release.xcconfig\"; path = \"Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t7BF2C52BF829726895EFB865 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6B35C96F2FDC4A10A2082240 /* Pods_RunnerTests.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t97C146EB1CF9000F007C117D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tF583EFC4D4CF51F4978597AC /* Pods_Runner.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t331C8082294A63A400263BE5 /* RunnerTests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t331C807B294A618700263BE5 /* RunnerTests.swift */,\n\t\t\t);\n\t\t\tpath = RunnerTests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t92053946832FBC8B5CE45542 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t272CEB1E0934A2ADD5D44C56 /* Pods-Runner.debug.xcconfig */,\n\t\t\t\tB2D2646CC85C5457B8D0C536 /* Pods-Runner.release.xcconfig */,\n\t\t\t\t47257ACFBA77E1966ABB77F1 /* Pods-Runner.profile.xcconfig */,\n\t\t\t\t36211D31DD83A06C480B7933 /* Pods-RunnerTests.debug.xcconfig */,\n\t\t\t\tE6B39746540F83CD989EDC9B /* Pods-RunnerTests.release.xcconfig */,\n\t\t\t\t3810D845A293D96D307AF84D /* Pods-RunnerTests.profile.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9740EEB11CF90186004384FC /* Flutter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,\n\t\t\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */,\n\t\t\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */,\n\t\t\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */,\n\t\t\t);\n\t\t\tname = Flutter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146E51CF9000F007C117D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9740EEB11CF90186004384FC /* Flutter */,\n\t\t\t\t97C146F01CF9000F007C117D /* Runner */,\n\t\t\t\t97C146EF1CF9000F007C117D /* Products */,\n\t\t\t\t331C8082294A63A400263BE5 /* RunnerTests */,\n\t\t\t\t7D9CF32C2591DC1A9362B866 /* GoogleService-Info.plist */,\n\t\t\t\t92053946832FBC8B5CE45542 /* Pods */,\n\t\t\t\tBB0F7C07A7CAC845A63B396E /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146EF1CF9000F007C117D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146EE1CF9000F007C117D /* Runner.app */,\n\t\t\t\t331C8081294A63A400263BE5 /* RunnerTests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146F01CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FA1CF9000F007C117D /* Main.storyboard */,\n\t\t\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */,\n\t\t\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,\n\t\t\t\t97C147021CF9000F007C117D /* Info.plist */,\n\t\t\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,\n\t\t\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,\n\t\t\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */,\n\t\t\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,\n\t\t\t);\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBB0F7C07A7CAC845A63B396E /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t15F570C26B5F42E37708B7BD /* Pods_Runner.framework */,\n\t\t\t\t9F907645AC58FE1557878117 /* Pods_RunnerTests.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t331C8080294A63A400263BE5 /* RunnerTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget \"RunnerTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t496B4F410984E7F8DE7E9A59 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t331C807D294A63A400263BE5 /* Sources */,\n\t\t\t\t331C807F294A63A400263BE5 /* Resources */,\n\t\t\t\t7BF2C52BF829726895EFB865 /* Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t331C8086294A63A400263BE5 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = RunnerTests;\n\t\t\tproductName = RunnerTests;\n\t\t\tproductReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t97C146ED1CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t5317527F9B4101BFF11BB5E3 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t9740EEB61CF901F6004384FC /* Run Script */,\n\t\t\t\t97C146EA1CF9000F007C117D /* Sources */,\n\t\t\t\t97C146EB1CF9000F007C117D /* Frameworks */,\n\t\t\t\t97C146EC1CF9000F007C117D /* Resources */,\n\t\t\t\t9705A1C41CF9048500538489 /* Embed Frameworks */,\n\t\t\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */,\n\t\t\t\tFCB1BED7BFC4D59CA29B954A /* [CP] Embed Pods Frameworks */,\n\t\t\t\t43A609F4BA8157E3189042DA /* [CP] Copy Pods Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Runner;\n\t\t\tproductName = Runner;\n\t\t\tproductReference = 97C146EE1CF9000F007C117D /* Runner.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t97C146E61CF9000F007C117D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1510;\n\t\t\t\tORGANIZATIONNAME = \"\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t331C8080294A63A400263BE5 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.0;\n\t\t\t\t\t\tTestTargetID = 97C146ED1CF9000F007C117D;\n\t\t\t\t\t};\n\t\t\t\t\t97C146ED1CF9000F007C117D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.3.1;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 97C146E51CF9000F007C117D;\n\t\t\tproductRefGroup = 97C146EF1CF9000F007C117D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t97C146ED1CF9000F007C117D /* Runner */,\n\t\t\t\t331C8080294A63A400263BE5 /* RunnerTests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t331C807F294A63A400263BE5 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t97C146EC1CF9000F007C117D /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,\n\t\t\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,\n\t\t\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,\n\t\t\t\t101895F97E16CE730BB23D30 /* GoogleService-Info.plist in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\",\n\t\t\t);\n\t\t\tname = \"Thin Binary\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" embed_and_thin\";\n\t\t};\n\t\t43A609F4BA8157E3189042DA /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t496B4F410984E7F8DE7E9A59 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t5317527F9B4101BFF11BB5E3 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t9740EEB61CF901F6004384FC /* Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" build\";\n\t\t};\n\t\tFCB1BED7BFC4D59CA29B954A /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t331C807D294A63A400263BE5 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t97C146EA1CF9000F007C117D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,\n\t\t\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t331C8086294A63A400263BE5 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 97C146ED1CF9000F007C117D /* Runner */;\n\t\t\ttargetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\t97C146FA1CF9000F007C117D /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FB1CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C147001CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t249021D3217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t249021D4217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = HNHCWRWB4P;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t331C8088294A63A400263BE5 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 36211D31DD83A06C480B7933 /* Pods-RunnerTests.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin.RunnerTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t331C8089294A63A400263BE5 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = E6B39746540F83CD989EDC9B /* Pods-RunnerTests.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin.RunnerTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t331C808A294A63A400263BE5 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 3810D845A293D96D307AF84D /* Pods-RunnerTests.profile.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin.RunnerTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t97C147031CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147041CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 13.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t97C147061CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = HNHCWRWB4P;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147071CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = HNHCWRWB4P;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.example.flutterFirebaseLogin;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget \"RunnerTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t331C8088294A63A400263BE5 /* Debug */,\n\t\t\t\t331C8089294A63A400263BE5 /* Release */,\n\t\t\t\t331C808A294A63A400263BE5 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147031CF9000F007C117D /* Debug */,\n\t\t\t\t97C147041CF9000F007C117D /* Release */,\n\t\t\t\t249021D3217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147061CF9000F007C117D /* Debug */,\n\t\t\t\t97C147071CF9000F007C117D /* Release */,\n\t\t\t\t249021D4217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 97C146E61CF9000F007C117D /* Project object */;\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1510\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n               BuildableName = \"Runner.app\"\n               BlueprintName = \"Runner\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\"\n            parallelizable = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"331C8080294A63A400263BE5\"\n               BuildableName = \"RunnerTests.xctest\"\n               BlueprintName = \"RunnerTests\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      enableGPUValidationMode = \"1\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Profile\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "examples/flutter_firebase_login/ios/RunnerTests/RunnerTests.swift",
    "content": "import Flutter\nimport UIKit\nimport XCTest\n\nclass RunnerTests: XCTestCase {\n\n  func testExample() {\n    // If you add code to the Runner application, consider adding tests here.\n    // See https://developer.apple.com/documentation/xctest for more information about using XCTest.\n  }\n\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/app.dart",
    "content": "export 'bloc/app_bloc.dart';\nexport 'bloc_observer.dart';\nexport 'routes/routes.dart';\nexport 'view/app.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/bloc/app_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'app_event.dart';\npart 'app_state.dart';\n\nclass AppBloc extends Bloc<AppEvent, AppState> {\n  AppBloc({required AuthenticationRepository authenticationRepository})\n    : _authenticationRepository = authenticationRepository,\n      super(AppState(user: authenticationRepository.currentUser)) {\n    on<AppUserSubscriptionRequested>(_onUserSubscriptionRequested);\n    on<AppLogoutPressed>(_onLogoutPressed);\n  }\n\n  final AuthenticationRepository _authenticationRepository;\n\n  Future<void> _onUserSubscriptionRequested(\n    AppUserSubscriptionRequested event,\n    Emitter<AppState> emit,\n  ) {\n    return emit.onEach(\n      _authenticationRepository.user,\n      onData: (user) => emit(AppState(user: user)),\n      onError: addError,\n    );\n  }\n\n  void _onLogoutPressed(\n    AppLogoutPressed event,\n    Emitter<AppState> emit,\n  ) {\n    _authenticationRepository.logOut();\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/bloc/app_event.dart",
    "content": "part of 'app_bloc.dart';\n\nsealed class AppEvent {\n  const AppEvent();\n}\n\nfinal class AppUserSubscriptionRequested extends AppEvent {\n  const AppUserSubscriptionRequested();\n}\n\nfinal class AppLogoutPressed extends AppEvent {\n  const AppLogoutPressed();\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/bloc/app_state.dart",
    "content": "part of 'app_bloc.dart';\n\nenum AppStatus { authenticated, unauthenticated }\n\nfinal class AppState extends Equatable {\n  const AppState({User user = User.empty})\n    : this._(\n        status: user == User.empty\n            ? AppStatus.unauthenticated\n            : AppStatus.authenticated,\n        user: user,\n      );\n\n  const AppState._({required this.status, this.user = User.empty});\n\n  final AppStatus status;\n  final User user;\n\n  @override\n  List<Object> get props => [status, user];\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/bloc_observer.dart",
    "content": "// ignore_for_file: avoid_print\nimport 'package:bloc/bloc.dart';\n\nclass AppBlocObserver extends BlocObserver {\n  const AppBlocObserver();\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    super.onEvent(bloc, event);\n    print(event);\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    print(error);\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    print(change);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    print(transition);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/routes/routes.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\n\nList<Page<dynamic>> onGenerateAppViewPages(\n  AppStatus state,\n  List<Page<dynamic>> pages,\n) {\n  switch (state) {\n    case AppStatus.authenticated:\n      return [HomePage.page()];\n    case AppStatus.unauthenticated:\n      return [LoginPage.page()];\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/app/view/app.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flow_builder/flow_builder.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/theme.dart';\n\nclass App extends StatelessWidget {\n  const App({\n    required AuthenticationRepository authenticationRepository,\n    super.key,\n  }) : _authenticationRepository = authenticationRepository;\n\n  final AuthenticationRepository _authenticationRepository;\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider.value(\n      value: _authenticationRepository,\n      child: BlocProvider(\n        lazy: false,\n        create: (_) => AppBloc(\n          authenticationRepository: _authenticationRepository,\n        )..add(const AppUserSubscriptionRequested()),\n        child: const AppView(),\n      ),\n    );\n  }\n}\n\nclass AppView extends StatelessWidget {\n  const AppView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      theme: theme,\n      home: FlowBuilder<AppStatus>(\n        state: context.select((AppBloc bloc) => bloc.state.status),\n        onGeneratePages: onGenerateAppViewPages,\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/home/home.dart",
    "content": "export 'view/home_page.dart';\nexport 'widgets/widgets.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/home/view/home_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  static Page<void> page() => const MaterialPage<void>(child: HomePage());\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    final user = context.select((AppBloc bloc) => bloc.state.user);\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('Home'),\n        actions: <Widget>[\n          IconButton(\n            key: const Key('homePage_logout_iconButton'),\n            icon: const Icon(Icons.exit_to_app),\n            onPressed: () {\n              context.read<AppBloc>().add(const AppLogoutPressed());\n            },\n          ),\n        ],\n      ),\n      body: Align(\n        alignment: const Alignment(0, -1 / 3),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: <Widget>[\n            Avatar(photo: user.photo),\n            const SizedBox(height: 4),\n            Text(user.email ?? '', style: textTheme.titleLarge),\n            const SizedBox(height: 4),\n            Text(user.name ?? '', style: textTheme.headlineSmall),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/home/widgets/avatar.dart",
    "content": "import 'package:flutter/material.dart';\n\nconst _avatarSize = 48.0;\n\nclass Avatar extends StatelessWidget {\n  const Avatar({super.key, this.photo});\n\n  final String? photo;\n\n  @override\n  Widget build(BuildContext context) {\n    final photo = this.photo;\n    return CircleAvatar(\n      radius: _avatarSize,\n      backgroundImage: photo != null ? NetworkImage(photo) : null,\n      child: photo == null\n          ? const Icon(Icons.person_outline, size: _avatarSize)\n          : null,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/home/widgets/widgets.dart",
    "content": "export 'avatar.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:form_inputs/form_inputs.dart';\nimport 'package:formz/formz.dart';\n\npart 'login_state.dart';\n\nclass LoginCubit extends Cubit<LoginState> {\n  LoginCubit(this._authenticationRepository) : super(const LoginState());\n\n  final AuthenticationRepository _authenticationRepository;\n\n  void emailChanged(String email) => emit(state.withEmail(email));\n\n  void passwordChanged(String password) => emit(state.withPassword(password));\n\n  Future<void> logInWithCredentials() async {\n    if (!state.isValid) return;\n    emit(state.withSubmissionInProgress());\n    try {\n      await _authenticationRepository.logInWithEmailAndPassword(\n        email: state.email.value,\n        password: state.password.value,\n      );\n      emit(state.withSubmissionSuccess());\n    } on LogInWithEmailAndPasswordFailure catch (e) {\n      emit(state.withSubmissionFailure(e.message));\n    } catch (_) {\n      emit(state.withSubmissionFailure());\n    }\n  }\n\n  Future<void> logInWithGoogle() async {\n    emit(state.withSubmissionInProgress());\n    try {\n      await _authenticationRepository.logInWithGoogle();\n      emit(state.withSubmissionSuccess());\n    } on LogInWithGoogleFailure catch (e) {\n      emit(state.withSubmissionFailure(e.message));\n    } catch (_) {\n      emit(state.withSubmissionFailure());\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/cubit/login_state.dart",
    "content": "part of 'login_cubit.dart';\n\nfinal class LoginState extends Equatable {\n  const LoginState() : this._();\n\n  const LoginState._({\n    this.email = const Email.pure(),\n    this.password = const Password.pure(),\n    this.status = FormzSubmissionStatus.initial,\n    this.errorMessage,\n  });\n\n  LoginState withEmail(String email) {\n    return LoginState._(email: Email.dirty(email), password: password);\n  }\n\n  LoginState withPassword(String password) {\n    return LoginState._(email: email, password: Password.dirty(password));\n  }\n\n  LoginState withSubmissionInProgress() {\n    return LoginState._(\n      email: email,\n      password: password,\n      status: FormzSubmissionStatus.inProgress,\n    );\n  }\n\n  LoginState withSubmissionSuccess() {\n    return LoginState._(\n      email: email,\n      password: password,\n      status: FormzSubmissionStatus.success,\n    );\n  }\n\n  LoginState withSubmissionFailure([String? error]) {\n    return LoginState._(\n      email: email,\n      password: password,\n      status: FormzSubmissionStatus.failure,\n      errorMessage: error,\n    );\n  }\n\n  final Email email;\n  final Password password;\n  final FormzSubmissionStatus status;\n  final String? errorMessage;\n\n  bool get isValid => Formz.validate([email, password]);\n\n  @override\n  List<Object?> get props => [email, password, status, errorMessage];\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/login.dart",
    "content": "export 'cubit/login_cubit.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/view/login_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:font_awesome_flutter/font_awesome_flutter.dart';\nimport 'package:formz/formz.dart';\n\nclass LoginForm extends StatelessWidget {\n  const LoginForm({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<LoginCubit, LoginState>(\n      listener: (context, state) {\n        if (state.status.isFailure) {\n          ScaffoldMessenger.of(context)\n            ..hideCurrentSnackBar()\n            ..showSnackBar(\n              SnackBar(\n                content: Text(state.errorMessage ?? 'Authentication Failure'),\n              ),\n            );\n        }\n      },\n      child: Align(\n        alignment: const Alignment(0, -1 / 3),\n        child: SingleChildScrollView(\n          child: Column(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Image.asset(\n                'assets/bloc_logo_small.png',\n                height: 120,\n              ),\n              const SizedBox(height: 16),\n              _EmailInput(),\n              const SizedBox(height: 8),\n              _PasswordInput(),\n              const SizedBox(height: 8),\n              _LoginButton(),\n              const SizedBox(height: 8),\n              _GoogleLoginButton(),\n              const SizedBox(height: 4),\n              _SignUpButton(),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass _EmailInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (LoginCubit cubit) => cubit.state.email.displayError,\n    );\n\n    return TextField(\n      key: const Key('loginForm_emailInput_textField'),\n      onChanged: (email) => context.read<LoginCubit>().emailChanged(email),\n      keyboardType: TextInputType.emailAddress,\n      decoration: InputDecoration(\n        labelText: 'email',\n        helperText: '',\n        errorText: displayError != null ? 'invalid email' : null,\n      ),\n    );\n  }\n}\n\nclass _PasswordInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (LoginCubit cubit) => cubit.state.password.displayError,\n    );\n\n    return TextField(\n      key: const Key('loginForm_passwordInput_textField'),\n      onChanged: (password) =>\n          context.read<LoginCubit>().passwordChanged(password),\n      obscureText: true,\n      decoration: InputDecoration(\n        labelText: 'password',\n        helperText: '',\n        errorText: displayError != null ? 'invalid password' : null,\n      ),\n    );\n  }\n}\n\nclass _LoginButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final isInProgress = context.select(\n      (LoginCubit cubit) => cubit.state.status.isInProgress,\n    );\n\n    if (isInProgress) return const CircularProgressIndicator();\n\n    final isValid = context.select(\n      (LoginCubit cubit) => cubit.state.isValid,\n    );\n\n    return ElevatedButton(\n      key: const Key('loginForm_continue_raisedButton'),\n      style: ElevatedButton.styleFrom(\n        shape: RoundedRectangleBorder(\n          borderRadius: BorderRadius.circular(30),\n        ),\n        backgroundColor: const Color(0xFFFFD600),\n      ),\n      onPressed: isValid\n          ? () => context.read<LoginCubit>().logInWithCredentials()\n          : null,\n      child: const Text('LOGIN'),\n    );\n  }\n}\n\nclass _GoogleLoginButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return ElevatedButton.icon(\n      key: const Key('loginForm_googleLogin_raisedButton'),\n      label: const Text(\n        'SIGN IN WITH GOOGLE',\n        style: TextStyle(color: Colors.white),\n      ),\n      style: ElevatedButton.styleFrom(\n        shape: RoundedRectangleBorder(\n          borderRadius: BorderRadius.circular(30),\n        ),\n        backgroundColor: theme.colorScheme.secondary,\n      ),\n      icon: const Icon(FontAwesomeIcons.google, color: Colors.white),\n      onPressed: () => context.read<LoginCubit>().logInWithGoogle(),\n    );\n  }\n}\n\nclass _SignUpButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return TextButton(\n      key: const Key('loginForm_createAccount_flatButton'),\n      onPressed: () => Navigator.of(context).push<void>(SignUpPage.route()),\n      child: Text(\n        'CREATE ACCOUNT',\n        style: TextStyle(color: theme.primaryColor),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/view/login_page.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\n\nclass LoginPage extends StatelessWidget {\n  const LoginPage({super.key});\n\n  static Page<void> page() => const MaterialPage<void>(child: LoginPage());\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Login')),\n      body: Padding(\n        padding: const EdgeInsets.all(8),\n        child: BlocProvider(\n          create: (_) => LoginCubit(context.read<AuthenticationRepository>()),\n          child: const LoginForm(),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/login/view/view.dart",
    "content": "export 'login_form.dart';\nexport 'login_page.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/main.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:firebase_core/firebase_core.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\n\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  Bloc.observer = const AppBlocObserver();\n\n  await Firebase.initializeApp();\n\n  final authenticationRepository = AuthenticationRepository();\n  await authenticationRepository.user.first;\n\n  runApp(App(authenticationRepository: authenticationRepository));\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:form_inputs/form_inputs.dart';\nimport 'package:formz/formz.dart';\n\npart 'sign_up_state.dart';\n\nclass SignUpCubit extends Cubit<SignUpState> {\n  SignUpCubit(this._authenticationRepository) : super(const SignUpState());\n\n  final AuthenticationRepository _authenticationRepository;\n\n  void emailChanged(String email) => emit(state.withEmail(email));\n\n  void passwordChanged(String password) => emit(state.withPassword(password));\n\n  void confirmedPasswordChanged(String confirmedPassword) {\n    emit(state.withConfirmedPassword(confirmedPassword));\n  }\n\n  Future<void> signUpFormSubmitted() async {\n    if (!state.isValid) return;\n    emit(state.withSubmissionInProgress());\n    try {\n      await _authenticationRepository.signUp(\n        email: state.email.value,\n        password: state.password.value,\n      );\n      emit(state.withSubmissionSuccess());\n    } on SignUpWithEmailAndPasswordFailure catch (e) {\n      emit(state.withSubmissionFailure(e.message));\n    } catch (_) {\n      emit(state.withSubmissionFailure());\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart",
    "content": "part of 'sign_up_cubit.dart';\n\nfinal class SignUpState extends Equatable {\n  const SignUpState() : this._();\n\n  const SignUpState._({\n    this.email = const Email.pure(),\n    this.password = const Password.pure(),\n    this.confirmedPassword = const ConfirmedPassword.pure(),\n    this.status = FormzSubmissionStatus.initial,\n    this.errorMessage,\n  });\n\n  final Email email;\n  final Password password;\n  final ConfirmedPassword confirmedPassword;\n  final FormzSubmissionStatus status;\n  final String? errorMessage;\n\n  SignUpState withEmail(String email) {\n    return SignUpState._(\n      email: Email.dirty(email),\n      password: password,\n      confirmedPassword: confirmedPassword,\n    );\n  }\n\n  SignUpState withPassword(String password) {\n    return SignUpState._(\n      email: email,\n      password: Password.dirty(password),\n      confirmedPassword: ConfirmedPassword.dirty(\n        password: password,\n        value: confirmedPassword.value,\n      ),\n    );\n  }\n\n  SignUpState withConfirmedPassword(String confirmedPassword) {\n    return SignUpState._(\n      email: email,\n      password: password,\n      confirmedPassword: ConfirmedPassword.dirty(\n        password: password.value,\n        value: confirmedPassword,\n      ),\n    );\n  }\n\n  SignUpState withSubmissionInProgress() {\n    return SignUpState._(\n      email: email,\n      password: password,\n      confirmedPassword: confirmedPassword,\n      status: FormzSubmissionStatus.inProgress,\n    );\n  }\n\n  SignUpState withSubmissionSuccess() {\n    return SignUpState._(\n      email: email,\n      password: password,\n      confirmedPassword: confirmedPassword,\n      status: FormzSubmissionStatus.success,\n    );\n  }\n\n  SignUpState withSubmissionFailure([String? error]) {\n    return SignUpState._(\n      email: email,\n      password: password,\n      confirmedPassword: confirmedPassword,\n      status: FormzSubmissionStatus.failure,\n      errorMessage: error,\n    );\n  }\n\n  bool get isValid => Formz.validate([email, password, confirmedPassword]);\n\n  @override\n  List<Object?> get props => [\n    email,\n    password,\n    confirmedPassword,\n    status,\n    errorMessage,\n  ];\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/sign_up.dart",
    "content": "export 'cubit/sign_up_cubit.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/view/sign_up_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:formz/formz.dart';\n\nclass SignUpForm extends StatelessWidget {\n  const SignUpForm({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<SignUpCubit, SignUpState>(\n      listener: (context, state) {\n        if (state.status.isSuccess) {\n          Navigator.of(context).pop();\n        } else if (state.status.isFailure) {\n          ScaffoldMessenger.of(context)\n            ..hideCurrentSnackBar()\n            ..showSnackBar(\n              SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')),\n            );\n        }\n      },\n      child: Align(\n        alignment: const Alignment(0, -1 / 3),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            _EmailInput(),\n            const SizedBox(height: 8),\n            _PasswordInput(),\n            const SizedBox(height: 8),\n            _ConfirmPasswordInput(),\n            const SizedBox(height: 8),\n            _SignUpButton(),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass _EmailInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (SignUpCubit cubit) => cubit.state.email.displayError,\n    );\n\n    return TextField(\n      key: const Key('signUpForm_emailInput_textField'),\n      onChanged: (email) => context.read<SignUpCubit>().emailChanged(email),\n      keyboardType: TextInputType.emailAddress,\n      decoration: InputDecoration(\n        labelText: 'email',\n        helperText: '',\n        errorText: displayError != null ? 'invalid email' : null,\n      ),\n    );\n  }\n}\n\nclass _PasswordInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (SignUpCubit cubit) => cubit.state.password.displayError,\n    );\n\n    return TextField(\n      key: const Key('signUpForm_passwordInput_textField'),\n      onChanged: (password) =>\n          context.read<SignUpCubit>().passwordChanged(password),\n      obscureText: true,\n      decoration: InputDecoration(\n        labelText: 'password',\n        helperText: '',\n        errorText: displayError != null ? 'invalid password' : null,\n      ),\n    );\n  }\n}\n\nclass _ConfirmPasswordInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (SignUpCubit cubit) => cubit.state.confirmedPassword.displayError,\n    );\n\n    return TextField(\n      key: const Key('signUpForm_confirmedPasswordInput_textField'),\n      onChanged: (confirmPassword) =>\n          context.read<SignUpCubit>().confirmedPasswordChanged(confirmPassword),\n      obscureText: true,\n      decoration: InputDecoration(\n        labelText: 'confirm password',\n        helperText: '',\n        errorText: displayError != null ? 'passwords do not match' : null,\n      ),\n    );\n  }\n}\n\nclass _SignUpButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final isInProgress = context.select(\n      (SignUpCubit cubit) => cubit.state.status.isInProgress,\n    );\n\n    if (isInProgress) return const CircularProgressIndicator();\n\n    final isValid = context.select(\n      (SignUpCubit cubit) => cubit.state.isValid,\n    );\n\n    return ElevatedButton(\n      key: const Key('signUpForm_continue_raisedButton'),\n      style: ElevatedButton.styleFrom(\n        shape: RoundedRectangleBorder(\n          borderRadius: BorderRadius.circular(30),\n        ),\n        backgroundColor: Colors.orangeAccent,\n      ),\n      onPressed: isValid\n          ? () => context.read<SignUpCubit>().signUpFormSubmitted()\n          : null,\n      child: const Text('SIGN UP'),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/view/sign_up_page.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\n\nclass SignUpPage extends StatelessWidget {\n  const SignUpPage({super.key});\n\n  static Route<void> route() {\n    return MaterialPageRoute<void>(builder: (_) => const SignUpPage());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Sign Up')),\n      body: Padding(\n        padding: const EdgeInsets.all(8),\n        child: BlocProvider<SignUpCubit>(\n          create: (_) => SignUpCubit(context.read<AuthenticationRepository>()),\n          child: const SignUpForm(),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/sign_up/view/view.dart",
    "content": "export 'sign_up_form.dart';\nexport 'sign_up_page.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/lib/theme.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:google_fonts/google_fonts.dart';\n\nfinal theme = ThemeData(\n  textTheme: GoogleFonts.openSansTextTheme(),\n  appBarTheme: const AppBarTheme(\n    backgroundColor: Color.fromARGB(255, 113, 243, 230),\n    elevation: 4,\n  ),\n  colorScheme: const ColorScheme.light(\n    primary: Color(0xFF0097A7),\n    secondary: Color(0xFF009688),\n    surface: Color(0xFFE0F2F1),\n  ),\n  inputDecorationTheme: InputDecorationTheme(\n    border: OutlineInputBorder(\n      borderRadius: BorderRadius.circular(8),\n    ),\n  ),\n);\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/lib/authentication_repository.dart",
    "content": "export 'src/authentication_repository.dart';\nexport 'src/models/models.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart",
    "content": "import 'dart:async';\n\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:cache/cache.dart';\nimport 'package:firebase_auth/firebase_auth.dart' as firebase_auth;\nimport 'package:flutter/foundation.dart' show kIsWeb;\nimport 'package:google_sign_in/google_sign_in.dart';\nimport 'package:meta/meta.dart';\n\n/// {@template sign_up_with_email_and_password_failure}\n/// Thrown during the sign up process if a failure occurs.\n/// {@endtemplate}\nclass SignUpWithEmailAndPasswordFailure implements Exception {\n  /// {@macro sign_up_with_email_and_password_failure}\n  const SignUpWithEmailAndPasswordFailure([\n    this.message = 'An unknown exception occurred.',\n  ]);\n\n  /// Create an authentication message\n  /// from a firebase authentication exception code.\n  /// https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/createUserWithEmailAndPassword.html\n  factory SignUpWithEmailAndPasswordFailure.fromCode(String code) {\n    switch (code) {\n      case 'invalid-email':\n        return const SignUpWithEmailAndPasswordFailure(\n          'Email is not valid or badly formatted.',\n        );\n      case 'user-disabled':\n        return const SignUpWithEmailAndPasswordFailure(\n          'This user has been disabled. Please contact support for help.',\n        );\n      case 'email-already-in-use':\n        return const SignUpWithEmailAndPasswordFailure(\n          'An account already exists for that email.',\n        );\n      case 'operation-not-allowed':\n        return const SignUpWithEmailAndPasswordFailure(\n          'Operation is not allowed.  Please contact support.',\n        );\n      case 'weak-password':\n        return const SignUpWithEmailAndPasswordFailure(\n          'Please enter a stronger password.',\n        );\n      default:\n        return const SignUpWithEmailAndPasswordFailure();\n    }\n  }\n\n  /// The associated error message.\n  final String message;\n}\n\n/// {@template log_in_with_email_and_password_failure}\n/// Thrown during the login process if a failure occurs.\n/// https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/signInWithEmailAndPassword.html\n/// {@endtemplate}\nclass LogInWithEmailAndPasswordFailure implements Exception {\n  /// {@macro log_in_with_email_and_password_failure}\n  const LogInWithEmailAndPasswordFailure([\n    this.message = 'An unknown exception occurred.',\n  ]);\n\n  /// Create an authentication message\n  /// from a firebase authentication exception code.\n  factory LogInWithEmailAndPasswordFailure.fromCode(String code) {\n    switch (code) {\n      case 'invalid-email':\n        return const LogInWithEmailAndPasswordFailure(\n          'Email is not valid or badly formatted.',\n        );\n      case 'user-disabled':\n        return const LogInWithEmailAndPasswordFailure(\n          'This user has been disabled. Please contact support for help.',\n        );\n      case 'user-not-found':\n        return const LogInWithEmailAndPasswordFailure(\n          'Email is not found, please create an account.',\n        );\n      case 'wrong-password':\n        return const LogInWithEmailAndPasswordFailure(\n          'Incorrect password, please try again.',\n        );\n      default:\n        return const LogInWithEmailAndPasswordFailure();\n    }\n  }\n\n  /// The associated error message.\n  final String message;\n}\n\n/// {@template log_in_with_google_failure}\n/// Thrown during the sign in with google process if a failure occurs.\n/// https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/signInWithCredential.html\n/// {@endtemplate}\nclass LogInWithGoogleFailure implements Exception {\n  /// {@macro log_in_with_google_failure}\n  const LogInWithGoogleFailure([\n    this.message = 'An unknown exception occurred.',\n  ]);\n\n  /// Create an authentication message\n  /// from a firebase authentication exception code.\n  factory LogInWithGoogleFailure.fromCode(String code) {\n    switch (code) {\n      case 'account-exists-with-different-credential':\n        return const LogInWithGoogleFailure(\n          'Account exists with different credentials.',\n        );\n      case 'invalid-credential':\n        return const LogInWithGoogleFailure(\n          'The credential received is malformed or has expired.',\n        );\n      case 'operation-not-allowed':\n        return const LogInWithGoogleFailure(\n          'Operation is not allowed.  Please contact support.',\n        );\n      case 'user-disabled':\n        return const LogInWithGoogleFailure(\n          'This user has been disabled. Please contact support for help.',\n        );\n      case 'user-not-found':\n        return const LogInWithGoogleFailure(\n          'Email is not found, please create an account.',\n        );\n      case 'wrong-password':\n        return const LogInWithGoogleFailure(\n          'Incorrect password, please try again.',\n        );\n      case 'invalid-verification-code':\n        return const LogInWithGoogleFailure(\n          'The credential verification code received is invalid.',\n        );\n      case 'invalid-verification-id':\n        return const LogInWithGoogleFailure(\n          'The credential verification ID received is invalid.',\n        );\n      default:\n        return const LogInWithGoogleFailure();\n    }\n  }\n\n  /// The associated error message.\n  final String message;\n}\n\n/// Thrown during the logout process if a failure occurs.\nclass LogOutFailure implements Exception {}\n\n/// {@template authentication_repository}\n/// Repository which manages user authentication.\n/// {@endtemplate}\nclass AuthenticationRepository {\n  /// {@macro authentication_repository}\n  AuthenticationRepository({\n    CacheClient? cache,\n    firebase_auth.FirebaseAuth? firebaseAuth,\n    GoogleSignIn? googleSignIn,\n  }) : _cache = cache ?? CacheClient(),\n       _firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance,\n       _googleSignIn = googleSignIn ?? GoogleSignIn.standard();\n\n  final CacheClient _cache;\n  final firebase_auth.FirebaseAuth _firebaseAuth;\n  final GoogleSignIn _googleSignIn;\n\n  /// Whether or not the current environment is web\n  /// Should only be overridden for testing purposes. Otherwise,\n  /// defaults to [kIsWeb]\n  @visibleForTesting\n  bool isWeb = kIsWeb;\n\n  /// User cache key.\n  /// Should only be used for testing purposes.\n  @visibleForTesting\n  static const userCacheKey = '__user_cache_key__';\n\n  /// Stream of [User] which will emit the current user when\n  /// the authentication state changes.\n  ///\n  /// Emits [User.empty] if the user is not authenticated.\n  Stream<User> get user {\n    return _firebaseAuth.authStateChanges().map((firebaseUser) {\n      final user = firebaseUser == null ? User.empty : firebaseUser.toUser;\n      _cache.write(key: userCacheKey, value: user);\n      return user;\n    });\n  }\n\n  /// Returns the current cached user.\n  /// Defaults to [User.empty] if there is no cached user.\n  User get currentUser {\n    return _cache.read<User>(key: userCacheKey) ?? User.empty;\n  }\n\n  /// Creates a new user with the provided [email] and [password].\n  ///\n  /// Throws a [SignUpWithEmailAndPasswordFailure] if an exception occurs.\n  Future<void> signUp({required String email, required String password}) async {\n    try {\n      await _firebaseAuth.createUserWithEmailAndPassword(\n        email: email,\n        password: password,\n      );\n    } on firebase_auth.FirebaseAuthException catch (e) {\n      throw SignUpWithEmailAndPasswordFailure.fromCode(e.code);\n    } catch (_) {\n      throw const SignUpWithEmailAndPasswordFailure();\n    }\n  }\n\n  /// Starts the Sign In with Google Flow.\n  ///\n  /// Throws a [LogInWithGoogleFailure] if an exception occurs.\n  Future<void> logInWithGoogle() async {\n    try {\n      late final firebase_auth.AuthCredential credential;\n      if (isWeb) {\n        final googleProvider = firebase_auth.GoogleAuthProvider();\n        final userCredential = await _firebaseAuth.signInWithPopup(\n          googleProvider,\n        );\n        credential = userCredential.credential!;\n      } else {\n        final googleUser = await _googleSignIn.signIn();\n        final googleAuth = await googleUser!.authentication;\n        credential = firebase_auth.GoogleAuthProvider.credential(\n          accessToken: googleAuth.accessToken,\n          idToken: googleAuth.idToken,\n        );\n      }\n\n      await _firebaseAuth.signInWithCredential(credential);\n    } on firebase_auth.FirebaseAuthException catch (e) {\n      throw LogInWithGoogleFailure.fromCode(e.code);\n    } catch (_) {\n      throw const LogInWithGoogleFailure();\n    }\n  }\n\n  /// Signs in with the provided [email] and [password].\n  ///\n  /// Throws a [LogInWithEmailAndPasswordFailure] if an exception occurs.\n  Future<void> logInWithEmailAndPassword({\n    required String email,\n    required String password,\n  }) async {\n    try {\n      await _firebaseAuth.signInWithEmailAndPassword(\n        email: email,\n        password: password,\n      );\n    } on firebase_auth.FirebaseAuthException catch (e) {\n      throw LogInWithEmailAndPasswordFailure.fromCode(e.code);\n    } catch (_) {\n      throw const LogInWithEmailAndPasswordFailure();\n    }\n  }\n\n  /// Signs out the current user which will emit\n  /// [User.empty] from the [user] Stream.\n  ///\n  /// Throws a [LogOutFailure] if an exception occurs.\n  Future<void> logOut() async {\n    try {\n      await Future.wait([\n        _firebaseAuth.signOut(),\n        _googleSignIn.signOut(),\n      ]);\n    } catch (_) {\n      throw LogOutFailure();\n    }\n  }\n}\n\nextension on firebase_auth.User {\n  /// Maps a [firebase_auth.User] into a [User].\n  User get toUser {\n    return User(id: uid, email: email, name: displayName, photo: photoURL);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/models.dart",
    "content": "export 'user.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/lib/src/models/user.dart",
    "content": "import 'package:equatable/equatable.dart';\n\n/// {@template user}\n/// User model\n///\n/// [User.empty] represents an unauthenticated user.\n/// {@endtemplate}\nclass User extends Equatable {\n  /// {@macro user}\n  const User({\n    required this.id,\n    this.email,\n    this.name,\n    this.photo,\n  });\n\n  /// The current user's email address.\n  final String? email;\n\n  /// The current user's id.\n  final String id;\n\n  /// The current user's name (display name).\n  final String? name;\n\n  /// Url for the current user's photo.\n  final String? photo;\n\n  /// Empty user which represents an unauthenticated user.\n  static const empty = User(id: '');\n\n  @override\n  List<Object?> get props => [email, id, name, photo];\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml",
    "content": "name: authentication_repository\ndescription: Dart package which manages the authentication domain.\nversion: 1.0.0\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  cache:\n    path: ../cache\n  equatable: ^2.0.0\n  firebase_auth: ^5.0.0\n  firebase_core: ^3.0.0\n  flutter:\n    sdk: flutter\n  google_sign_in: ^6.1.0\n  meta: ^1.8.0\n\ndev_dependencies:\n  firebase_auth_platform_interface: ^7.0.5\n  firebase_core_platform_interface: ^5.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n  plugin_platform_interface: ^2.1.7\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/test/authentication_repository_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:cache/cache.dart';\nimport 'package:firebase_auth/firebase_auth.dart' as firebase_auth;\nimport 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';\nimport 'package:firebase_core/firebase_core.dart';\nimport 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:google_sign_in/google_sign_in.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:plugin_platform_interface/plugin_platform_interface.dart';\n\nconst _mockFirebaseUserUid = 'mock-uid';\nconst _mockFirebaseUserEmail = 'mock-email';\n\nclass MockCacheClient extends Mock implements CacheClient {}\n\nclass MockFirebaseAuth extends Mock implements firebase_auth.FirebaseAuth {}\n\nclass MockFirebaseCore extends Mock\n    with MockPlatformInterfaceMixin\n    implements FirebasePlatform {}\n\nclass MockFirebaseUser extends Mock implements firebase_auth.User {}\n\nclass MockGoogleSignIn extends Mock implements GoogleSignIn {}\n\nclass MockGoogleSignInAccount extends Mock implements GoogleSignInAccount {}\n\nclass MockGoogleSignInAuthentication extends Mock\n    implements GoogleSignInAuthentication {}\n\nclass MockUserCredential extends Mock implements firebase_auth.UserCredential {}\n\nclass FakeAuthCredential extends Fake implements firebase_auth.AuthCredential {}\n\nclass FakeAuthProvider extends Fake implements AuthProvider {}\n\nvoid main() {\n  TestWidgetsFlutterBinding.ensureInitialized();\n\n  const email = 'test@gmail.com';\n  const password = 't0ps3cret42';\n  const user = User(\n    id: _mockFirebaseUserUid,\n    email: _mockFirebaseUserEmail,\n  );\n\n  group('AuthenticationRepository', () {\n    late CacheClient cache;\n    late firebase_auth.FirebaseAuth firebaseAuth;\n    late GoogleSignIn googleSignIn;\n    late AuthenticationRepository authenticationRepository;\n\n    setUpAll(() {\n      registerFallbackValue(FakeAuthCredential());\n      registerFallbackValue(FakeAuthProvider());\n    });\n\n    setUp(() {\n      const options = FirebaseOptions(\n        apiKey: 'apiKey',\n        appId: 'appId',\n        messagingSenderId: 'messagingSenderId',\n        projectId: 'projectId',\n      );\n      final platformApp = FirebaseAppPlatform(defaultFirebaseAppName, options);\n      final firebaseCore = MockFirebaseCore();\n\n      when(() => firebaseCore.apps).thenReturn([platformApp]);\n      when(firebaseCore.app).thenReturn(platformApp);\n      when(\n        () => firebaseCore.initializeApp(\n          name: defaultFirebaseAppName,\n          options: options,\n        ),\n      ).thenAnswer((_) async => platformApp);\n\n      Firebase.delegatePackingProperty = firebaseCore;\n\n      cache = MockCacheClient();\n      firebaseAuth = MockFirebaseAuth();\n      googleSignIn = MockGoogleSignIn();\n      authenticationRepository = AuthenticationRepository(\n        cache: cache,\n        firebaseAuth: firebaseAuth,\n        googleSignIn: googleSignIn,\n      );\n    });\n\n    test('creates FirebaseAuth instance internally when not injected', () {\n      expect(AuthenticationRepository.new, isNot(throwsException));\n    });\n\n    group('signUp', () {\n      setUp(() {\n        when(\n          () => firebaseAuth.createUserWithEmailAndPassword(\n            email: any(named: 'email'),\n            password: any(named: 'password'),\n          ),\n        ).thenAnswer((_) => Future.value(MockUserCredential()));\n      });\n\n      test('calls createUserWithEmailAndPassword', () async {\n        await authenticationRepository.signUp(email: email, password: password);\n        verify(\n          () => firebaseAuth.createUserWithEmailAndPassword(\n            email: email,\n            password: password,\n          ),\n        ).called(1);\n      });\n\n      test('succeeds when createUserWithEmailAndPassword succeeds', () async {\n        expect(\n          authenticationRepository.signUp(email: email, password: password),\n          completes,\n        );\n      });\n\n      test('throws SignUpWithEmailAndPasswordFailure '\n          'when createUserWithEmailAndPassword throws', () async {\n        when(\n          () => firebaseAuth.createUserWithEmailAndPassword(\n            email: any(named: 'email'),\n            password: any(named: 'password'),\n          ),\n        ).thenThrow(Exception());\n        expect(\n          authenticationRepository.signUp(email: email, password: password),\n          throwsA(isA<SignUpWithEmailAndPasswordFailure>()),\n        );\n      });\n    });\n\n    group('loginWithGoogle', () {\n      const accessToken = 'access-token';\n      const idToken = 'id-token';\n\n      setUp(() {\n        final googleSignInAuthentication = MockGoogleSignInAuthentication();\n        final googleSignInAccount = MockGoogleSignInAccount();\n        when(\n          () => googleSignInAuthentication.accessToken,\n        ).thenReturn(accessToken);\n        when(() => googleSignInAuthentication.idToken).thenReturn(idToken);\n        when(\n          () => googleSignInAccount.authentication,\n        ).thenAnswer((_) async => googleSignInAuthentication);\n        when(\n          () => googleSignIn.signIn(),\n        ).thenAnswer((_) async => googleSignInAccount);\n        when(\n          () => firebaseAuth.signInWithCredential(any()),\n        ).thenAnswer((_) => Future.value(MockUserCredential()));\n        when(\n          () => firebaseAuth.signInWithPopup(any()),\n        ).thenAnswer((_) => Future.value(MockUserCredential()));\n      });\n\n      test('calls signIn authentication, and signInWithCredential', () async {\n        await authenticationRepository.logInWithGoogle();\n        verify(() => googleSignIn.signIn()).called(1);\n        verify(() => firebaseAuth.signInWithCredential(any())).called(1);\n      });\n\n      test(\n        'throws LogInWithGoogleFailure and calls signIn authentication, and '\n        'signInWithPopup when authCredential is null and kIsWeb is true',\n        () async {\n          authenticationRepository.isWeb = true;\n          await expectLater(\n            () => authenticationRepository.logInWithGoogle(),\n            throwsA(isA<LogInWithGoogleFailure>()),\n          );\n          verifyNever(() => googleSignIn.signIn());\n          verify(() => firebaseAuth.signInWithPopup(any())).called(1);\n        },\n      );\n\n      test(\n        'successfully calls signIn authentication, and '\n        'signInWithPopup when authCredential is not null and kIsWeb is true',\n        () async {\n          final credential = MockUserCredential();\n          when(\n            () => firebaseAuth.signInWithPopup(any()),\n          ).thenAnswer((_) async => credential);\n          when(() => credential.credential).thenReturn(FakeAuthCredential());\n          authenticationRepository.isWeb = true;\n          await expectLater(\n            authenticationRepository.logInWithGoogle(),\n            completes,\n          );\n          verifyNever(() => googleSignIn.signIn());\n          verify(() => firebaseAuth.signInWithPopup(any())).called(1);\n        },\n      );\n\n      test('succeeds when signIn succeeds', () {\n        expect(authenticationRepository.logInWithGoogle(), completes);\n      });\n\n      test('throws LogInWithGoogleFailure when exception occurs', () async {\n        when(\n          () => firebaseAuth.signInWithCredential(any()),\n        ).thenThrow(Exception());\n        expect(\n          authenticationRepository.logInWithGoogle(),\n          throwsA(isA<LogInWithGoogleFailure>()),\n        );\n      });\n    });\n\n    group('logInWithEmailAndPassword', () {\n      setUp(() {\n        when(\n          () => firebaseAuth.signInWithEmailAndPassword(\n            email: any(named: 'email'),\n            password: any(named: 'password'),\n          ),\n        ).thenAnswer((_) => Future.value(MockUserCredential()));\n      });\n\n      test('calls signInWithEmailAndPassword', () async {\n        await authenticationRepository.logInWithEmailAndPassword(\n          email: email,\n          password: password,\n        );\n        verify(\n          () => firebaseAuth.signInWithEmailAndPassword(\n            email: email,\n            password: password,\n          ),\n        ).called(1);\n      });\n\n      test('succeeds when signInWithEmailAndPassword succeeds', () async {\n        expect(\n          authenticationRepository.logInWithEmailAndPassword(\n            email: email,\n            password: password,\n          ),\n          completes,\n        );\n      });\n\n      test('throws LogInWithEmailAndPasswordFailure '\n          'when signInWithEmailAndPassword throws', () async {\n        when(\n          () => firebaseAuth.signInWithEmailAndPassword(\n            email: any(named: 'email'),\n            password: any(named: 'password'),\n          ),\n        ).thenThrow(Exception());\n        expect(\n          authenticationRepository.logInWithEmailAndPassword(\n            email: email,\n            password: password,\n          ),\n          throwsA(isA<LogInWithEmailAndPasswordFailure>()),\n        );\n      });\n    });\n\n    group('logOut', () {\n      test('calls signOut', () async {\n        when(() => firebaseAuth.signOut()).thenAnswer((_) async {});\n        when(() => googleSignIn.signOut()).thenAnswer((_) async => null);\n        await authenticationRepository.logOut();\n        verify(() => firebaseAuth.signOut()).called(1);\n        verify(() => googleSignIn.signOut()).called(1);\n      });\n\n      test('throws LogOutFailure when signOut throws', () async {\n        when(() => firebaseAuth.signOut()).thenThrow(Exception());\n        expect(\n          authenticationRepository.logOut(),\n          throwsA(isA<LogOutFailure>()),\n        );\n      });\n    });\n\n    group('user', () {\n      test('emits User.empty when firebase user is null', () async {\n        when(\n          () => firebaseAuth.authStateChanges(),\n        ).thenAnswer((_) => Stream.value(null));\n        await expectLater(\n          authenticationRepository.user,\n          emitsInOrder(const <User>[User.empty]),\n        );\n      });\n\n      test('emits User when firebase user is not null', () async {\n        final firebaseUser = MockFirebaseUser();\n        when(() => firebaseUser.uid).thenReturn(_mockFirebaseUserUid);\n        when(() => firebaseUser.email).thenReturn(_mockFirebaseUserEmail);\n        when(() => firebaseUser.photoURL).thenReturn(null);\n        when(\n          () => firebaseAuth.authStateChanges(),\n        ).thenAnswer((_) => Stream.value(firebaseUser));\n        await expectLater(\n          authenticationRepository.user,\n          emitsInOrder(const <User>[user]),\n        );\n        verify(\n          () => cache.write(\n            key: AuthenticationRepository.userCacheKey,\n            value: user,\n          ),\n        ).called(1);\n      });\n    });\n\n    group('currentUser', () {\n      test('returns User.empty when cached user is null', () {\n        when(\n          () => cache.read(key: AuthenticationRepository.userCacheKey),\n        ).thenReturn(null);\n        expect(\n          authenticationRepository.currentUser,\n          equals(User.empty),\n        );\n      });\n\n      test('returns User when cached user is not null', () async {\n        when(\n          () => cache.read<User>(key: AuthenticationRepository.userCacheKey),\n        ).thenReturn(user);\n        expect(authenticationRepository.currentUser, equals(user));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/authentication_repository/test/models/user_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('User', () {\n    const id = 'mock-id';\n    const email = 'mock-email';\n\n    test('uses value equality', () {\n      expect(\n        User(email: email, id: id),\n        equals(User(email: email, id: id)),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/cache/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/cache/lib/cache.dart",
    "content": "/// {@template cache_client}\n/// An in-memory cache client.\n/// {@endtemplate}\nclass CacheClient {\n  /// {@macro cache_client}\n  CacheClient() : _cache = <String, Object>{};\n\n  final Map<String, Object> _cache;\n\n  /// Writes the provide [key], [value] pair to the in-memory cache.\n  void write<T extends Object>({required String key, required T value}) {\n    _cache[key] = value;\n  }\n\n  /// Looks up the value for the provided [key].\n  /// Defaults to `null` if no value exists for the provided key.\n  T? read<T extends Object>({required String key}) {\n    final value = _cache[key];\n    if (value is T) return value;\n    return null;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/cache/pubspec.yaml",
    "content": "name: cache\ndescription: A simple in memory cache made for dart\nversion: 1.0.0\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndev_dependencies:\n  test: ^1.17.11\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/cache/test/cache_test.dart",
    "content": "import 'package:cache/cache.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('CacheClient', () {\n    test('can write and read a value for a given key', () {\n      final cache = CacheClient();\n      const key = '__key__';\n      const value = '__value__';\n      expect(cache.read(key: key), isNull);\n      cache.write(key: key, value: value);\n      expect(cache.read(key: key), equals(value));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/lib/form_inputs.dart",
    "content": "export './src/confirmed_password.dart';\nexport './src/email.dart';\nexport './src/password.dart';\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/lib/src/confirmed_password.dart",
    "content": "import 'package:formz/formz.dart';\n\n/// Validation errors for the [ConfirmedPassword] [FormzInput].\nenum ConfirmedPasswordValidationError {\n  /// Generic invalid error.\n  invalid,\n}\n\n/// {@template confirmed_password}\n/// Form input for a confirmed password input.\n/// {@endtemplate}\nclass ConfirmedPassword\n    extends FormzInput<String, ConfirmedPasswordValidationError> {\n  /// {@macro confirmed_password}\n  const ConfirmedPassword.pure({this.password = ''}) : super.pure('');\n\n  /// {@macro confirmed_password}\n  const ConfirmedPassword.dirty({required this.password, String value = ''})\n    : super.dirty(value);\n\n  /// The original password.\n  final String password;\n\n  @override\n  ConfirmedPasswordValidationError? validator(String? value) {\n    return password == value ? null : ConfirmedPasswordValidationError.invalid;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/lib/src/email.dart",
    "content": "import 'package:formz/formz.dart';\n\n/// Validation errors for the [Email] [FormzInput].\nenum EmailValidationError {\n  /// Generic invalid error.\n  invalid,\n}\n\n/// {@template email}\n/// Form input for an email input.\n/// {@endtemplate}\nclass Email extends FormzInput<String, EmailValidationError> {\n  /// {@macro email}\n  const Email.pure() : super.pure('');\n\n  /// {@macro email}\n  const Email.dirty([super.value = '']) : super.dirty();\n\n  static final RegExp _emailRegExp = RegExp(\n    r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$',\n  );\n\n  @override\n  EmailValidationError? validator(String? value) {\n    return _emailRegExp.hasMatch(value ?? '')\n        ? null\n        : EmailValidationError.invalid;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/lib/src/password.dart",
    "content": "import 'package:formz/formz.dart';\n\n/// Validation errors for the [Password] [FormzInput].\nenum PasswordValidationError {\n  /// Generic invalid error.\n  invalid,\n}\n\n/// {@template password}\n/// Form input for an password input.\n/// {@endtemplate}\nclass Password extends FormzInput<String, PasswordValidationError> {\n  /// {@macro password}\n  const Password.pure() : super.pure('');\n\n  /// {@macro password}\n  const Password.dirty([super.value = '']) : super.dirty();\n\n  static final _passwordRegExp = RegExp(\n    r'^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$',\n  );\n\n  @override\n  PasswordValidationError? validator(String? value) {\n    return _passwordRegExp.hasMatch(value ?? '')\n        ? null\n        : PasswordValidationError.invalid;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/packages/form_inputs/pubspec.yaml",
    "content": "name: form_inputs\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  formz: ^0.8.0\n"
  },
  {
    "path": "examples/flutter_firebase_login/pubspec.yaml",
    "content": "name: flutter_firebase_login\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  authentication_repository:\n    path: packages/authentication_repository\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  firebase_core: ^3.0.0\n  flow_builder: ^0.1.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  font_awesome_flutter: ^10.1.0\n  form_inputs:\n    path: packages/form_inputs\n  formz: ^0.8.0\n  google_fonts: ^6.0.0\n  meta: ^1.7.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n  assets:\n    - assets/\n"
  },
  {
    "path": "examples/flutter_firebase_login/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/app/bloc/app_bloc_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nclass MockUser extends Mock implements User {}\n\nvoid main() {\n  group(AppBloc, () {\n    final user = MockUser();\n    late AuthenticationRepository authenticationRepository;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n      when(() => authenticationRepository.user).thenAnswer(\n        (_) => const Stream.empty(),\n      );\n      when(\n        () => authenticationRepository.currentUser,\n      ).thenReturn(user);\n    });\n\n    AppBloc buildBloc() {\n      return AppBloc(\n        authenticationRepository: authenticationRepository,\n      );\n    }\n\n    test('initial state is $AppState', () {\n      expect(buildBloc().state, equals(AppState(user: user)));\n    });\n\n    group(AppUserSubscriptionRequested, () {\n      final error = Exception('oops');\n\n      blocTest<AppBloc, AppState>(\n        'emits $AppState when user stream emits a new value',\n        setUp: () {\n          when(() => authenticationRepository.user).thenAnswer(\n            (_) => Stream.value(user),\n          );\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AppUserSubscriptionRequested()),\n        expect: () => [AppState(user: user)],\n      );\n\n      blocTest<AppBloc, AppState>(\n        'adds error when user stream emits an error',\n        setUp: () {\n          when(\n            () => authenticationRepository.user,\n          ).thenAnswer((_) => Stream.error(error));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AppUserSubscriptionRequested()),\n        errors: () => [error],\n      );\n    });\n\n    group(AppLogoutPressed, () {\n      blocTest<AppBloc, AppState>(\n        'invokes logOut',\n        setUp: () {\n          when(\n            () => authenticationRepository.logOut(),\n          ).thenAnswer((_) async {});\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AppLogoutPressed()),\n        verify: (_) {\n          verify(() => authenticationRepository.logOut()).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/app/bloc/app_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockUser extends Mock implements User {}\n\nvoid main() {\n  group(AppState, () {\n    test('returns state with status unauthenticated '\n        'when user is empty', () {\n      expect(AppState().status, equals(AppStatus.unauthenticated));\n    });\n\n    test('returns state with status authenticated and user '\n        'when user is not empty', () {\n      final user = MockUser();\n      final state = AppState(user: user);\n      expect(state.status, equals(AppStatus.authenticated));\n      expect(state.user, equals(user));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/app/bloc_observer_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass FakeBloc extends Fake implements Bloc<Object, Object> {}\n\nclass FakeCubit extends Fake implements Cubit<Object> {}\n\nclass FakeEvent extends Fake implements Object {}\n\nclass FakeStackTrace extends Fake implements StackTrace {}\n\nclass FakeChange extends Fake implements Change<Object> {}\n\nclass FakeTransition extends Fake implements Transition<Object, Object> {}\n\nvoid main() {\n  group('AppBlocObserver', () {\n    setUp(logs.clear);\n\n    test(\n      'onEvent prints event',\n      overridePrint(() {\n        final bloc = FakeBloc();\n        final event = FakeEvent();\n        AppBlocObserver().onEvent(bloc, event);\n        expect(logs, equals(['$event']));\n      }),\n    );\n\n    test(\n      'onError prints error',\n      overridePrint(() {\n        final bloc = FakeBloc();\n        final error = Object();\n        final stackTrace = FakeStackTrace();\n        AppBlocObserver().onError(bloc, error, stackTrace);\n        expect(logs, equals(['$error']));\n      }),\n    );\n\n    test(\n      'onChange prints change',\n      overridePrint(() {\n        final cubit = FakeCubit();\n        final change = FakeChange();\n        AppBlocObserver().onChange(cubit, change);\n        expect(logs, equals(['$change']));\n      }),\n    );\n\n    test(\n      'onTransition prints transition',\n      overridePrint(() {\n        final bloc = FakeBloc();\n        final transition = FakeTransition();\n        AppBlocObserver().onTransition(bloc, transition);\n        expect(logs, equals(['$transition']));\n      }),\n    );\n  });\n}\n\nfinal logs = <String>[];\n\nvoid Function() overridePrint(void Function() testFn) {\n  return () {\n    final spec = ZoneSpecification(\n      print: (_, _, _, String msg) => logs.add(msg),\n    );\n    return Zone.current.fork(specification: spec).run<void>(testFn);\n  };\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/app/routes/routes_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('onGenerateAppViewPages', () {\n    test('returns [HomePage] when authenticated', () {\n      expect(\n        onGenerateAppViewPages(AppStatus.authenticated, []),\n        [\n          isA<MaterialPage<void>>().having(\n            (p) => p.child,\n            'child',\n            isA<HomePage>(),\n          ),\n        ],\n      );\n    });\n\n    test('returns [LoginPage] when unauthenticated', () {\n      expect(\n        onGenerateAppViewPages(AppStatus.unauthenticated, []),\n        [\n          isA<MaterialPage<void>>().having(\n            (p) => p.child,\n            'child',\n            isA<LoginPage>(),\n          ),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/app/view/app_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockUser extends Mock implements User {}\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nclass MockAppBloc extends MockBloc<AppEvent, AppState> implements AppBloc {}\n\nvoid main() {\n  group('App', () {\n    late AuthenticationRepository authenticationRepository;\n    late User user;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n      user = MockUser();\n      when(() => authenticationRepository.user).thenAnswer(\n        (_) => const Stream.empty(),\n      );\n      when(() => authenticationRepository.currentUser).thenReturn(user);\n      when(() => user.email).thenReturn('test@gmail.com');\n    });\n\n    testWidgets('renders AppView', (tester) async {\n      await tester.pumpWidget(\n        App(authenticationRepository: authenticationRepository),\n      );\n      await tester.pump();\n      expect(find.byType(AppView), findsOneWidget);\n    });\n  });\n\n  group('AppView', () {\n    late AuthenticationRepository authenticationRepository;\n    late AppBloc appBloc;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n      appBloc = MockAppBloc();\n    });\n\n    testWidgets('navigates to LoginPage when unauthenticated', (tester) async {\n      when(() => appBloc.state).thenReturn(AppState());\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: authenticationRepository,\n          child: MaterialApp(\n            home: BlocProvider.value(value: appBloc, child: const AppView()),\n          ),\n        ),\n      );\n      await tester.pumpAndSettle();\n      expect(find.byType(LoginPage), findsOneWidget);\n    });\n\n    testWidgets('navigates to HomePage when authenticated', (tester) async {\n      final user = MockUser();\n      when(() => user.email).thenReturn('test@gmail.com');\n      when(() => appBloc.state).thenReturn(AppState(user: user));\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: authenticationRepository,\n          child: MaterialApp(\n            home: BlocProvider.value(value: appBloc, child: const AppView()),\n          ),\n        ),\n      );\n      await tester.pumpAndSettle();\n      expect(find.byType(HomePage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/home/view/home_page_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/app/app.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAppBloc extends MockBloc<AppEvent, AppState> implements AppBloc {}\n\nclass MockUser extends Mock implements User {}\n\nvoid main() {\n  const logoutButtonKey = Key('homePage_logout_iconButton');\n  group('HomePage', () {\n    late AppBloc appBloc;\n    late User user;\n\n    setUp(() {\n      appBloc = MockAppBloc();\n      user = MockUser();\n      when(() => user.email).thenReturn('test@gmail.com');\n      when(() => appBloc.state).thenReturn(AppState(user: user));\n    });\n\n    group('calls', () {\n      testWidgets('AppLogoutPressed when logout is pressed', (tester) async {\n        await tester.pumpWidget(\n          BlocProvider.value(\n            value: appBloc,\n            child: const MaterialApp(home: HomePage()),\n          ),\n        );\n        await tester.tap(find.byKey(logoutButtonKey));\n        verify(() => appBloc.add(const AppLogoutPressed())).called(1);\n      });\n    });\n\n    group('renders', () {\n      testWidgets('avatar widget', (tester) async {\n        await tester.pumpWidget(\n          BlocProvider.value(\n            value: appBloc,\n            child: const MaterialApp(home: HomePage()),\n          ),\n        );\n        expect(find.byType(Avatar), findsOneWidget);\n      });\n\n      testWidgets('email address', (tester) async {\n        await tester.pumpWidget(\n          BlocProvider.value(\n            value: appBloc,\n            child: const MaterialApp(home: HomePage()),\n          ),\n        );\n        expect(find.text('test@gmail.com'), findsOneWidget);\n      });\n\n      testWidgets('name', (tester) async {\n        when(() => user.name).thenReturn('Joe');\n        await tester.pumpWidget(\n          BlocProvider.value(\n            value: appBloc,\n            child: const MaterialApp(home: HomePage()),\n          ),\n        );\n        expect(find.text('Joe'), findsOneWidget);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/home/widgets/avatar_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'dart:io';\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_firebase_login/home/home.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const imageUrl = 'https://www.fnordware.com/superpng/pngtest16rgba.png';\n  group('Avatar', () {\n    setUpAll(() => HttpOverrides.global = null);\n\n    testWidgets('renders CircleAvatar', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: Avatar()));\n      expect(find.byType(CircleAvatar), findsOneWidget);\n    });\n\n    testWidgets('has correct radius', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: Avatar()));\n      final avatar = tester.widget<CircleAvatar>(find.byType(CircleAvatar));\n      expect(avatar.radius, 48);\n    });\n\n    testWidgets('renders backgroundImage if photo is not null', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: Avatar(photo: imageUrl)));\n      final avatar = tester.widget<CircleAvatar>(find.byType(CircleAvatar));\n      expect(avatar.backgroundImage, isNotNull);\n      await tester.pumpAndSettle();\n    });\n\n    testWidgets('renders icon if photo is null', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: Avatar()));\n      final avatar = tester.widget<CircleAvatar>(find.byType(CircleAvatar));\n      expect(avatar.backgroundImage, isNull);\n      final icon = avatar.child! as Icon;\n      expect(icon.icon, Icons.person_outline);\n      expect(icon.size, 48);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/login/cubit/login_cubit_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  const invalidEmail = 'invalid';\n  const validEmail = 'test@gmail.com';\n  const invalidPassword = 'invalid';\n  const validPassword = 't0pS3cret1234';\n\n  group('LoginCubit', () {\n    late AuthenticationRepository authenticationRepository;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n      when(\n        () => authenticationRepository.logInWithGoogle(),\n      ).thenAnswer((_) async {});\n      when(\n        () => authenticationRepository.logInWithEmailAndPassword(\n          email: any(named: 'email'),\n          password: any(named: 'password'),\n        ),\n      ).thenAnswer((_) async {});\n    });\n\n    test('initial state is LoginState', () {\n      expect(LoginCubit(authenticationRepository).state, LoginState());\n    });\n\n    group('emailChanged', () {\n      blocTest<LoginCubit, LoginState>(\n        'emits [invalid] when email/password are invalid',\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.emailChanged(invalidEmail),\n        expect: () => <LoginState>[LoginState().withEmail(invalidEmail)],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [valid] when email/password are valid',\n        build: () => LoginCubit(authenticationRepository),\n        seed: () => LoginState().withPassword(validPassword),\n        act: (cubit) => cubit.emailChanged(validEmail),\n        expect: () => <LoginState>[\n          LoginState().withEmail(validEmail).withPassword(validPassword),\n        ],\n      );\n    });\n\n    group('passwordChanged', () {\n      blocTest<LoginCubit, LoginState>(\n        'emits [invalid] when email/password are invalid',\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.passwordChanged(invalidPassword),\n        expect: () => <LoginState>[LoginState().withPassword(invalidPassword)],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [valid] when email/password are valid',\n        build: () => LoginCubit(authenticationRepository),\n        seed: () => LoginState().withEmail(validEmail),\n        act: (cubit) => cubit.passwordChanged(validPassword),\n        expect: () => <LoginState>[\n          LoginState().withEmail(validEmail).withPassword(validPassword),\n        ],\n      );\n    });\n\n    group('logInWithCredentials', () {\n      blocTest<LoginCubit, LoginState>(\n        'does nothing when status is not validated',\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.logInWithCredentials(),\n        expect: () => const <LoginState>[],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'calls logInWithEmailAndPassword with correct email/password',\n        build: () => LoginCubit(authenticationRepository),\n        seed: () {\n          return LoginState().withEmail(validEmail).withPassword(validPassword);\n        },\n        act: (cubit) => cubit.logInWithCredentials(),\n        verify: (_) {\n          verify(\n            () => authenticationRepository.logInWithEmailAndPassword(\n              email: validEmail,\n              password: validPassword,\n            ),\n          ).called(1);\n        },\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [submissionInProgress, submissionSuccess] '\n        'when logInWithEmailAndPassword succeeds',\n        build: () => LoginCubit(authenticationRepository),\n        seed: () {\n          return LoginState().withEmail(validEmail).withPassword(validPassword);\n        },\n        act: (cubit) => cubit.logInWithCredentials(),\n        expect: () => <LoginState>[\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionInProgress(),\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionSuccess(),\n        ],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [submissionInProgress, submissionFailure] '\n        'when logInWithEmailAndPassword fails '\n        'due to LogInWithEmailAndPasswordFailure',\n        setUp: () {\n          when(\n            () => authenticationRepository.logInWithEmailAndPassword(\n              email: any(named: 'email'),\n              password: any(named: 'password'),\n            ),\n          ).thenThrow(LogInWithEmailAndPasswordFailure('oops'));\n        },\n        build: () => LoginCubit(authenticationRepository),\n        seed: () {\n          return LoginState().withEmail(validEmail).withPassword(validPassword);\n        },\n        act: (cubit) => cubit.logInWithCredentials(),\n        expect: () => <LoginState>[\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionInProgress(),\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionFailure('oops'),\n        ],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [submissionInProgress, submissionFailure] '\n        'when logInWithEmailAndPassword fails due to generic exception',\n        setUp: () {\n          when(\n            () => authenticationRepository.logInWithEmailAndPassword(\n              email: any(named: 'email'),\n              password: any(named: 'password'),\n            ),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => LoginCubit(authenticationRepository),\n        seed: () {\n          return LoginState().withEmail(validEmail).withPassword(validPassword);\n        },\n        act: (cubit) => cubit.logInWithCredentials(),\n        expect: () => <LoginState>[\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionInProgress(),\n          LoginState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withSubmissionFailure(),\n        ],\n      );\n    });\n\n    group('logInWithGoogle', () {\n      blocTest<LoginCubit, LoginState>(\n        'calls logInWithGoogle',\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.logInWithGoogle(),\n        verify: (_) {\n          verify(() => authenticationRepository.logInWithGoogle()).called(1);\n        },\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [inProgress, success] '\n        'when logInWithGoogle succeeds',\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.logInWithGoogle(),\n        expect: () => <LoginState>[\n          LoginState().withSubmissionInProgress(),\n          LoginState().withSubmissionSuccess(),\n        ],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [inProgress, failure] '\n        'when logInWithGoogle fails due to LogInWithGoogleFailure',\n        setUp: () {\n          when(\n            () => authenticationRepository.logInWithGoogle(),\n          ).thenThrow(LogInWithGoogleFailure('oops'));\n        },\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.logInWithGoogle(),\n        expect: () => <LoginState>[\n          LoginState().withSubmissionInProgress(),\n          LoginState().withSubmissionFailure('oops'),\n        ],\n      );\n\n      blocTest<LoginCubit, LoginState>(\n        'emits [inProgress, failure] '\n        'when logInWithGoogle fails due to generic exception',\n        setUp: () {\n          when(\n            () => authenticationRepository.logInWithGoogle(),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => LoginCubit(authenticationRepository),\n        act: (cubit) => cubit.logInWithGoogle(),\n        expect: () => <LoginState>[\n          LoginState().withSubmissionInProgress(),\n          LoginState().withSubmissionFailure(),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/login/cubit/login_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const email = 'test@gmail.com';\n  const password = 'Test1234';\n\n  group('LoginState', () {\n    test('supports value comparisons', () {\n      expect(LoginState(), LoginState());\n    });\n\n    group('isValid', () {\n      test('is false for initial state', () {\n        expect(LoginState().isValid, isFalse);\n      });\n\n      test('is true when validation succeeds', () {\n        expect(\n          LoginState().withEmail(email).withPassword(password).isValid,\n          isTrue,\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/login/view/login_form_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nclass MockLoginCubit extends MockCubit<LoginState> implements LoginCubit {}\n\nvoid main() {\n  const loginButtonKey = Key('loginForm_continue_raisedButton');\n  const signInWithGoogleButtonKey = Key('loginForm_googleLogin_raisedButton');\n  const emailInputKey = Key('loginForm_emailInput_textField');\n  const passwordInputKey = Key('loginForm_passwordInput_textField');\n  const createAccountButtonKey = Key('loginForm_createAccount_flatButton');\n\n  const validEmail = 'test@gmail.com';\n  const validPassword = 'Test1234';\n  const invalidEmail = 'invalid';\n  const invalidPassword = 'invalid';\n\n  group('LoginForm', () {\n    late LoginCubit loginCubit;\n\n    setUp(() {\n      loginCubit = MockLoginCubit();\n      when(() => loginCubit.state).thenReturn(const LoginState());\n      when(() => loginCubit.logInWithGoogle()).thenAnswer((_) async {});\n      when(() => loginCubit.logInWithCredentials()).thenAnswer((_) async {});\n    });\n\n    group('calls', () {\n      testWidgets('emailChanged when email changes', (tester) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(find.byKey(emailInputKey), validEmail);\n        verify(() => loginCubit.emailChanged(validEmail)).called(1);\n      });\n\n      testWidgets('passwordChanged when password changes', (tester) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(find.byKey(passwordInputKey), validPassword);\n        verify(() => loginCubit.passwordChanged(validPassword)).called(1);\n      });\n\n      testWidgets('logInWithCredentials when login button is pressed', (\n        tester,\n      ) async {\n        when(() => loginCubit.state).thenReturn(\n          const LoginState().withEmail(validEmail).withPassword(validPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.tap(find.byKey(loginButtonKey));\n        verify(() => loginCubit.logInWithCredentials()).called(1);\n      });\n\n      testWidgets(\n        'logInWithGoogle when sign in with google button is pressed',\n        (tester) async {\n          await tester.pumpWidget(\n            MaterialApp(\n              home: Scaffold(\n                body: BlocProvider.value(\n                  value: loginCubit,\n                  child: const LoginForm(),\n                ),\n              ),\n            ),\n          );\n          await tester.tap(find.byKey(signInWithGoogleButtonKey));\n          verify(() => loginCubit.logInWithGoogle()).called(1);\n        },\n      );\n    });\n\n    group('renders', () {\n      testWidgets('AuthenticationFailure SnackBar when submission fails', (\n        tester,\n      ) async {\n        whenListen(\n          loginCubit,\n          Stream.fromIterable(<LoginState>[\n            const LoginState().withSubmissionInProgress(),\n            const LoginState().withSubmissionFailure(),\n          ]),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.pump();\n        expect(find.text('Authentication Failure'), findsOneWidget);\n      });\n\n      testWidgets('invalid email error text when email is invalid', (\n        tester,\n      ) async {\n        when(() => loginCubit.state).thenReturn(\n          const LoginState().withEmail(invalidEmail),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.text('invalid email'), findsOneWidget);\n      });\n\n      testWidgets('invalid password error text when password is invalid', (\n        tester,\n      ) async {\n        when(() => loginCubit.state).thenReturn(\n          const LoginState().withPassword(invalidPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.text('invalid password'), findsOneWidget);\n      });\n\n      testWidgets('disabled login button when status is not validated', (\n        tester,\n      ) async {\n        when(() => loginCubit.state).thenReturn(const LoginState());\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        final loginButton = tester.widget<ElevatedButton>(\n          find.byKey(loginButtonKey),\n        );\n        expect(loginButton.enabled, isFalse);\n      });\n\n      testWidgets('enabled login button when status is validated', (\n        tester,\n      ) async {\n        when(() => loginCubit.state).thenReturn(\n          const LoginState().withEmail(validEmail).withPassword(validPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        final loginButton = tester.widget<ElevatedButton>(\n          find.byKey(loginButtonKey),\n        );\n        expect(loginButton.enabled, isTrue);\n      });\n\n      testWidgets('Sign in with Google Button', (tester) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginCubit,\n                child: const LoginForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.byKey(signInWithGoogleButtonKey), findsOneWidget);\n      });\n    });\n\n    group('navigates', () {\n      testWidgets('to SignUpPage when Create Account is pressed', (\n        tester,\n      ) async {\n        await tester.pumpWidget(\n          RepositoryProvider<AuthenticationRepository>(\n            create: (_) => MockAuthenticationRepository(),\n            child: MaterialApp(\n              home: Scaffold(\n                body: BlocProvider.value(\n                  value: loginCubit,\n                  child: const LoginForm(),\n                ),\n              ),\n            ),\n          ),\n        );\n        await tester.tap(find.byKey(createAccountButtonKey));\n        await tester.pumpAndSettle();\n        expect(find.byType(SignUpPage), findsOneWidget);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/login/view/login_page_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  group('LoginPage', () {\n    test('has a page', () {\n      expect(LoginPage.page(), isA<MaterialPage<void>>());\n    });\n\n    testWidgets('renders a LoginForm', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider<AuthenticationRepository>(\n          create: (_) => MockAuthenticationRepository(),\n          child: const MaterialApp(home: LoginPage()),\n        ),\n      );\n      expect(find.byType(LoginForm), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/sign_up/cubit/sign_up_cubit_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  const invalidEmail = 'invalid';\n  const validEmail = 'test@gmail.com';\n  const invalidPassword = 'invalid';\n  const validPassword = 't0pS3cret1234';\n  const invalidConfirmedPassword = 'invalid';\n  const validConfirmedPassword = 't0pS3cret1234';\n\n  group('SignUpCubit', () {\n    late AuthenticationRepository authenticationRepository;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n      when(\n        () => authenticationRepository.signUp(\n          email: any(named: 'email'),\n          password: any(named: 'password'),\n        ),\n      ).thenAnswer((_) async {});\n    });\n\n    test('initial state is SignUpState', () {\n      expect(\n        SignUpCubit(authenticationRepository).state,\n        SignUpState(),\n      );\n    });\n\n    group('emailChanged', () {\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [invalid] when email/password/confirmedPassword are invalid',\n        build: () => SignUpCubit(authenticationRepository),\n        act: (cubit) => cubit.emailChanged(invalidEmail),\n        expect: () => <SignUpState>[SignUpState().withEmail(invalidEmail)],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [valid] when email/password/confirmedPassword are valid',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withPassword(validPassword)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.emailChanged(validEmail),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword),\n        ],\n      );\n    });\n\n    group('passwordChanged', () {\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [invalid] when email/password/confirmedPassword are invalid',\n        build: () => SignUpCubit(authenticationRepository),\n        act: (cubit) => cubit.passwordChanged(invalidPassword),\n        expect: () => <SignUpState>[\n          SignUpState().withPassword(invalidPassword),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [valid] when email/password/confirmedPassword are valid',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withEmail(validEmail)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.passwordChanged(validPassword),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [valid] when confirmedPasswordChanged is called first and then '\n        'passwordChanged is called',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState().withEmail(validEmail),\n        act: (cubit) => cubit\n          ..confirmedPasswordChanged(validConfirmedPassword)\n          ..passwordChanged(validPassword),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withConfirmedPassword(validConfirmedPassword),\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword),\n        ],\n      );\n    });\n\n    group('confirmedPasswordChanged', () {\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [invalid] when email/password/confirmedPassword are invalid',\n        build: () => SignUpCubit(authenticationRepository),\n        act: (cubit) {\n          cubit.confirmedPasswordChanged(invalidConfirmedPassword);\n        },\n        expect: () => <SignUpState>[\n          SignUpState().withConfirmedPassword(invalidConfirmedPassword),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [valid] when email/password/confirmedPassword are valid',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () {\n          return SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword);\n        },\n        act: (cubit) => cubit.confirmedPasswordChanged(validConfirmedPassword),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [valid] when passwordChanged is called first and then '\n        'confirmedPasswordChanged is called',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState().withEmail(validEmail),\n        act: (cubit) => cubit\n          ..passwordChanged(validPassword)\n          ..confirmedPasswordChanged(validConfirmedPassword),\n        expect: () => <SignUpState>[\n          SignUpState().withEmail(validEmail).withPassword(validPassword),\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword),\n        ],\n      );\n    });\n\n    group('signUpFormSubmitted', () {\n      blocTest<SignUpCubit, SignUpState>(\n        'does nothing when status is not validated',\n        build: () => SignUpCubit(authenticationRepository),\n        act: (cubit) => cubit.signUpFormSubmitted(),\n        expect: () => const <SignUpState>[],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'calls signUp with correct email/password/confirmedPassword',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withEmail(validEmail)\n            .withPassword(validPassword)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.signUpFormSubmitted(),\n        verify: (_) {\n          verify(\n            () => authenticationRepository.signUp(\n              email: validEmail,\n              password: validPassword,\n            ),\n          ).called(1);\n        },\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [inProgress, success] '\n        'when signUp succeeds',\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withEmail(validEmail)\n            .withPassword(validPassword)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.signUpFormSubmitted(),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionInProgress(),\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionSuccess(),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [inProgress, failure] '\n        'when signUp fails due to SignUpWithEmailAndPasswordFailure',\n        setUp: () {\n          when(\n            () => authenticationRepository.signUp(\n              email: any(named: 'email'),\n              password: any(named: 'password'),\n            ),\n          ).thenThrow(SignUpWithEmailAndPasswordFailure('oops'));\n        },\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withEmail(validEmail)\n            .withPassword(validPassword)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.signUpFormSubmitted(),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionInProgress(),\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionFailure('oops'),\n        ],\n      );\n\n      blocTest<SignUpCubit, SignUpState>(\n        'emits [inProgress, failure] '\n        'when signUp fails due to generic exception',\n        setUp: () {\n          when(\n            () => authenticationRepository.signUp(\n              email: any(named: 'email'),\n              password: any(named: 'password'),\n            ),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => SignUpCubit(authenticationRepository),\n        seed: () => SignUpState()\n            .withEmail(validEmail)\n            .withPassword(validPassword)\n            .withConfirmedPassword(validConfirmedPassword),\n        act: (cubit) => cubit.signUpFormSubmitted(),\n        expect: () => <SignUpState>[\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionInProgress(),\n          SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validConfirmedPassword)\n              .withSubmissionFailure(),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/sign_up/cubit/sign_up_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const email = 'test@gmail.com';\n  const password = 'Test1234';\n\n  group('SignUpState', () {\n    test('supports value comparisons', () {\n      expect(SignUpState(), SignUpState());\n    });\n\n    group('isValid', () {\n      test('is false for initial state', () {\n        expect(SignUpState().isValid, isFalse);\n      });\n\n      test('is true when validation succeeds', () {\n        expect(\n          SignUpState()\n              .withEmail(email)\n              .withPassword(password)\n              .withConfirmedPassword(password)\n              .isValid,\n          isTrue,\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockSignUpCubit extends MockCubit<SignUpState> implements SignUpCubit {}\n\nvoid main() {\n  const signUpButtonKey = Key('signUpForm_continue_raisedButton');\n  const emailInputKey = Key('signUpForm_emailInput_textField');\n  const passwordInputKey = Key('signUpForm_passwordInput_textField');\n  const confirmedPasswordInputKey = Key(\n    'signUpForm_confirmedPasswordInput_textField',\n  );\n\n  const validEmail = 'test@gmail.com';\n  const validPassword = 'Test1234';\n  const invalidEmail = 'invalid';\n  const invalidPassword = 'invalid';\n\n  group('SignUpForm', () {\n    late SignUpCubit signUpCubit;\n\n    setUp(() {\n      signUpCubit = MockSignUpCubit();\n      when(() => signUpCubit.state).thenReturn(const SignUpState());\n      when(() => signUpCubit.signUpFormSubmitted()).thenAnswer((_) async {});\n    });\n\n    group('calls', () {\n      testWidgets('emailChanged when email changes', (tester) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(find.byKey(emailInputKey), validEmail);\n        verify(() => signUpCubit.emailChanged(validEmail)).called(1);\n      });\n\n      testWidgets('passwordChanged when password changes', (tester) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(find.byKey(passwordInputKey), validPassword);\n        verify(() => signUpCubit.passwordChanged(validPassword)).called(1);\n      });\n\n      testWidgets('confirmedPasswordChanged when confirmedPassword changes', (\n        tester,\n      ) async {\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(\n          find.byKey(confirmedPasswordInputKey),\n          validPassword,\n        );\n        verify(\n          () => signUpCubit.confirmedPasswordChanged(validPassword),\n        ).called(1);\n      });\n\n      testWidgets('signUpFormSubmitted when sign up button is pressed', (\n        tester,\n      ) async {\n        when(() => signUpCubit.state).thenReturn(\n          const SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.tap(find.byKey(signUpButtonKey));\n        verify(() => signUpCubit.signUpFormSubmitted()).called(1);\n      });\n    });\n\n    group('renders', () {\n      testWidgets('Sign Up Failure SnackBar when submission fails', (\n        tester,\n      ) async {\n        whenListen(\n          signUpCubit,\n          Stream.fromIterable(<SignUpState>[\n            const SignUpState(),\n            const SignUpState().withSubmissionFailure(),\n          ]),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.pump();\n        expect(find.text('Sign Up Failure'), findsOneWidget);\n      });\n\n      testWidgets('invalid email error text when email is invalid', (\n        tester,\n      ) async {\n        when(() => signUpCubit.state).thenReturn(\n          const SignUpState()\n              .withEmail(invalidEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.text('invalid email'), findsOneWidget);\n      });\n\n      testWidgets('invalid password error text when password is invalid', (\n        tester,\n      ) async {\n        when(() => signUpCubit.state).thenReturn(\n          const SignUpState()\n              .withEmail(validEmail)\n              .withPassword(invalidPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.text('invalid password'), findsOneWidget);\n      });\n\n      testWidgets('invalid confirmedPassword error text'\n          ' when confirmedPassword is invalid', (tester) async {\n        when(() => signUpCubit.state).thenReturn(\n          const SignUpState().withConfirmedPassword(invalidPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.text('passwords do not match'), findsOneWidget);\n      });\n\n      testWidgets('disabled sign up button when status is not validated', (\n        tester,\n      ) async {\n        when(() => signUpCubit.state).thenReturn(const SignUpState());\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        final signUpButton = tester.widget<ElevatedButton>(\n          find.byKey(signUpButtonKey),\n        );\n        expect(signUpButton.enabled, isFalse);\n      });\n\n      testWidgets('enabled sign up button when status is validated', (\n        tester,\n      ) async {\n        when(() => signUpCubit.state).thenReturn(\n          const SignUpState()\n              .withEmail(validEmail)\n              .withPassword(validPassword)\n              .withConfirmedPassword(validPassword),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        final signUpButton = tester.widget<ElevatedButton>(\n          find.byKey(signUpButtonKey),\n        );\n        expect(signUpButton.enabled, isTrue);\n      });\n    });\n\n    group('navigates', () {\n      testWidgets('back to previous page when submission status is success', (\n        tester,\n      ) async {\n        whenListen(\n          signUpCubit,\n          Stream.fromIterable(<SignUpState>[\n            const SignUpState()\n                .withEmail(validEmail)\n                .withPassword(validPassword)\n                .withConfirmedPassword(validPassword)\n                .withSubmissionInProgress(),\n            const SignUpState()\n                .withEmail(validEmail)\n                .withPassword(validPassword)\n                .withConfirmedPassword(validPassword)\n                .withSubmissionSuccess(),\n          ]),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: signUpCubit,\n                child: const SignUpForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.byType(SignUpForm), findsOneWidget);\n        await tester.pumpAndSettle();\n        expect(find.byType(SignUpForm), findsNothing);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_firebase_login/test/sign_up/view/sign_up_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_firebase_login/sign_up/sign_up.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  group('SignUpPage', () {\n    test('has a route', () {\n      expect(SignUpPage.route(), isA<MaterialPageRoute<void>>());\n    });\n\n    testWidgets('renders a SignUpForm', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider<AuthenticationRepository>(\n          create: (_) => MockAuthenticationRepository(),\n          child: MaterialApp(home: SignUpPage()),\n        ),\n      );\n      expect(find.byType(SignUpForm), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_form_validation/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_form_validation/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_form_validation\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_form_validation/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_form_validation/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/bloc/my_form_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_form_validation/models/models.dart';\nimport 'package:formz/formz.dart';\n\npart 'my_form_event.dart';\npart 'my_form_state.dart';\n\nclass MyFormBloc extends Bloc<MyFormEvent, MyFormState> {\n  MyFormBloc() : super(const MyFormState()) {\n    on<EmailChanged>(_onEmailChanged);\n    on<PasswordChanged>(_onPasswordChanged);\n    on<EmailUnfocused>(_onEmailUnfocused);\n    on<PasswordUnfocused>(_onPasswordUnfocused);\n    on<FormSubmitted>(_onFormSubmitted);\n  }\n\n  void _onEmailChanged(EmailChanged event, Emitter<MyFormState> emit) {\n    final email = Email.dirty(event.email);\n    emit(\n      state.copyWith(\n        email: email.isValid ? email : Email.pure(event.email),\n        isValid: Formz.validate([email, state.password]),\n        status: FormzSubmissionStatus.initial,\n      ),\n    );\n  }\n\n  void _onPasswordChanged(PasswordChanged event, Emitter<MyFormState> emit) {\n    final password = Password.dirty(event.password);\n    emit(\n      state.copyWith(\n        password: password.isValid ? password : Password.pure(event.password),\n        isValid: Formz.validate([state.email, password]),\n        status: FormzSubmissionStatus.initial,\n      ),\n    );\n  }\n\n  void _onEmailUnfocused(EmailUnfocused event, Emitter<MyFormState> emit) {\n    final email = Email.dirty(state.email.value);\n    emit(\n      state.copyWith(\n        email: email,\n        isValid: Formz.validate([email, state.password]),\n        status: FormzSubmissionStatus.initial,\n      ),\n    );\n  }\n\n  void _onPasswordUnfocused(\n    PasswordUnfocused event,\n    Emitter<MyFormState> emit,\n  ) {\n    final password = Password.dirty(state.password.value);\n    emit(\n      state.copyWith(\n        password: password,\n        isValid: Formz.validate([state.email, password]),\n        status: FormzSubmissionStatus.initial,\n      ),\n    );\n  }\n\n  Future<void> _onFormSubmitted(\n    FormSubmitted event,\n    Emitter<MyFormState> emit,\n  ) async {\n    final email = Email.dirty(state.email.value);\n    final password = Password.dirty(state.password.value);\n    emit(\n      state.copyWith(\n        email: email,\n        password: password,\n        isValid: Formz.validate([email, password]),\n        status: FormzSubmissionStatus.initial,\n      ),\n    );\n    if (state.isValid) {\n      emit(state.copyWith(status: FormzSubmissionStatus.inProgress));\n      await Future<void>.delayed(const Duration(seconds: 1));\n      emit(state.copyWith(status: FormzSubmissionStatus.success));\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/bloc/my_form_event.dart",
    "content": "part of 'my_form_bloc.dart';\n\nsealed class MyFormEvent extends Equatable {\n  const MyFormEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class EmailChanged extends MyFormEvent {\n  const EmailChanged({required this.email});\n\n  final String email;\n\n  @override\n  List<Object> get props => [email];\n}\n\nfinal class EmailUnfocused extends MyFormEvent {}\n\nfinal class PasswordChanged extends MyFormEvent {\n  const PasswordChanged({required this.password});\n\n  final String password;\n\n  @override\n  List<Object> get props => [password];\n}\n\nfinal class PasswordUnfocused extends MyFormEvent {}\n\nfinal class FormSubmitted extends MyFormEvent {}\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/bloc/my_form_state.dart",
    "content": "part of 'my_form_bloc.dart';\n\nfinal class MyFormState extends Equatable {\n  const MyFormState({\n    this.email = const Email.pure(),\n    this.password = const Password.pure(),\n    this.isValid = false,\n    this.status = FormzSubmissionStatus.initial,\n  });\n\n  final Email email;\n  final Password password;\n  final bool isValid;\n  final FormzSubmissionStatus status;\n\n  MyFormState copyWith({\n    Email? email,\n    Password? password,\n    bool? isValid,\n    FormzSubmissionStatus? status,\n  }) {\n    return MyFormState(\n      email: email ?? this.email,\n      password: password ?? this.password,\n      isValid: isValid ?? this.isValid,\n      status: status ?? this.status,\n    );\n  }\n\n  @override\n  List<Object> get props => [email, password, status];\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/main.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_form_validation/bloc/my_form_bloc.dart';\nimport 'package:formz/formz.dart';\n\nvoid main() => runApp(const App());\n\nclass App extends StatelessWidget {\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Scaffold(\n        body: BlocProvider(\n          create: (_) => MyFormBloc(),\n          child: const MyForm(),\n        ),\n      ),\n    );\n  }\n}\n\nclass MyForm extends StatefulWidget {\n  const MyForm({super.key});\n\n  @override\n  State<MyForm> createState() => _MyFormState();\n}\n\nclass _MyFormState extends State<MyForm> {\n  final _emailFocusNode = FocusNode();\n  final _passwordFocusNode = FocusNode();\n\n  @override\n  void initState() {\n    super.initState();\n    _emailFocusNode.addListener(() {\n      if (!_emailFocusNode.hasFocus) {\n        context.read<MyFormBloc>().add(EmailUnfocused());\n        FocusScope.of(context).requestFocus(_passwordFocusNode);\n      }\n    });\n    _passwordFocusNode.addListener(() {\n      if (!_passwordFocusNode.hasFocus) {\n        context.read<MyFormBloc>().add(PasswordUnfocused());\n      }\n    });\n  }\n\n  @override\n  void dispose() {\n    _emailFocusNode.dispose();\n    _passwordFocusNode.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<MyFormBloc, MyFormState>(\n      listener: (context, state) {\n        if (state.status.isSuccess) {\n          ScaffoldMessenger.of(context).hideCurrentSnackBar();\n          showDialog<void>(\n            context: context,\n            builder: (_) => const SuccessDialog(),\n          );\n        }\n        if (state.status.isInProgress) {\n          ScaffoldMessenger.of(context)\n            ..hideCurrentSnackBar()\n            ..showSnackBar(\n              const SnackBar(content: Text('Submitting...')),\n            );\n        }\n      },\n      child: Padding(\n        padding: const EdgeInsets.all(16),\n        child: Align(\n          alignment: const Alignment(0, -3 / 4),\n          child: Column(\n            mainAxisSize: MainAxisSize.min,\n            children: <Widget>[\n              EmailInput(focusNode: _emailFocusNode),\n              PasswordInput(focusNode: _passwordFocusNode),\n              const SubmitButton(),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass EmailInput extends StatelessWidget {\n  const EmailInput({required this.focusNode, super.key});\n\n  final FocusNode focusNode;\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<MyFormBloc, MyFormState>(\n      builder: (context, state) {\n        return TextFormField(\n          initialValue: state.email.value,\n          focusNode: focusNode,\n          decoration: InputDecoration(\n            icon: const Icon(Icons.email),\n            labelText: 'Email',\n            helperText: 'A complete, valid email e.g. joe@gmail.com',\n            errorText: state.email.displayError != null\n                ? 'Please ensure the email entered is valid'\n                : null,\n          ),\n          keyboardType: TextInputType.emailAddress,\n          onChanged: (value) {\n            context.read<MyFormBloc>().add(EmailChanged(email: value));\n          },\n          textInputAction: TextInputAction.next,\n        );\n      },\n    );\n  }\n}\n\nclass PasswordInput extends StatelessWidget {\n  const PasswordInput({required this.focusNode, super.key});\n\n  final FocusNode focusNode;\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<MyFormBloc, MyFormState>(\n      builder: (context, state) {\n        return TextFormField(\n          initialValue: state.password.value,\n          focusNode: focusNode,\n          decoration: InputDecoration(\n            icon: const Icon(Icons.lock),\n            helperText:\n                '''Password should be at least 8 characters with at least one letter and number''',\n            helperMaxLines: 2,\n            labelText: 'Password',\n            errorMaxLines: 2,\n            errorText: state.password.displayError != null\n                ? '''Password must be at least 8 characters and contain at least one letter and number'''\n                : null,\n          ),\n          obscureText: true,\n          onChanged: (value) {\n            context.read<MyFormBloc>().add(PasswordChanged(password: value));\n          },\n          textInputAction: TextInputAction.done,\n        );\n      },\n    );\n  }\n}\n\nclass SubmitButton extends StatelessWidget {\n  const SubmitButton({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final isValid = context.select((MyFormBloc bloc) => bloc.state.isValid);\n    return ElevatedButton(\n      onPressed: isValid\n          ? () => context.read<MyFormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  }\n}\n\nclass SuccessDialog extends StatelessWidget {\n  const SuccessDialog({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Dialog(\n      shape: RoundedRectangleBorder(\n        borderRadius: BorderRadius.circular(8),\n      ),\n      child: Padding(\n        padding: const EdgeInsets.all(8),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: <Widget>[\n            const Row(\n              children: <Widget>[\n                Icon(Icons.info),\n                Flexible(\n                  child: Padding(\n                    padding: EdgeInsets.all(10),\n                    child: Text(\n                      'Form Submitted Successfully!',\n                      softWrap: true,\n                    ),\n                  ),\n                ),\n              ],\n            ),\n            ElevatedButton(\n              child: const Text('OK'),\n              onPressed: () => Navigator.of(context).pop(),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/models/email.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum EmailValidationError { invalid }\n\nfinal class Email extends FormzInput<String, EmailValidationError> {\n  const Email.pure([super.value = '']) : super.pure();\n  const Email.dirty([super.value = '']) : super.dirty();\n\n  static final _emailRegex = RegExp(\n    r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$',\n  );\n\n  @override\n  EmailValidationError? validator(String? value) {\n    return _emailRegex.hasMatch(value ?? '')\n        ? null\n        : EmailValidationError.invalid;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/models/models.dart",
    "content": "export 'email.dart';\nexport 'password.dart';\n"
  },
  {
    "path": "examples/flutter_form_validation/lib/models/password.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum PasswordValidationError { invalid }\n\nfinal class Password extends FormzInput<String, PasswordValidationError> {\n  const Password.pure([super.value = '']) : super.pure();\n  const Password.dirty([super.value = '']) : super.dirty();\n\n  static final _passwordRegex = RegExp(\n    r'^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$',\n  );\n\n  @override\n  PasswordValidationError? validator(String? value) {\n    return _passwordRegex.hasMatch(value ?? '')\n        ? null\n        : PasswordValidationError.invalid;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_form_validation/pubspec.yaml",
    "content": "name: flutter_form_validation\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  formz: ^0.8.0\n\nflutter:\n  uses-material-design: true\ndev_dependencies:\n  bloc_lint: ^0.3.0\n"
  },
  {
    "path": "examples/flutter_form_validation/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_form_validation/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_form_validation\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_form_validation</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_form_validation/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_form_validation\",\n    \"short_name\": \"flutter_form_validation\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_infinite_list/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_infinite_list/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_infinite_list\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_infinite_list/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_infinite_list/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\n\nclass App extends MaterialApp {\n  const App({super.key}) : super(home: const PostsPage());\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/main.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_infinite_list/app.dart';\nimport 'package:flutter_infinite_list/simple_bloc_observer.dart';\n\nvoid main() {\n  Bloc.observer = const SimpleBlocObserver();\n  runApp(const App());\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/bloc/post_bloc.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:http/http.dart' as http;\nimport 'package:stream_transform/stream_transform.dart';\n\npart 'post_event.dart';\npart 'post_state.dart';\n\nconst _postLimit = 20;\nconst throttleDuration = Duration(milliseconds: 100);\n\nEventTransformer<E> throttleDroppable<E>(Duration duration) {\n  return (events, mapper) {\n    return droppable<E>().call(events.throttle(duration), mapper);\n  };\n}\n\nclass PostBloc extends Bloc<PostEvent, PostState> {\n  PostBloc({required http.Client httpClient})\n    : _httpClient = httpClient,\n      super(const PostState()) {\n    on<PostFetched>(\n      _onFetched,\n      transformer: throttleDroppable(throttleDuration),\n    );\n  }\n\n  final http.Client _httpClient;\n\n  Future<void> _onFetched(\n    PostFetched event,\n    Emitter<PostState> emit,\n  ) async {\n    if (state.hasReachedMax) return;\n\n    try {\n      final posts = await _fetchPosts(startIndex: state.posts.length);\n\n      if (posts.isEmpty) {\n        return emit(state.copyWith(hasReachedMax: true));\n      }\n\n      emit(\n        state.copyWith(\n          status: PostStatus.success,\n          posts: [...state.posts, ...posts],\n        ),\n      );\n    } catch (_) {\n      emit(state.copyWith(status: PostStatus.failure));\n    }\n  }\n\n  Future<List<Post>> _fetchPosts({required int startIndex}) async {\n    final response = await _httpClient.get(\n      Uri.https(\n        'jsonplaceholder.typicode.com',\n        '/posts',\n        <String, String>{'_start': '$startIndex', '_limit': '$_postLimit'},\n      ),\n    );\n    if (response.statusCode == 200) {\n      final body = json.decode(response.body) as List;\n      return body.map((dynamic json) {\n        final map = json as Map<String, dynamic>;\n        return Post(\n          id: map['id'] as int,\n          title: map['title'] as String,\n          body: map['body'] as String,\n        );\n      }).toList();\n    }\n    throw Exception('error fetching posts');\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/bloc/post_event.dart",
    "content": "part of 'post_bloc.dart';\n\nsealed class PostEvent extends Equatable {\n  @override\n  List<Object> get props => [];\n}\n\nfinal class PostFetched extends PostEvent {}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/bloc/post_state.dart",
    "content": "part of 'post_bloc.dart';\n\nenum PostStatus { initial, success, failure }\n\nfinal class PostState extends Equatable {\n  const PostState({\n    this.status = PostStatus.initial,\n    this.posts = const <Post>[],\n    this.hasReachedMax = false,\n  });\n\n  final PostStatus status;\n  final List<Post> posts;\n  final bool hasReachedMax;\n\n  PostState copyWith({\n    PostStatus? status,\n    List<Post>? posts,\n    bool? hasReachedMax,\n  }) {\n    return PostState(\n      status: status ?? this.status,\n      posts: posts ?? this.posts,\n      hasReachedMax: hasReachedMax ?? this.hasReachedMax,\n    );\n  }\n\n  @override\n  String toString() {\n    return '''PostState { status: $status, hasReachedMax: $hasReachedMax, posts: ${posts.length} }''';\n  }\n\n  @override\n  List<Object> get props => [status, posts, hasReachedMax];\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/models/models.dart",
    "content": "export './post.dart';\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/models/post.dart",
    "content": "import 'package:equatable/equatable.dart';\n\nfinal class Post extends Equatable {\n  const Post({required this.id, required this.title, required this.body});\n\n  final int id;\n  final String title;\n  final String body;\n\n  @override\n  List<Object> get props => [id, title, body];\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/posts.dart",
    "content": "export 'bloc/post_bloc.dart';\nexport 'models/models.dart';\nexport 'view/view.dart';\nexport 'widgets/widgets.dart';\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/view/posts_list.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\n\nclass PostsList extends StatefulWidget {\n  const PostsList({super.key});\n\n  @override\n  State<PostsList> createState() => _PostsListState();\n}\n\nclass _PostsListState extends State<PostsList> {\n  final _scrollController = ScrollController();\n\n  @override\n  void initState() {\n    super.initState();\n    _scrollController.addListener(_onScroll);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<PostBloc, PostState>(\n      builder: (context, state) {\n        switch (state.status) {\n          case PostStatus.failure:\n            return const Center(child: Text('failed to fetch posts'));\n          case PostStatus.success:\n            if (state.posts.isEmpty) {\n              return const Center(child: Text('no posts'));\n            }\n            return ListView.builder(\n              itemBuilder: (BuildContext context, int index) {\n                return index >= state.posts.length\n                    ? const BottomLoader()\n                    : PostListItem(post: state.posts[index]);\n              },\n              itemCount: state.hasReachedMax\n                  ? state.posts.length\n                  : state.posts.length + 1,\n              controller: _scrollController,\n            );\n          case PostStatus.initial:\n            return const Center(child: CircularProgressIndicator());\n        }\n      },\n    );\n  }\n\n  @override\n  void dispose() {\n    _scrollController.dispose();\n    super.dispose();\n  }\n\n  void _onScroll() {\n    if (_isBottom) context.read<PostBloc>().add(PostFetched());\n  }\n\n  bool get _isBottom {\n    if (!_scrollController.hasClients) return false;\n    final maxScroll = _scrollController.position.maxScrollExtent;\n    final currentScroll = _scrollController.offset;\n    return currentScroll >= (maxScroll * 0.9);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/view/posts_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:http/http.dart' as http;\n\nclass PostsPage extends StatelessWidget {\n  const PostsPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: BlocProvider(\n        create: (_) => PostBloc(httpClient: http.Client())..add(PostFetched()),\n        child: const PostsList(),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/view/view.dart",
    "content": "export 'posts_list.dart';\nexport 'posts_page.dart';\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/widgets/bottom_loader.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass BottomLoader extends StatelessWidget {\n  const BottomLoader({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return const Center(\n      child: SizedBox(\n        height: 24,\n        width: 24,\n        child: CircularProgressIndicator(strokeWidth: 1.5),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/widgets/post_list_item.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\n\nclass PostListItem extends StatelessWidget {\n  const PostListItem({required this.post, super.key});\n\n  final Post post;\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    return ListTile(\n      leading: Text('${post.id}', style: textTheme.bodySmall),\n      title: Text(post.title),\n      isThreeLine: true,\n      subtitle: Text(post.body),\n      dense: true,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/posts/widgets/widgets.dart",
    "content": "export 'bottom_loader.dart';\nexport 'post_list_item.dart';\n"
  },
  {
    "path": "examples/flutter_infinite_list/lib/simple_bloc_observer.dart",
    "content": "// ignore_for_file: avoid_print\n\nimport 'package:bloc/bloc.dart';\n\nclass SimpleBlocObserver extends BlocObserver {\n  const SimpleBlocObserver();\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    print(transition);\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    print(error);\n    super.onError(bloc, error, stackTrace);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/macos/.gitignore",
    "content": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
  },
  {
    "path": "examples/flutter_infinite_list/macos/Flutter/GeneratedPluginRegistrant.swift",
    "content": "//\n//  Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\n\nfunc RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/pubspec.yaml",
    "content": "name: flutter_infinite_list\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  bloc_concurrency: ^0.3.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  http: ^1.0.0\n  stream_transform: ^2.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_infinite_list/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/app_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_infinite_list/app.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('App', () {\n    testWidgets('renders PostsPage', (tester) async {\n      await tester.pumpWidget(App());\n      await tester.pumpAndSettle();\n      expect(find.byType(PostsPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/bloc/post_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_infinite_list/posts/bloc/post_bloc.dart';\nimport 'package:flutter_infinite_list/posts/models/post.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:http/http.dart' as http;\nimport 'package:mocktail/mocktail.dart';\n\nclass MockClient extends Mock implements http.Client {}\n\nUri _postsUrl({required int start}) {\n  return Uri.https(\n    'jsonplaceholder.typicode.com',\n    '/posts',\n    <String, String>{'_start': '$start', '_limit': '20'},\n  );\n}\n\nvoid main() {\n  group('PostBloc', () {\n    const mockPosts = [Post(id: 1, title: 'post title', body: 'post body')];\n    const extraMockPosts = [\n      Post(id: 2, title: 'post title', body: 'post body'),\n    ];\n\n    late http.Client httpClient;\n\n    setUpAll(() {\n      registerFallbackValue(Uri());\n    });\n\n    setUp(() {\n      httpClient = MockClient();\n    });\n\n    test('initial state is PostState()', () {\n      expect(PostBloc(httpClient: httpClient).state, const PostState());\n    });\n\n    group('PostFetched', () {\n      blocTest<PostBloc, PostState>(\n        'emits nothing when posts has reached maximum amount',\n        build: () => PostBloc(httpClient: httpClient),\n        seed: () => const PostState(hasReachedMax: true),\n        act: (bloc) => bloc.add(PostFetched()),\n        expect: () => <PostState>[],\n      );\n\n      blocTest<PostBloc, PostState>(\n        'emits successful status when http fetches initial posts',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer((_) async {\n            return http.Response(\n              '[{ \"id\": 1, \"title\": \"post title\", \"body\": \"post body\" }]',\n              200,\n            );\n          });\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        act: (bloc) => bloc.add(PostFetched()),\n        expect: () => const <PostState>[\n          PostState(status: PostStatus.success, posts: mockPosts),\n        ],\n        verify: (_) {\n          verify(() => httpClient.get(_postsUrl(start: 0))).called(1);\n        },\n      );\n\n      blocTest<PostBloc, PostState>(\n        'drops new events when processing current event',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer((_) async {\n            return http.Response(\n              '[{ \"id\": 1, \"title\": \"post title\", \"body\": \"post body\" }]',\n              200,\n            );\n          });\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        act: (bloc) => bloc\n          ..add(PostFetched())\n          ..add(PostFetched()),\n        expect: () => const <PostState>[\n          PostState(status: PostStatus.success, posts: mockPosts),\n        ],\n        verify: (_) {\n          verify(() => httpClient.get(any())).called(1);\n        },\n      );\n\n      blocTest<PostBloc, PostState>(\n        'throttles events',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer((_) async {\n            return http.Response(\n              '[{ \"id\": 1, \"title\": \"post title\", \"body\": \"post body\" }]',\n              200,\n            );\n          });\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        act: (bloc) async {\n          bloc.add(PostFetched());\n          await Future<void>.delayed(Duration.zero);\n          bloc.add(PostFetched());\n        },\n        expect: () => const <PostState>[\n          PostState(status: PostStatus.success, posts: mockPosts),\n        ],\n        verify: (_) {\n          verify(() => httpClient.get(any())).called(1);\n        },\n      );\n\n      blocTest<PostBloc, PostState>(\n        'emits failure status when http fetches posts and throw exception',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer(\n            (_) async => http.Response('', 500),\n          );\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        act: (bloc) => bloc.add(PostFetched()),\n        expect: () => <PostState>[const PostState(status: PostStatus.failure)],\n        verify: (_) {\n          verify(() => httpClient.get(_postsUrl(start: 0))).called(1);\n        },\n      );\n\n      blocTest<PostBloc, PostState>(\n        'emits successful status and reaches max posts when '\n        '0 additional posts are fetched',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer(\n            (_) async => http.Response('[]', 200),\n          );\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        seed: () => const PostState(\n          status: PostStatus.success,\n          posts: mockPosts,\n        ),\n        act: (bloc) => bloc.add(PostFetched()),\n        expect: () => const <PostState>[\n          PostState(\n            status: PostStatus.success,\n            posts: mockPosts,\n            hasReachedMax: true,\n          ),\n        ],\n        verify: (_) {\n          verify(() => httpClient.get(_postsUrl(start: 1))).called(1);\n        },\n      );\n\n      blocTest<PostBloc, PostState>(\n        'emits successful status and does not reach max posts '\n        'when additional posts are fetched',\n        setUp: () {\n          when(() => httpClient.get(any())).thenAnswer((_) async {\n            return http.Response(\n              '[{ \"id\": 2, \"title\": \"post title\", \"body\": \"post body\" }]',\n              200,\n            );\n          });\n        },\n        build: () => PostBloc(httpClient: httpClient),\n        seed: () => const PostState(\n          status: PostStatus.success,\n          posts: mockPosts,\n        ),\n        act: (bloc) => bloc.add(PostFetched()),\n        expect: () => const <PostState>[\n          PostState(\n            status: PostStatus.success,\n            posts: [...mockPosts, ...extraMockPosts],\n          ),\n        ],\n        verify: (_) {\n          verify(() => httpClient.get(_postsUrl(start: 1))).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/bloc/post_event_test.dart",
    "content": "import 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('PostEvent', () {\n    group('PostFetched', () {\n      test('supports value comparison', () {\n        expect(PostFetched(), PostFetched());\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/bloc/post_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('PostState', () {\n    test('supports value comparison', () {\n      expect(PostState(), PostState());\n      expect(\n        PostState().toString(),\n        PostState().toString(),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/models/post_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_infinite_list/posts/models/models.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('Post', () {\n    test('supports value comparison', () {\n      expect(\n        Post(id: 1, title: 'post title', body: 'post body'),\n        Post(id: 1, title: 'post title', body: 'post body'),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/view/posts_list_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockPostBloc extends MockBloc<PostEvent, PostState> implements PostBloc {}\n\nextension on WidgetTester {\n  Future<void> pumpPostsList(PostBloc postBloc) {\n    return pumpWidget(\n      MaterialApp(\n        home: BlocProvider.value(\n          value: postBloc,\n          child: Scaffold(body: PostsList()),\n        ),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  final mockPosts = List.generate(\n    5,\n    (i) => Post(id: i, title: 'post title', body: 'post body'),\n  );\n\n  late PostBloc postBloc;\n\n  setUp(() {\n    postBloc = MockPostBloc();\n  });\n\n  group('PostsList', () {\n    testWidgets('renders CircularProgressIndicator '\n        'when post status is initial', (tester) async {\n      when(() => postBloc.state).thenReturn(const PostState());\n      await tester.pumpPostsList(postBloc);\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n\n    testWidgets('renders no posts text '\n        'when post status is success but with 0 posts', (tester) async {\n      when(() => postBloc.state).thenReturn(\n        const PostState(status: PostStatus.success, hasReachedMax: true),\n      );\n      await tester.pumpPostsList(postBloc);\n      expect(find.text('no posts'), findsOneWidget);\n    });\n\n    testWidgets(\n      'renders 5 posts and a bottom loader when post max is not reached yet',\n      (tester) async {\n        when(() => postBloc.state).thenReturn(\n          PostState(\n            status: PostStatus.success,\n            posts: mockPosts,\n          ),\n        );\n        await tester.pumpPostsList(postBloc);\n        expect(find.byType(PostListItem), findsNWidgets(5));\n        expect(find.byType(BottomLoader), findsOneWidget);\n      },\n    );\n\n    testWidgets('does not render bottom loader when post max is reached', (\n      tester,\n    ) async {\n      when(() => postBloc.state).thenReturn(\n        PostState(\n          status: PostStatus.success,\n          posts: mockPosts,\n          hasReachedMax: true,\n        ),\n      );\n      await tester.pumpPostsList(postBloc);\n      expect(find.byType(BottomLoader), findsNothing);\n    });\n\n    testWidgets('fetches more posts when scrolled to the bottom', (\n      tester,\n    ) async {\n      when(() => postBloc.state).thenReturn(\n        PostState(\n          status: PostStatus.success,\n          posts: List.generate(\n            10,\n            (i) => Post(id: i, title: 'post title', body: 'post body'),\n          ),\n        ),\n      );\n      await tester.pumpPostsList(postBloc);\n      await tester.drag(find.byType(PostsList), const Offset(0, -500));\n      verify(() => postBloc.add(PostFetched())).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/test/posts/view/posts_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_infinite_list/posts/posts.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('PostsPage', () {\n    testWidgets('renders PostList', (tester) async {\n      await tester.pumpWidget(MaterialApp(home: PostsPage()));\n      await tester.pumpAndSettle();\n      expect(find.byType(PostsList), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_infinite_list/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_infinite_list\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_infinite_list</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_infinite_list/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_infinite_list\",\n    \"short_name\": \"flutter_infinite_list\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_login/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_login/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_login/README.md",
    "content": "# flutter_login\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_login/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_login/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_login/lib/app.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/authentication/authentication.dart';\nimport 'package:flutter_login/home/home.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_login/splash/splash.dart';\nimport 'package:user_repository/user_repository.dart';\n\nclass App extends StatelessWidget {\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiRepositoryProvider(\n      providers: [\n        RepositoryProvider(\n          create: (_) => AuthenticationRepository(),\n          dispose: (repository) => repository.dispose(),\n        ),\n        RepositoryProvider(create: (_) => UserRepository()),\n      ],\n      child: BlocProvider(\n        lazy: false,\n        create: (context) => AuthenticationBloc(\n          authenticationRepository: context.read<AuthenticationRepository>(),\n          userRepository: context.read<UserRepository>(),\n        )..add(AuthenticationSubscriptionRequested()),\n        child: const AppView(),\n      ),\n    );\n  }\n}\n\nclass AppView extends StatefulWidget {\n  const AppView({super.key});\n\n  @override\n  State<AppView> createState() => _AppViewState();\n}\n\nclass _AppViewState extends State<AppView> {\n  final _navigatorKey = GlobalKey<NavigatorState>();\n\n  NavigatorState get _navigator => _navigatorKey.currentState!;\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      navigatorKey: _navigatorKey,\n      builder: (context, child) {\n        return BlocListener<AuthenticationBloc, AuthenticationState>(\n          listener: (context, state) {\n            switch (state.status) {\n              case AuthenticationStatus.authenticated:\n                _navigator.pushAndRemoveUntil<void>(\n                  HomePage.route(),\n                  (route) => false,\n                );\n              case AuthenticationStatus.unauthenticated:\n                _navigator.pushAndRemoveUntil<void>(\n                  LoginPage.route(),\n                  (route) => false,\n                );\n              case AuthenticationStatus.unknown:\n                break;\n            }\n          },\n          child: child,\n        );\n      },\n      onGenerateRoute: (_) => SplashPage.route(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/authentication/authentication.dart",
    "content": "export 'bloc/authentication_bloc.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/authentication/bloc/authentication_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:user_repository/user_repository.dart';\n\npart 'authentication_event.dart';\npart 'authentication_state.dart';\n\nclass AuthenticationBloc\n    extends Bloc<AuthenticationEvent, AuthenticationState> {\n  AuthenticationBloc({\n    required AuthenticationRepository authenticationRepository,\n    required UserRepository userRepository,\n  }) : _authenticationRepository = authenticationRepository,\n       _userRepository = userRepository,\n       super(const AuthenticationState.unknown()) {\n    on<AuthenticationSubscriptionRequested>(_onSubscriptionRequested);\n    on<AuthenticationLogoutPressed>(_onLogoutPressed);\n  }\n\n  final AuthenticationRepository _authenticationRepository;\n  final UserRepository _userRepository;\n\n  Future<void> _onSubscriptionRequested(\n    AuthenticationSubscriptionRequested event,\n    Emitter<AuthenticationState> emit,\n  ) {\n    return emit.onEach(\n      _authenticationRepository.status,\n      onData: (status) async {\n        switch (status) {\n          case AuthenticationStatus.unauthenticated:\n            return emit(const AuthenticationState.unauthenticated());\n          case AuthenticationStatus.authenticated:\n            final user = await _tryGetUser();\n            return emit(\n              user != null\n                  ? AuthenticationState.authenticated(user)\n                  : const AuthenticationState.unauthenticated(),\n            );\n          case AuthenticationStatus.unknown:\n            return emit(const AuthenticationState.unknown());\n        }\n      },\n      onError: addError,\n    );\n  }\n\n  void _onLogoutPressed(\n    AuthenticationLogoutPressed event,\n    Emitter<AuthenticationState> emit,\n  ) {\n    _authenticationRepository.logOut();\n  }\n\n  Future<User?> _tryGetUser() async {\n    try {\n      final user = await _userRepository.getUser();\n      return user;\n    } catch (_) {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/authentication/bloc/authentication_event.dart",
    "content": "part of 'authentication_bloc.dart';\n\nsealed class AuthenticationEvent {\n  const AuthenticationEvent();\n}\n\nfinal class AuthenticationSubscriptionRequested extends AuthenticationEvent {}\n\nfinal class AuthenticationLogoutPressed extends AuthenticationEvent {}\n"
  },
  {
    "path": "examples/flutter_login/lib/authentication/bloc/authentication_state.dart",
    "content": "part of 'authentication_bloc.dart';\n\nclass AuthenticationState extends Equatable {\n  const AuthenticationState._({\n    this.status = AuthenticationStatus.unknown,\n    this.user = User.empty,\n  });\n\n  const AuthenticationState.unknown() : this._();\n\n  const AuthenticationState.authenticated(User user)\n    : this._(status: AuthenticationStatus.authenticated, user: user);\n\n  const AuthenticationState.unauthenticated()\n    : this._(status: AuthenticationStatus.unauthenticated);\n\n  final AuthenticationStatus status;\n  final User user;\n\n  @override\n  List<Object> get props => [status, user];\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/home/home.dart",
    "content": "export 'view/home_page.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/home/view/home_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/authentication/authentication.dart';\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  static Route<void> route() {\n    return MaterialPageRoute<void>(builder: (_) => const HomePage());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return const Scaffold(\n      body: Center(\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [_UserId(), _LogoutButton()],\n        ),\n      ),\n    );\n  }\n}\n\nclass _LogoutButton extends StatelessWidget {\n  const _LogoutButton();\n\n  @override\n  Widget build(BuildContext context) {\n    return ElevatedButton(\n      child: const Text('Logout'),\n      onPressed: () {\n        context.read<AuthenticationBloc>().add(AuthenticationLogoutPressed());\n      },\n    );\n  }\n}\n\nclass _UserId extends StatelessWidget {\n  const _UserId();\n\n  @override\n  Widget build(BuildContext context) {\n    final userId = context.select(\n      (AuthenticationBloc bloc) => bloc.state.user.id,\n    );\n\n    return Text('UserID: $userId');\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/bloc/login_bloc.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:formz/formz.dart';\n\npart 'login_event.dart';\npart 'login_state.dart';\n\nclass LoginBloc extends Bloc<LoginEvent, LoginState> {\n  LoginBloc({\n    required AuthenticationRepository authenticationRepository,\n  }) : _authenticationRepository = authenticationRepository,\n       super(const LoginState()) {\n    on<LoginUsernameChanged>(_onUsernameChanged);\n    on<LoginPasswordChanged>(_onPasswordChanged);\n    on<LoginSubmitted>(_onSubmitted);\n  }\n\n  final AuthenticationRepository _authenticationRepository;\n\n  void _onUsernameChanged(\n    LoginUsernameChanged event,\n    Emitter<LoginState> emit,\n  ) {\n    final username = Username.dirty(event.username);\n    emit(\n      state.copyWith(\n        username: username,\n        isValid: Formz.validate([state.password, username]),\n      ),\n    );\n  }\n\n  void _onPasswordChanged(\n    LoginPasswordChanged event,\n    Emitter<LoginState> emit,\n  ) {\n    final password = Password.dirty(event.password);\n    emit(\n      state.copyWith(\n        password: password,\n        isValid: Formz.validate([password, state.username]),\n      ),\n    );\n  }\n\n  Future<void> _onSubmitted(\n    LoginSubmitted event,\n    Emitter<LoginState> emit,\n  ) async {\n    if (state.isValid) {\n      emit(state.copyWith(status: FormzSubmissionStatus.inProgress));\n      try {\n        await _authenticationRepository.logIn(\n          username: state.username.value,\n          password: state.password.value,\n        );\n        emit(state.copyWith(status: FormzSubmissionStatus.success));\n      } catch (_) {\n        emit(state.copyWith(status: FormzSubmissionStatus.failure));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/bloc/login_event.dart",
    "content": "part of 'login_bloc.dart';\n\nsealed class LoginEvent extends Equatable {\n  const LoginEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class LoginUsernameChanged extends LoginEvent {\n  const LoginUsernameChanged(this.username);\n\n  final String username;\n\n  @override\n  List<Object> get props => [username];\n}\n\nfinal class LoginPasswordChanged extends LoginEvent {\n  const LoginPasswordChanged(this.password);\n\n  final String password;\n\n  @override\n  List<Object> get props => [password];\n}\n\nfinal class LoginSubmitted extends LoginEvent {\n  const LoginSubmitted();\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/bloc/login_state.dart",
    "content": "part of 'login_bloc.dart';\n\nfinal class LoginState extends Equatable {\n  const LoginState({\n    this.status = FormzSubmissionStatus.initial,\n    this.username = const Username.pure(),\n    this.password = const Password.pure(),\n    this.isValid = false,\n  });\n\n  final FormzSubmissionStatus status;\n  final Username username;\n  final Password password;\n  final bool isValid;\n\n  LoginState copyWith({\n    FormzSubmissionStatus? status,\n    Username? username,\n    Password? password,\n    bool? isValid,\n  }) {\n    return LoginState(\n      status: status ?? this.status,\n      username: username ?? this.username,\n      password: password ?? this.password,\n      isValid: isValid ?? this.isValid,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, username, password];\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/login.dart",
    "content": "export 'bloc/login_bloc.dart';\nexport 'models/models.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/login/models/models.dart",
    "content": "export 'password.dart';\nexport 'username.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/login/models/password.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum PasswordValidationError { empty }\n\nclass Password extends FormzInput<String, PasswordValidationError> {\n  const Password.pure() : super.pure('');\n  const Password.dirty([super.value = '']) : super.dirty();\n\n  @override\n  PasswordValidationError? validator(String value) {\n    if (value.isEmpty) return PasswordValidationError.empty;\n    return null;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/models/username.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum UsernameValidationError { empty }\n\nclass Username extends FormzInput<String, UsernameValidationError> {\n  const Username.pure() : super.pure('');\n  const Username.dirty([super.value = '']) : super.dirty();\n\n  @override\n  UsernameValidationError? validator(String value) {\n    if (value.isEmpty) return UsernameValidationError.empty;\n    return null;\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/view/login_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:formz/formz.dart';\n\nclass LoginForm extends StatelessWidget {\n  const LoginForm({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<LoginBloc, LoginState>(\n      listener: (context, state) {\n        if (state.status.isFailure) {\n          ScaffoldMessenger.of(context)\n            ..hideCurrentSnackBar()\n            ..showSnackBar(\n              const SnackBar(content: Text('Authentication Failure')),\n            );\n        }\n      },\n      child: Align(\n        alignment: const Alignment(0, -1 / 3),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            _UsernameInput(),\n            const Padding(padding: EdgeInsets.all(12)),\n            _PasswordInput(),\n            const Padding(padding: EdgeInsets.all(12)),\n            _LoginButton(),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass _UsernameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (LoginBloc bloc) => bloc.state.username.displayError,\n    );\n\n    return TextField(\n      key: const Key('loginForm_usernameInput_textField'),\n      onChanged: (username) {\n        context.read<LoginBloc>().add(LoginUsernameChanged(username));\n      },\n      decoration: InputDecoration(\n        labelText: 'username',\n        errorText: displayError != null ? 'invalid username' : null,\n      ),\n    );\n  }\n}\n\nclass _PasswordInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final displayError = context.select(\n      (LoginBloc bloc) => bloc.state.password.displayError,\n    );\n\n    return TextField(\n      key: const Key('loginForm_passwordInput_textField'),\n      onChanged: (password) {\n        context.read<LoginBloc>().add(LoginPasswordChanged(password));\n      },\n      obscureText: true,\n      decoration: InputDecoration(\n        labelText: 'password',\n        errorText: displayError != null ? 'invalid password' : null,\n      ),\n    );\n  }\n}\n\nclass _LoginButton extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final isInProgressOrSuccess = context.select(\n      (LoginBloc bloc) => bloc.state.status.isInProgressOrSuccess,\n    );\n\n    if (isInProgressOrSuccess) return const CircularProgressIndicator();\n\n    final isValid = context.select((LoginBloc bloc) => bloc.state.isValid);\n\n    return ElevatedButton(\n      key: const Key('loginForm_continue_raisedButton'),\n      onPressed: isValid\n          ? () => context.read<LoginBloc>().add(const LoginSubmitted())\n          : null,\n      child: const Text('Login'),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/view/login_page.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/login/login.dart';\n\nclass LoginPage extends StatelessWidget {\n  const LoginPage({super.key});\n\n  static Route<void> route() {\n    return MaterialPageRoute<void>(builder: (_) => const LoginPage());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Padding(\n        padding: const EdgeInsets.all(12),\n        child: BlocProvider(\n          create: (context) => LoginBloc(\n            authenticationRepository: context.read<AuthenticationRepository>(),\n          ),\n          child: const LoginForm(),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/lib/login/view/view.dart",
    "content": "export 'login_form.dart';\nexport 'login_page.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/main.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_login/app.dart';\n\nvoid main() => runApp(const App());\n"
  },
  {
    "path": "examples/flutter_login/lib/splash/splash.dart",
    "content": "export 'view/splash_page.dart';\n"
  },
  {
    "path": "examples/flutter_login/lib/splash/view/splash_page.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass SplashPage extends StatelessWidget {\n  const SplashPage({super.key});\n\n  static Route<void> route() {\n    return MaterialPageRoute<void>(builder: (_) => const SplashPage());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return const Scaffold(\n      body: Center(child: CircularProgressIndicator()),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/packages/authentication_repository/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_login/packages/authentication_repository/lib/authentication_repository.dart",
    "content": "export 'src/authentication_repository.dart';\n"
  },
  {
    "path": "examples/flutter_login/packages/authentication_repository/lib/src/authentication_repository.dart",
    "content": "import 'dart:async';\n\nenum AuthenticationStatus { unknown, authenticated, unauthenticated }\n\nclass AuthenticationRepository {\n  final _controller = StreamController<AuthenticationStatus>();\n\n  Stream<AuthenticationStatus> get status async* {\n    await Future<void>.delayed(const Duration(seconds: 1));\n    yield AuthenticationStatus.unauthenticated;\n    yield* _controller.stream;\n  }\n\n  Future<void> logIn({\n    required String username,\n    required String password,\n  }) async {\n    await Future.delayed(\n      const Duration(milliseconds: 300),\n      () => _controller.add(AuthenticationStatus.authenticated),\n    );\n  }\n\n  void logOut() {\n    _controller.add(AuthenticationStatus.unauthenticated);\n  }\n\n  void dispose() => _controller.close();\n}\n"
  },
  {
    "path": "examples/flutter_login/packages/authentication_repository/pubspec.yaml",
    "content": "name: authentication_repository\ndescription: Dart package which manages the authentication domain.\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/lib/src/models/models.dart",
    "content": "export 'user.dart';\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/lib/src/models/user.dart",
    "content": "import 'package:equatable/equatable.dart';\n\nclass User extends Equatable {\n  const User(this.id);\n\n  final String id;\n\n  @override\n  List<Object> get props => [id];\n\n  static const empty = User('-');\n}\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/lib/src/user_repository.dart",
    "content": "import 'dart:async';\n\nimport 'package:user_repository/src/models/models.dart';\nimport 'package:uuid/uuid.dart';\n\nclass UserRepository {\n  User? _user;\n\n  Future<User?> getUser() async {\n    if (_user != null) return _user;\n    return Future.delayed(\n      const Duration(milliseconds: 300),\n      () => _user = User(const Uuid().v4()),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/lib/user_repository.dart",
    "content": "export 'src/models/models.dart';\nexport 'src/user_repository.dart';\n"
  },
  {
    "path": "examples/flutter_login/packages/user_repository/pubspec.yaml",
    "content": "name: user_repository\ndescription: Dart package which manages the user domain.\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  equatable: ^2.0.0\n  uuid: ^3.0.0\n"
  },
  {
    "path": "examples/flutter_login/pubspec.yaml",
    "content": "name: flutter_login\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  authentication_repository:\n    path: packages/authentication_repository\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  formz: ^0.8.0\n  user_repository:\n    path: packages/user_repository\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_login/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_login/test/authentication/authentication_bloc_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_login/authentication/authentication.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:user_repository/user_repository.dart';\n\nclass _MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nclass _MockUserRepository extends Mock implements UserRepository {}\n\nvoid main() {\n  const user = User('id');\n  late AuthenticationRepository authenticationRepository;\n  late UserRepository userRepository;\n\n  setUp(() {\n    authenticationRepository = _MockAuthenticationRepository();\n    when(\n      () => authenticationRepository.status,\n    ).thenAnswer((_) => const Stream.empty());\n    userRepository = _MockUserRepository();\n  });\n\n  AuthenticationBloc buildBloc() {\n    return AuthenticationBloc(\n      authenticationRepository: authenticationRepository,\n      userRepository: userRepository,\n    );\n  }\n\n  group('AuthenticationBloc', () {\n    test('initial state is AuthenticationState.unknown', () {\n      final authenticationBloc = buildBloc();\n      expect(authenticationBloc.state, const AuthenticationState.unknown());\n      authenticationBloc.close();\n    });\n\n    group('AuthenticationSubscriptionRequested', () {\n      final error = Exception('oops');\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [unauthenticated] when status is unauthenticated',\n        setUp: () {\n          when(() => authenticationRepository.status).thenAnswer(\n            (_) => Stream.value(AuthenticationStatus.unauthenticated),\n          );\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.unauthenticated()],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [authenticated] when status is authenticated',\n        setUp: () {\n          when(() => authenticationRepository.status).thenAnswer(\n            (_) => Stream.value(AuthenticationStatus.authenticated),\n          );\n          when(() => userRepository.getUser()).thenAnswer((_) async => user);\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.authenticated(user)],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [authenticated] when status is authenticated',\n        setUp: () {\n          when(\n            () => authenticationRepository.status,\n          ).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));\n          when(() => userRepository.getUser()).thenAnswer((_) async => user);\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.authenticated(user)],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [unauthenticated] when status is unauthenticated',\n        setUp: () {\n          when(() => authenticationRepository.status).thenAnswer(\n            (_) => Stream.value(AuthenticationStatus.unauthenticated),\n          );\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.unauthenticated()],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [unauthenticated] when status is authenticated '\n        'but getUser fails',\n        setUp: () {\n          when(\n            () => authenticationRepository.status,\n          ).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));\n          when(() => userRepository.getUser()).thenThrow(Exception('oops'));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.unauthenticated()],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [unauthenticated] when status is authenticated '\n        'but getUser returns null',\n        setUp: () {\n          when(\n            () => authenticationRepository.status,\n          ).thenAnswer((_) => Stream.value(AuthenticationStatus.authenticated));\n          when(() => userRepository.getUser()).thenAnswer((_) async => null);\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.unauthenticated()],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'emits [unknown] when status is unknown',\n        setUp: () {\n          when(\n            () => authenticationRepository.status,\n          ).thenAnswer((_) => Stream.value(AuthenticationStatus.unknown));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        expect: () => const [AuthenticationState.unknown()],\n      );\n\n      blocTest<AuthenticationBloc, AuthenticationState>(\n        'adds error when status stream emits an error',\n        setUp: () {\n          when(\n            () => authenticationRepository.status,\n          ).thenAnswer((_) => Stream.error(error));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(AuthenticationSubscriptionRequested()),\n        errors: () => [error],\n      );\n    });\n  });\n\n  group('AuthenticationLogoutPressed', () {\n    blocTest<AuthenticationBloc, AuthenticationState>(\n      'calls logOut on authenticationRepository ',\n      build: buildBloc,\n      act: (bloc) => bloc.add(AuthenticationLogoutPressed()),\n      verify: (_) {\n        verify(() => authenticationRepository.logOut()).called(1);\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/authentication/authentication_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_login/authentication/authentication.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:user_repository/user_repository.dart';\n\nclass MockUser extends Mock implements User {}\n\nvoid main() {\n  group('AuthenticationState', () {\n    group('AuthenticationState.unknown', () {\n      test('supports value comparisons', () {\n        expect(\n          AuthenticationState.unknown(),\n          AuthenticationState.unknown(),\n        );\n      });\n    });\n\n    group('AuthenticationState.authenticated', () {\n      test('supports value comparisons', () {\n        final user = MockUser();\n        expect(\n          AuthenticationState.authenticated(user),\n          AuthenticationState.authenticated(user),\n        );\n      });\n    });\n\n    group('AuthenticationState.unauthenticated', () {\n      test('supports value comparisons', () {\n        expect(\n          AuthenticationState.unauthenticated(),\n          AuthenticationState.unauthenticated(),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/bloc/login_bloc_test.dart",
    "content": "import 'package:authentication_repository/authentication_repository.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:formz/formz.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  late AuthenticationRepository authenticationRepository;\n\n  setUp(() {\n    authenticationRepository = MockAuthenticationRepository();\n  });\n\n  group('LoginBloc', () {\n    test('initial state is LoginState', () {\n      final loginBloc = LoginBloc(\n        authenticationRepository: authenticationRepository,\n      );\n      expect(loginBloc.state, const LoginState());\n    });\n\n    group('LoginSubmitted', () {\n      blocTest<LoginBloc, LoginState>(\n        'emits [submissionInProgress, submissionSuccess] '\n        'when login succeeds',\n        setUp: () {\n          when(\n            () => authenticationRepository.logIn(\n              username: 'username',\n              password: 'password',\n            ),\n          ).thenAnswer((_) => Future<String>.value('user'));\n        },\n        build: () => LoginBloc(\n          authenticationRepository: authenticationRepository,\n        ),\n        act: (bloc) {\n          bloc\n            ..add(const LoginUsernameChanged('username'))\n            ..add(const LoginPasswordChanged('password'))\n            ..add(const LoginSubmitted());\n        },\n        expect: () => const <LoginState>[\n          LoginState(username: Username.dirty('username')),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n          ),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n            status: FormzSubmissionStatus.inProgress,\n          ),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n            status: FormzSubmissionStatus.success,\n          ),\n        ],\n      );\n\n      blocTest<LoginBloc, LoginState>(\n        'emits [LoginInProgress, LoginFailure] when logIn fails',\n        setUp: () {\n          when(\n            () => authenticationRepository.logIn(\n              username: 'username',\n              password: 'password',\n            ),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => LoginBloc(\n          authenticationRepository: authenticationRepository,\n        ),\n        act: (bloc) {\n          bloc\n            ..add(const LoginUsernameChanged('username'))\n            ..add(const LoginPasswordChanged('password'))\n            ..add(const LoginSubmitted());\n        },\n        expect: () => const <LoginState>[\n          LoginState(\n            username: Username.dirty('username'),\n          ),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n          ),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n            status: FormzSubmissionStatus.inProgress,\n          ),\n          LoginState(\n            username: Username.dirty('username'),\n            password: Password.dirty('password'),\n            isValid: true,\n            status: FormzSubmissionStatus.failure,\n          ),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/bloc/login_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const username = 'mock-username';\n  const password = 'mock-password';\n  group('LoginEvent', () {\n    group('LoginUsernameChanged', () {\n      test('supports value comparisons', () {\n        expect(LoginUsernameChanged(username), LoginUsernameChanged(username));\n      });\n    });\n\n    group('LoginPasswordChanged', () {\n      test('supports value comparisons', () {\n        expect(LoginPasswordChanged(password), LoginPasswordChanged(password));\n      });\n    });\n\n    group('LoginSubmitted', () {\n      test('supports value comparisons', () {\n        expect(LoginSubmitted(), LoginSubmitted());\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/bloc/login_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:formz/formz.dart';\n\nvoid main() {\n  const username = Username.dirty('username');\n  const password = Password.dirty('password');\n  group('LoginState', () {\n    test('supports value comparisons', () {\n      expect(LoginState(), LoginState());\n    });\n\n    test('returns same object when no properties are passed', () {\n      expect(LoginState().copyWith(), LoginState());\n    });\n\n    test('returns object with updated status when status is passed', () {\n      expect(\n        LoginState().copyWith(status: FormzSubmissionStatus.initial),\n        LoginState(),\n      );\n    });\n\n    test('returns object with updated username when username is passed', () {\n      expect(\n        LoginState().copyWith(username: username),\n        LoginState(username: username),\n      );\n    });\n\n    test('returns object with updated password when password is passed', () {\n      expect(\n        LoginState().copyWith(password: password),\n        LoginState(password: password),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/models/password_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const passwordString = 'mock-password';\n  group('Password', () {\n    group('constructors', () {\n      test('pure creates correct instance', () {\n        final password = Password.pure();\n        expect(password.value, '');\n        expect(password.isPure, isTrue);\n      });\n\n      test('dirty creates correct instance', () {\n        final password = Password.dirty(passwordString);\n        expect(password.value, passwordString);\n        expect(password.isPure, isFalse);\n      });\n    });\n\n    group('validator', () {\n      test('returns empty error when password is empty', () {\n        expect(\n          Password.dirty().error,\n          PasswordValidationError.empty,\n        );\n      });\n\n      test('is valid when password is not empty', () {\n        expect(\n          Password.dirty(passwordString).error,\n          isNull,\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/models/username_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  const usernameString = 'mock-username';\n  group('Username', () {\n    group('constructors', () {\n      test('pure creates correct instance', () {\n        final username = Username.pure();\n        expect(username.value, '');\n        expect(username.isPure, isTrue);\n      });\n\n      test('dirty creates correct instance', () {\n        final username = Username.dirty(usernameString);\n        expect(username.value, usernameString);\n        expect(username.isPure, isFalse);\n      });\n    });\n\n    group('validator', () {\n      test('returns empty error when username is empty', () {\n        expect(\n          Username.dirty().error,\n          UsernameValidationError.empty,\n        );\n      });\n\n      test('is valid when username is not empty', () {\n        expect(\n          Username.dirty(usernameString).error,\n          isNull,\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/view/login_form_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:formz/formz.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockLoginBloc extends MockBloc<LoginEvent, LoginState>\n    implements LoginBloc {}\n\nvoid main() {\n  group('LoginForm', () {\n    late LoginBloc loginBloc;\n\n    setUp(() {\n      loginBloc = MockLoginBloc();\n    });\n\n    testWidgets(\n      'adds LoginUsernameChanged to LoginBloc when username is updated',\n      (tester) async {\n        const username = 'username';\n        when(() => loginBloc.state).thenReturn(const LoginState());\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginBloc,\n                child: LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(\n          find.byKey(const Key('loginForm_usernameInput_textField')),\n          username,\n        );\n        verify(\n          () => loginBloc.add(const LoginUsernameChanged(username)),\n        ).called(1);\n      },\n    );\n\n    testWidgets(\n      'adds LoginPasswordChanged to LoginBloc when password is updated',\n      (tester) async {\n        const password = 'password';\n        when(() => loginBloc.state).thenReturn(const LoginState());\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginBloc,\n                child: LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.enterText(\n          find.byKey(const Key('loginForm_passwordInput_textField')),\n          password,\n        );\n        verify(\n          () => loginBloc.add(const LoginPasswordChanged(password)),\n        ).called(1);\n      },\n    );\n\n    testWidgets('continue button is disabled by default', (tester) async {\n      when(() => loginBloc.state).thenReturn(const LoginState());\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider.value(\n              value: loginBloc,\n              child: LoginForm(),\n            ),\n          ),\n        ),\n      );\n      final button = tester.widget<ElevatedButton>(find.byType(ElevatedButton));\n      expect(button.enabled, isFalse);\n    });\n\n    testWidgets(\n      'loading indicator is shown when status is submission in progress',\n      (tester) async {\n        when(() => loginBloc.state).thenReturn(\n          const LoginState(status: FormzSubmissionStatus.inProgress),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginBloc,\n                child: LoginForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.byType(ElevatedButton), findsNothing);\n        expect(find.byType(CircularProgressIndicator), findsOneWidget);\n      },\n    );\n\n    testWidgets(\n      'loading indicator is shown when status is submission success',\n      (tester) async {\n        when(() => loginBloc.state).thenReturn(\n          const LoginState(status: FormzSubmissionStatus.success),\n        );\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginBloc,\n                child: LoginForm(),\n              ),\n            ),\n          ),\n        );\n        expect(find.byType(ElevatedButton), findsNothing);\n        expect(find.byType(CircularProgressIndicator), findsOneWidget);\n      },\n    );\n\n    testWidgets('continue button is enabled when status is validated', (\n      tester,\n    ) async {\n      when(() => loginBloc.state).thenReturn(const LoginState(isValid: true));\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider.value(\n              value: loginBloc,\n              child: LoginForm(),\n            ),\n          ),\n        ),\n      );\n      final button = tester.widget<ElevatedButton>(find.byType(ElevatedButton));\n      expect(button.enabled, isTrue);\n    });\n\n    testWidgets(\n      'LoginSubmitted is added to LoginBloc when continue is tapped',\n      (tester) async {\n        when(() => loginBloc.state).thenReturn(const LoginState(isValid: true));\n        await tester.pumpWidget(\n          MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: loginBloc,\n                child: LoginForm(),\n              ),\n            ),\n          ),\n        );\n        await tester.tap(find.byType(ElevatedButton));\n        verify(() => loginBloc.add(const LoginSubmitted())).called(1);\n      },\n    );\n\n    testWidgets('shows SnackBar when status is submission failure', (\n      tester,\n    ) async {\n      whenListen(\n        loginBloc,\n        Stream.fromIterable([\n          const LoginState(status: FormzSubmissionStatus.inProgress),\n          const LoginState(status: FormzSubmissionStatus.failure),\n        ]),\n        initialState: const LoginState(status: FormzSubmissionStatus.failure),\n      );\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider.value(\n              value: loginBloc,\n              child: LoginForm(),\n            ),\n          ),\n        ),\n      );\n      await tester.pump();\n      expect(find.byType(SnackBar), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/test/login/view/login_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:authentication_repository/authentication_repository.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_login/login/login.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockAuthenticationRepository extends Mock\n    implements AuthenticationRepository {}\n\nvoid main() {\n  group('LoginPage', () {\n    late AuthenticationRepository authenticationRepository;\n\n    setUp(() {\n      authenticationRepository = MockAuthenticationRepository();\n    });\n\n    test('is routable', () {\n      expect(LoginPage.route(), isA<MaterialPageRoute<void>>());\n    });\n\n    testWidgets('renders a LoginForm', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: authenticationRepository,\n          child: MaterialApp(\n            home: Scaffold(body: LoginPage()),\n          ),\n        ),\n      );\n      expect(find.byType(LoginForm), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_login/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_login\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_login</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_login/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_login\",\n    \"short_name\": \"flutter_login\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_shopping_cart/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_shopping_cart/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_shopping_cart\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_shopping_cart/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_shopping_cart/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\n\nclass App extends StatelessWidget {\n  const App({required this.shoppingRepository, super.key});\n\n  final ShoppingRepository shoppingRepository;\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocProvider(\n      providers: [\n        BlocProvider(\n          create: (_) => CatalogBloc(\n            shoppingRepository: shoppingRepository,\n          )..add(CatalogStarted()),\n        ),\n        BlocProvider(\n          create: (_) => CartBloc(\n            shoppingRepository: shoppingRepository,\n          )..add(CartStarted()),\n        ),\n      ],\n      child: MaterialApp(\n        title: 'Flutter Bloc Shopping Cart',\n        theme: ThemeData(\n          colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),\n          appBarTheme: const AppBarTheme(\n            backgroundColor: Colors.transparent,\n            elevation: 0,\n          ),\n        ),\n        initialRoute: '/',\n        routes: {\n          '/': (_) => const CatalogPage(),\n          '/cart': (_) => const CartPage(),\n        },\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/bloc/cart_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:meta/meta.dart';\n\npart 'cart_event.dart';\npart 'cart_state.dart';\n\nclass CartBloc extends Bloc<CartEvent, CartState> {\n  CartBloc({required ShoppingRepository shoppingRepository})\n    : _shoppingRepository = shoppingRepository,\n      super(CartLoading()) {\n    on<CartStarted>(_onStarted);\n    on<CartItemAdded>(_onItemAdded);\n    on<CartItemRemoved>(_onItemRemoved);\n  }\n\n  final ShoppingRepository _shoppingRepository;\n\n  Future<void> _onStarted(CartStarted event, Emitter<CartState> emit) async {\n    emit(CartLoading());\n    try {\n      final items = await _shoppingRepository.loadCartItems();\n      emit(CartLoaded(cart: Cart(items: [...items])));\n    } catch (_) {\n      emit(CartError());\n    }\n  }\n\n  Future<void> _onItemAdded(\n    CartItemAdded event,\n    Emitter<CartState> emit,\n  ) async {\n    final state = this.state;\n    if (state is CartLoaded) {\n      try {\n        _shoppingRepository.addItemToCart(event.item);\n        emit(CartLoaded(cart: Cart(items: [...state.cart.items, event.item])));\n      } catch (_) {\n        emit(CartError());\n      }\n    }\n  }\n\n  void _onItemRemoved(CartItemRemoved event, Emitter<CartState> emit) {\n    final state = this.state;\n    if (state is CartLoaded) {\n      try {\n        _shoppingRepository.removeItemFromCart(event.item);\n        emit(\n          CartLoaded(\n            cart: Cart(\n              items: [...state.cart.items]..remove(event.item),\n            ),\n          ),\n        );\n      } catch (_) {\n        emit(CartError());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/bloc/cart_event.dart",
    "content": "part of 'cart_bloc.dart';\n\n@immutable\nsealed class CartEvent extends Equatable {\n  const CartEvent();\n}\n\nfinal class CartStarted extends CartEvent {\n  @override\n  List<Object> get props => [];\n}\n\nfinal class CartItemAdded extends CartEvent {\n  const CartItemAdded(this.item);\n\n  final Item item;\n\n  @override\n  List<Object> get props => [item];\n}\n\nfinal class CartItemRemoved extends CartEvent {\n  const CartItemRemoved(this.item);\n\n  final Item item;\n\n  @override\n  List<Object> get props => [item];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/bloc/cart_state.dart",
    "content": "part of 'cart_bloc.dart';\n\n@immutable\nsealed class CartState extends Equatable {\n  const CartState();\n}\n\nfinal class CartLoading extends CartState {\n  @override\n  List<Object> get props => [];\n}\n\nfinal class CartLoaded extends CartState {\n  const CartLoaded({this.cart = const Cart()});\n\n  final Cart cart;\n\n  @override\n  List<Object> get props => [cart];\n}\n\nfinal class CartError extends CartState {\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/cart.dart",
    "content": "export 'bloc/cart_bloc.dart';\nexport 'models/models.dart';\nexport 'view/cart_page.dart';\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/models/cart.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\n\nclass Cart extends Equatable {\n  const Cart({this.items = const <Item>[]});\n\n  final List<Item> items;\n\n  int get totalPrice {\n    return items.fold(0, (total, current) => total + current.price);\n  }\n\n  @override\n  List<Object> get props => [items];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/models/models.dart",
    "content": "export 'cart.dart';\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/cart/view/cart_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\n\nclass CartPage extends StatelessWidget {\n  const CartPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      extendBodyBehindAppBar: true,\n      appBar: AppBar(title: const Text('Cart')),\n      body: const ColoredBox(\n        color: Colors.yellow,\n        child: Column(\n          children: [\n            Expanded(\n              child: Padding(\n                padding: EdgeInsets.all(32),\n                child: CartList(),\n              ),\n            ),\n            Divider(height: 4, color: Colors.black),\n            CartTotal(),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass CartList extends StatelessWidget {\n  const CartList({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final itemNameStyle = Theme.of(context).textTheme.titleLarge;\n\n    return BlocBuilder<CartBloc, CartState>(\n      builder: (context, state) {\n        return switch (state) {\n          CartLoading() => const CircularProgressIndicator(),\n          CartError() => const Text('Something went wrong!'),\n          CartLoaded() => ListView.separated(\n            itemCount: state.cart.items.length,\n            separatorBuilder: (_, _) => const SizedBox(height: 4),\n            itemBuilder: (context, index) {\n              final item = state.cart.items[index];\n              return Material(\n                shape: RoundedRectangleBorder(\n                  borderRadius: BorderRadius.circular(16),\n                ),\n                clipBehavior: Clip.hardEdge,\n                child: ListTile(\n                  leading: const Icon(Icons.done),\n                  title: Text(item.name, style: itemNameStyle),\n                  onLongPress: () {\n                    context.read<CartBloc>().add(CartItemRemoved(item));\n                  },\n                ),\n              );\n            },\n          ),\n        };\n      },\n    );\n  }\n}\n\nclass CartTotal extends StatelessWidget {\n  const CartTotal({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final hugeStyle = Theme.of(\n      context,\n    ).textTheme.displayLarge?.copyWith(fontSize: 48);\n\n    return SizedBox(\n      height: 200,\n      child: Center(\n        child: Column(\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: [\n            BlocBuilder<CartBloc, CartState>(\n              builder: (context, state) {\n                return switch (state) {\n                  CartLoading() => const CircularProgressIndicator(),\n                  CartError() => const Text('Something went wrong!'),\n                  CartLoaded() => Text(\n                    '\\$${state.cart.totalPrice}',\n                    style: hugeStyle,\n                  ),\n                };\n              },\n            ),\n            const SizedBox(width: 24),\n            ElevatedButton(\n              onPressed: () {\n                ScaffoldMessenger.of(context).showSnackBar(\n                  const SnackBar(content: Text('Buying not supported yet.')),\n                );\n              },\n              child: const Text('BUY'),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/bloc/catalog_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\n\npart 'catalog_event.dart';\npart 'catalog_state.dart';\n\nclass CatalogBloc extends Bloc<CatalogEvent, CatalogState> {\n  CatalogBloc({required ShoppingRepository shoppingRepository})\n    : _shoppingRepository = shoppingRepository,\n      super(CatalogLoading()) {\n    on<CatalogStarted>(_onStarted);\n  }\n\n  final ShoppingRepository _shoppingRepository;\n\n  Future<void> _onStarted(\n    CatalogStarted event,\n    Emitter<CatalogState> emit,\n  ) async {\n    emit(CatalogLoading());\n    try {\n      final catalog = await _shoppingRepository.loadCatalog();\n      emit(CatalogLoaded(Catalog(itemNames: catalog)));\n    } catch (_) {\n      emit(CatalogError());\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/bloc/catalog_event.dart",
    "content": "part of 'catalog_bloc.dart';\n\nsealed class CatalogEvent extends Equatable {\n  const CatalogEvent();\n}\n\nfinal class CatalogStarted extends CatalogEvent {\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/bloc/catalog_state.dart",
    "content": "part of 'catalog_bloc.dart';\n\nsealed class CatalogState extends Equatable {\n  const CatalogState();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class CatalogLoading extends CatalogState {}\n\nfinal class CatalogLoaded extends CatalogState {\n  const CatalogLoaded(this.catalog);\n\n  final Catalog catalog;\n\n  @override\n  List<Object> get props => [catalog];\n}\n\nfinal class CatalogError extends CatalogState {}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/catalog.dart",
    "content": "export 'bloc/catalog_bloc.dart';\nexport 'models/models.dart';\nexport 'view/catalog_page.dart';\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/models/catalog.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\n\nclass Catalog extends Equatable {\n  const Catalog({required this.itemNames});\n\n  final List<String> itemNames;\n\n  Item getById(int id) => Item(id, itemNames[id % itemNames.length]);\n\n  Item getByPosition(int position) => getById(position);\n\n  @override\n  List<Object> get props => [itemNames];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/models/item.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter/material.dart';\n\nclass Item extends Equatable {\n  Item(this.id, this.name)\n    : color = Colors.primaries[id % Colors.primaries.length];\n\n  final int id;\n  final String name;\n  final Color color;\n  final int price = 42;\n\n  @override\n  List<Object> get props => [id, name, color, price];\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/models/models.dart",
    "content": "export 'catalog.dart';\nexport 'item.dart';\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/catalog/view/catalog_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\n\nclass CatalogPage extends StatelessWidget {\n  const CatalogPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: CustomScrollView(\n        slivers: [\n          const CatalogAppBar(),\n          const SliverToBoxAdapter(child: SizedBox(height: 12)),\n          BlocBuilder<CatalogBloc, CatalogState>(\n            builder: (context, state) {\n              return switch (state) {\n                CatalogLoading() => const SliverFillRemaining(\n                  child: Center(child: CircularProgressIndicator()),\n                ),\n                CatalogError() => const SliverFillRemaining(\n                  child: Text('Something went wrong!'),\n                ),\n                CatalogLoaded() => SliverList(\n                  delegate: SliverChildBuilderDelegate(\n                    (context, index) => CatalogListItem(\n                      state.catalog.getByPosition(index),\n                    ),\n                    childCount: state.catalog.itemNames.length,\n                  ),\n                ),\n              };\n            },\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass AddButton extends StatelessWidget {\n  const AddButton({required this.item, super.key});\n\n  final Item item;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return BlocBuilder<CartBloc, CartState>(\n      builder: (context, state) {\n        return switch (state) {\n          CartLoading() => const CircularProgressIndicator(),\n          CartError() => const Text('Something went wrong!'),\n          CartLoaded() => Builder(\n            builder: (context) {\n              final isInCart = state.cart.items.contains(item);\n              return TextButton(\n                style: TextButton.styleFrom(\n                  disabledForegroundColor: theme.primaryColor,\n                ),\n                onPressed: isInCart\n                    ? null\n                    : () => context.read<CartBloc>().add(CartItemAdded(item)),\n                child: isInCart\n                    ? const Icon(Icons.check, semanticLabel: 'ADDED')\n                    : const Text('ADD'),\n              );\n            },\n          ),\n        };\n      },\n    );\n  }\n}\n\nclass CatalogAppBar extends StatelessWidget {\n  const CatalogAppBar({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return SliverAppBar(\n      title: const Text('Catalog'),\n      floating: true,\n      actions: [\n        IconButton(\n          icon: const Icon(Icons.shopping_cart),\n          onPressed: () => Navigator.of(context).pushNamed('/cart'),\n        ),\n      ],\n    );\n  }\n}\n\nclass CatalogListItem extends StatelessWidget {\n  const CatalogListItem(this.item, {super.key});\n\n  final Item item;\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme.titleLarge;\n    return Padding(\n      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),\n      child: LimitedBox(\n        maxHeight: 48,\n        child: Row(\n          children: [\n            AspectRatio(aspectRatio: 1, child: ColoredBox(color: item.color)),\n            const SizedBox(width: 24),\n            Expanded(child: Text(item.name, style: textTheme)),\n            const SizedBox(width: 24),\n            AddButton(item: item),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/main.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_shopping_cart/app.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:flutter_shopping_cart/simple_bloc_observer.dart';\n\nvoid main() {\n  Bloc.observer = const SimpleBlocObserver();\n  runApp(App(shoppingRepository: ShoppingRepository()));\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/shopping_repository.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\n\nconst _delay = Duration(milliseconds: 800);\n\nconst _catalog = [\n  'Code Smell',\n  'Control Flow',\n  'Interpreter',\n  'Recursion',\n  'Sprint',\n  'Heisenbug',\n  'Spaghetti',\n  'Hydra Code',\n  'Off-By-One',\n  'Scope',\n  'Callback',\n  'Closure',\n  'Automata',\n  'Bit Shift',\n  'Currying',\n];\n\nclass ShoppingRepository {\n  final _items = <Item>[];\n\n  Future<List<String>> loadCatalog() => Future.delayed(_delay, () => _catalog);\n\n  Future<List<Item>> loadCartItems() => Future.delayed(_delay, () => _items);\n\n  void addItemToCart(Item item) => _items.add(item);\n\n  void removeItemFromCart(Item item) => _items.remove(item);\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/lib/simple_bloc_observer.dart",
    "content": "import 'dart:developer';\n\nimport 'package:bloc/bloc.dart';\n\nclass SimpleBlocObserver extends BlocObserver {\n  const SimpleBlocObserver();\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    super.onEvent(bloc, event);\n    log('${bloc.runtimeType} $event');\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    log('${bloc.runtimeType} $error');\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    log('$transition');\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/macos/.gitignore",
    "content": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
  },
  {
    "path": "examples/flutter_shopping_cart/macos/Flutter/GeneratedPluginRegistrant.swift",
    "content": "//\n//  Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\n\nfunc RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/pubspec.yaml",
    "content": "name: flutter_shopping_cart\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  meta: ^1.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_shopping_cart/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/app_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_shopping_cart/app.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockShoppingRepository extends Mock implements ShoppingRepository {}\n\nvoid main() {\n  group('App', () {\n    late ShoppingRepository shoppingRepository;\n\n    setUp(() {\n      shoppingRepository = MockShoppingRepository();\n      when(shoppingRepository.loadCatalog).thenAnswer(\n        (_) async => <String>['Orange Juice', 'Milk'],\n      );\n    });\n\n    testWidgets('renders CatalogPage', (tester) async {\n      await tester.pumpWidget(App(shoppingRepository: shoppingRepository));\n      expect(find.byType(CatalogPage), findsOneWidget);\n    });\n\n    testWidgets('renders CatalogPage (initial route)', (tester) async {\n      await tester.pumpWidget(App(shoppingRepository: shoppingRepository));\n      expect(find.byType(CatalogPage), findsOneWidget);\n    });\n\n    testWidgets('can navigate back and forth '\n        'between CartPage and CatalogPage', (tester) async {\n      await tester.pumpWidget(App(shoppingRepository: shoppingRepository));\n\n      await tester.tap(find.byIcon(Icons.shopping_cart));\n      await tester.pumpAndSettle();\n\n      expect(find.byType(CartPage), findsOneWidget);\n      expect(find.byType(CatalogPage), findsNothing);\n\n      await tester.tap(find.byIcon(Icons.arrow_back));\n      await tester.pumpAndSettle();\n\n      expect(find.byType(CartPage), findsNothing);\n      expect(find.byType(CatalogPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/cart/bloc/cart_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockShoppingRepository extends Mock implements ShoppingRepository {}\n\nvoid main() {\n  group('CartBloc', () {\n    final mockItems = [\n      Item(1, 'item #1'),\n      Item(2, 'item #2'),\n      Item(3, 'item #3'),\n    ];\n\n    final mockItemToAdd = Item(4, 'item #4');\n    final mockItemToRemove = Item(2, 'item #2');\n\n    late ShoppingRepository shoppingRepository;\n\n    setUp(() {\n      shoppingRepository = MockShoppingRepository();\n    });\n\n    test('initial state is CartLoading', () {\n      expect(\n        CartBloc(shoppingRepository: shoppingRepository).state,\n        CartLoading(),\n      );\n    });\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartLoading, CartLoaded] when cart is loaded successfully',\n      setUp: () {\n        when(shoppingRepository.loadCartItems).thenAnswer((_) async => []);\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      act: (bloc) => bloc.add(CartStarted()),\n      expect: () => <CartState>[CartLoading(), const CartLoaded()],\n      verify: (_) => verify(shoppingRepository.loadCartItems).called(1),\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartLoading, CartError] when loading the cart throws an error',\n      setUp: () {\n        when(shoppingRepository.loadCartItems).thenThrow(Exception('Error'));\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      act: (bloc) => bloc..add(CartStarted()),\n      expect: () => <CartState>[CartLoading(), CartError()],\n      verify: (_) => verify(shoppingRepository.loadCartItems).called(1),\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [] when cart is not finished loading and item is added',\n      setUp: () {\n        when(\n          () => shoppingRepository.addItemToCart(mockItemToAdd),\n        ).thenAnswer((_) async {});\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      act: (bloc) => bloc.add(CartItemAdded(mockItemToAdd)),\n      expect: () => <CartState>[],\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartLoaded] when item is added successfully',\n      setUp: () {\n        when(\n          () => shoppingRepository.addItemToCart(mockItemToAdd),\n        ).thenAnswer((_) async {});\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      seed: () => CartLoaded(cart: Cart(items: mockItems)),\n      act: (bloc) => bloc.add(CartItemAdded(mockItemToAdd)),\n      expect: () => <CartState>[\n        CartLoaded(cart: Cart(items: [...mockItems, mockItemToAdd])),\n      ],\n      verify: (_) {\n        verify(() => shoppingRepository.addItemToCart(mockItemToAdd)).called(1);\n      },\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartError] when item is not added successfully',\n      setUp: () {\n        when(\n          () => shoppingRepository.addItemToCart(mockItemToAdd),\n        ).thenThrow(Exception('Error'));\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      seed: () => CartLoaded(cart: Cart(items: mockItems)),\n      act: (bloc) => bloc.add(CartItemAdded(mockItemToAdd)),\n      expect: () => <CartState>[CartError()],\n      verify: (_) {\n        verify(\n          () => shoppingRepository.addItemToCart(mockItemToAdd),\n        ).called(1);\n      },\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartLoaded] when item is removed successfully',\n      setUp: () {\n        when(\n          () => shoppingRepository.removeItemFromCart(mockItemToRemove),\n        ).thenAnswer((_) async {});\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      seed: () => CartLoaded(cart: Cart(items: mockItems)),\n      act: (bloc) => bloc.add(CartItemRemoved(mockItemToRemove)),\n      expect: () => <CartState>[\n        CartLoaded(cart: Cart(items: [...mockItems]..remove(mockItemToRemove))),\n      ],\n      verify: (_) {\n        verify(\n          () => shoppingRepository.removeItemFromCart(mockItemToRemove),\n        ).called(1);\n      },\n    );\n\n    blocTest<CartBloc, CartState>(\n      'emits [CartError] when item is not removed successfully',\n      setUp: () {\n        when(\n          () => shoppingRepository.removeItemFromCart(mockItemToRemove),\n        ).thenThrow(Exception('Error'));\n      },\n      build: () => CartBloc(shoppingRepository: shoppingRepository),\n      seed: () => CartLoaded(cart: Cart(items: mockItems)),\n      act: (bloc) => bloc.add(CartItemRemoved(mockItemToRemove)),\n      expect: () => <CartState>[CartError()],\n      verify: (_) {\n        verify(\n          () => shoppingRepository.removeItemFromCart(mockItemToRemove),\n        ).called(1);\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/cart/bloc/cart_event_test.dart",
    "content": "import 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass FakeItem extends Fake implements Item {}\n\nvoid main() {\n  group('CartEvent', () {\n    group('CartStarted', () {\n      test('supports value comparison', () {\n        expect(CartStarted(), CartStarted());\n      });\n    });\n\n    group('CartItemAdded', () {\n      final item = FakeItem();\n      test('supports value comparison', () {\n        expect(CartItemAdded(item), CartItemAdded(item));\n      });\n    });\n\n    group('CartItemRemoved', () {\n      final item = FakeItem();\n      test('supports value comparison', () {\n        expect(CartItemRemoved(item), CartItemRemoved(item));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/cart/bloc/cart_state_test.dart",
    "content": "import 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass FakeCart extends Fake implements Cart {}\n\nvoid main() {\n  group('CartState', () {\n    group('CartLoading', () {\n      test('supports value comparison', () {\n        expect(CartLoading(), CartLoading());\n      });\n    });\n\n    group('CartLoaded', () {\n      final cart = FakeCart();\n      test('supports value comparison', () {\n        expect(CartLoaded(cart: cart), CartLoaded(cart: cart));\n      });\n    });\n\n    group('CartError', () {\n      test('supports value comparison', () {\n        expect(CartError(), CartError());\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/cart/models/cart_test.dart",
    "content": "import 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('Cart', () {\n    final mockItems = [\n      Item(1, 'item #1'),\n      Item(2, 'item #2'),\n      Item(3, 'item #3'),\n    ];\n\n    test('supports value comparison', () async {\n      expect(Cart(items: mockItems), Cart(items: mockItems));\n    });\n\n    test('gets correct total price for 3 items', () async {\n      expect(Cart(items: mockItems).totalPrice, 42 * 3);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/cart/view/cart_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nimport '../../helper.dart';\n\nvoid main() {\n  late CartBloc cartBloc;\n\n  final mockItems = [\n    Item(1, 'item #1'),\n    Item(2, 'item #2'),\n    Item(3, 'item #3'),\n  ];\n\n  setUp(() {\n    cartBloc = MockCartBloc();\n  });\n\n  group('CartPage', () {\n    testWidgets('renders CartList and CartTotal', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartLoading());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartPage(),\n      );\n      expect(find.byType(CartList), findsOneWidget);\n      expect(find.byType(CartTotal), findsOneWidget);\n    });\n  });\n\n  group('CartList', () {\n    testWidgets('renders CircularProgressIndicator '\n        'when cart is loading', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartLoading());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartList(),\n      );\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n\n    testWidgets('renders 3 ListTile '\n        'when cart is loaded with three items', (tester) async {\n      when(\n        () => cartBloc.state,\n      ).thenReturn(CartLoaded(cart: Cart(items: mockItems)));\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartList(),\n      );\n      expect(find.byType(ListTile), findsNWidgets(3));\n    });\n\n    testWidgets('renders error text '\n        'when cart fails to load', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartError());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartList(),\n      );\n      expect(find.text('Something went wrong!'), findsOneWidget);\n    });\n  });\n\n  group('CartTotal', () {\n    testWidgets('renders CircularProgressIndicator '\n        'when cart is loading', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartLoading());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartTotal(),\n      );\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n\n    testWidgets('renders total price '\n        'when cart is loaded with three items', (tester) async {\n      when(\n        () => cartBloc.state,\n      ).thenReturn(CartLoaded(cart: Cart(items: mockItems)));\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartTotal(),\n      );\n      expect(find.text('\\$${42 * 3}'), findsOneWidget);\n    });\n\n    testWidgets('renders error text '\n        'when cart fails to load', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartError());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: CartTotal(),\n      );\n      expect(find.text('Something went wrong!'), findsOneWidget);\n    });\n\n    testWidgets('renders SnackBar after '\n        \"tapping the 'BUY' button\", (tester) async {\n      when(\n        () => cartBloc.state,\n      ).thenReturn(CartLoaded(cart: Cart(items: mockItems)));\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: Scaffold(body: CartTotal()),\n      );\n      await tester.tap(find.text('BUY'));\n      await tester.pumpAndSettle();\n      expect(find.byType(SnackBar), findsOneWidget);\n      expect(find.text('Buying not supported yet.'), findsOneWidget);\n    });\n\n    testWidgets('adds CartItemRemoved on long press', (tester) async {\n      when(() => cartBloc.state).thenReturn(\n        CartLoaded(cart: Cart(items: mockItems)),\n      );\n      final mockItemToRemove = mockItems.last;\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: Scaffold(body: CartList()),\n      );\n      await tester.longPress(find.text(mockItemToRemove.name));\n      verify(() => cartBloc.add(CartItemRemoved(mockItemToRemove))).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/bloc/catalog_bloc_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockShoppingRepository extends Mock implements ShoppingRepository {}\n\nvoid main() {\n  group('CatalogBloc', () {\n    const mockItemNames = ['Orange Juice', 'Milk', 'Macaroons', 'Cookies'];\n\n    late ShoppingRepository shoppingRepository;\n\n    setUp(() {\n      shoppingRepository = MockShoppingRepository();\n    });\n\n    test('initial state is CatalogLoading', () {\n      expect(\n        CatalogBloc(shoppingRepository: shoppingRepository).state,\n        CatalogLoading(),\n      );\n    });\n\n    blocTest<CatalogBloc, CatalogState>(\n      'emits [CatalogLoading, CatalogLoaded] '\n      'when catalog is loaded successfully',\n      setUp: () {\n        when(shoppingRepository.loadCatalog).thenAnswer(\n          (_) async => mockItemNames,\n        );\n      },\n      build: () => CatalogBloc(shoppingRepository: shoppingRepository),\n      act: (bloc) => bloc.add(CatalogStarted()),\n      expect: () => <CatalogState>[\n        CatalogLoading(),\n        CatalogLoaded(Catalog(itemNames: mockItemNames)),\n      ],\n      verify: (_) => verify(shoppingRepository.loadCatalog).called(1),\n    );\n\n    blocTest<CatalogBloc, CatalogState>(\n      'emits [CatalogLoading, CatalogError] '\n      'when loading the catalog throws an exception',\n      setUp: () {\n        when(shoppingRepository.loadCatalog).thenThrow(Exception('Error'));\n      },\n      build: () => CatalogBloc(shoppingRepository: shoppingRepository),\n      act: (bloc) => bloc.add(CatalogStarted()),\n      expect: () => <CatalogState>[\n        CatalogLoading(),\n        CatalogError(),\n      ],\n      verify: (_) => verify(shoppingRepository.loadCatalog).called(1),\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/bloc/catalog_event_test.dart",
    "content": "import 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('CatalogEvent', () {\n    group('CatalogStarted', () {\n      test('supports value comparison', () {\n        expect(CatalogStarted(), CatalogStarted());\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/bloc/catalog_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors,\n\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('CatalogState', () {\n    group('CatalogLoading', () {\n      test('supports value comparison', () {\n        expect(CatalogLoading(), CatalogLoading());\n      });\n    });\n\n    group('CatalogLoaded', () {\n      test('supports value comparison', () {\n        final catalog = Catalog(itemNames: const ['item #1', 'item #2']);\n        expect(CatalogLoaded(catalog), CatalogLoaded(catalog));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/models/catalog_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('Catalog', () {\n    const mockItemNames = ['Orange Juice', 'Milk', 'Macaroons', 'Cookies'];\n\n    test('supports value comparison', () async {\n      expect(\n        Catalog(itemNames: mockItemNames),\n        Catalog(itemNames: mockItemNames),\n      );\n    });\n\n    test('gets correct item by id', () async {\n      expect(\n        Catalog(itemNames: mockItemNames).getById(1),\n        Item(1, 'Milk'),\n      );\n    });\n\n    test('gets correct item by id', () async {\n      expect(\n        Catalog(itemNames: mockItemNames).getByPosition(2),\n        Item(2, 'Macaroons'),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/models/item_test.dart",
    "content": "import 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('Item', () {\n    test('supports value comparison', () async {\n      expect(Item(1, 'item #1'), Item(1, 'item #1'));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/catalog/view/catalog_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors,\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nimport '../../helper.dart';\n\nvoid main() {\n  late CartBloc cartBloc;\n  late CatalogBloc catalogBloc;\n\n  setUp(() {\n    catalogBloc = MockCatalogBloc();\n    cartBloc = MockCartBloc();\n  });\n\n  group('CatalogPage', () {\n    testWidgets('renders SliverFillRemaining with loading indicator '\n        'when catalog is loading', (tester) async {\n      when(() => catalogBloc.state).thenReturn(CatalogLoading());\n      await tester.pumpApp(\n        catalogBloc: catalogBloc,\n        child: CatalogPage(),\n      );\n      expect(\n        find.descendant(\n          of: find.byType(SliverFillRemaining),\n          matching: find.byType(CircularProgressIndicator),\n        ),\n        findsOneWidget,\n      );\n    });\n\n    testWidgets('renders SliverList with two items '\n        'when catalog is loaded', (tester) async {\n      final catalog = Catalog(itemNames: const ['item #1', 'item #2']);\n      when(() => catalogBloc.state).thenReturn(CatalogLoaded(catalog));\n      when(() => cartBloc.state).thenReturn(CartLoading());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        catalogBloc: catalogBloc,\n        child: CatalogPage(),\n      );\n\n      expect(find.byType(SliverList), findsOneWidget);\n      expect(find.byType(CatalogListItem), findsNWidgets(2));\n    });\n\n    testWidgets('renders error text '\n        'when catalog fails to load', (tester) async {\n      when(() => catalogBloc.state).thenReturn(CatalogError());\n      await tester.pumpApp(\n        catalogBloc: catalogBloc,\n        child: CatalogPage(),\n      );\n\n      expect(find.text('Something went wrong!'), findsOneWidget);\n    });\n  });\n\n  group('AddButton', () {\n    final mockItem = Item(1, 'item #1');\n    testWidgets('renders CircularProgressIndicator when '\n        'cart is loading', (tester) async {\n      when(() => cartBloc.state).thenReturn(CartLoading());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: AddButton(item: mockItem),\n      );\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n    });\n\n    testWidgets(\"renders 'Add' text button \"\n        'when item is not in the cart', (tester) async {\n      when(() => cartBloc.state).thenReturn(const CartLoaded());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: AddButton(item: mockItem),\n      );\n      expect(find.text('ADD'), findsOneWidget);\n      expect(find.byIcon(Icons.check), findsNothing);\n    });\n\n    testWidgets('renders check icon '\n        'when item is already added to cart', (tester) async {\n      when(() => cartBloc.state).thenReturn(\n        CartLoaded(cart: Cart(items: [mockItem])),\n      );\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: AddButton(item: mockItem),\n      );\n\n      expect(find.byIcon(Icons.check), findsOneWidget);\n      expect(find.text('ADD'), findsNothing);\n    });\n\n    testWidgets('adds item to the cart', (tester) async {\n      when(() => cartBloc.state).thenReturn(const CartLoaded());\n      await tester.pumpApp(\n        cartBloc: cartBloc,\n        child: AddButton(item: mockItem),\n      );\n\n      await tester.tap(find.text('ADD'));\n      verify(() => cartBloc.add(CartItemAdded(mockItem))).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/helper.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_shopping_cart/cart/cart.dart';\nimport 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MockCartBloc extends MockBloc<CartEvent, CartState> implements CartBloc {}\n\nclass MockCatalogBloc extends MockBloc<CatalogEvent, CatalogState>\n    implements CatalogBloc {}\n\nextension PumpApp on WidgetTester {\n  Future<void> pumpApp({\n    required Widget child,\n    CartBloc? cartBloc,\n    CatalogBloc? catalogBloc,\n  }) {\n    return pumpWidget(\n      MaterialApp(\n        home: MultiBlocProvider(\n          providers: [\n            if (cartBloc != null)\n              BlocProvider.value(value: cartBloc)\n            else\n              BlocProvider(create: (_) => MockCartBloc()),\n            if (catalogBloc != null)\n              BlocProvider.value(value: catalogBloc)\n            else\n              BlocProvider(create: (_) => MockCatalogBloc()),\n          ],\n          child: child,\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/test/shopping_repository_test.dart",
    "content": "import 'package:flutter_shopping_cart/catalog/catalog.dart';\nimport 'package:flutter_shopping_cart/shopping_repository.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nvoid main() {\n  group('ShoppingRepository', () {\n    late ShoppingRepository shoppingRepository;\n\n    setUp(() {\n      shoppingRepository = ShoppingRepository();\n    });\n\n    group('loadCatalog', () {\n      test('returns list of item names', () {\n        const items = [\n          'Code Smell',\n          'Control Flow',\n          'Interpreter',\n          'Recursion',\n          'Sprint',\n          'Heisenbug',\n          'Spaghetti',\n          'Hydra Code',\n          'Off-By-One',\n          'Scope',\n          'Callback',\n          'Closure',\n          'Automata',\n          'Bit Shift',\n          'Currying',\n        ];\n        expect(\n          shoppingRepository.loadCatalog(),\n          completion(equals(items)),\n        );\n      });\n    });\n\n    group('loadCartItems', () {\n      test('return empty list after loading cart items', () {\n        expect(\n          shoppingRepository.loadCartItems(),\n          completion(equals(<Item>[])),\n        );\n      });\n    });\n\n    group('addItemToCart', () {\n      test('returns newly added item after adding item to cart', () {\n        final item = Item(1, 'item #1');\n        shoppingRepository.addItemToCart(item);\n        expect(\n          shoppingRepository.loadCartItems(),\n          completion(equals([item])),\n        );\n      });\n    });\n\n    group('removeItemFromCart', () {\n      test('removes item from cart', () {\n        final item = Item(1, 'item #1');\n        shoppingRepository\n          ..addItemToCart(item)\n          ..removeItemFromCart(item);\n        expect(\n          shoppingRepository.loadCartItems(),\n          completion(equals(<Item>[])),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_shopping_cart/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_shopping_cart\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_shopping_cart</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_shopping_cart/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_shopping_cart\",\n    \"short_name\": \"flutter_shopping_cart\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_timer/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_timer/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_timer/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_timer\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our \n[online documentation](https://flutter.dev/docs), which offers tutorials, \nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_timer/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_timer/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_timer/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_timer/timer/timer.dart';\n\nclass App extends StatelessWidget {\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Flutter Timer',\n      theme: ThemeData(\n        colorScheme: const ColorScheme.light(\n          primary: Color.fromRGBO(72, 74, 126, 1),\n        ),\n      ),\n      home: const TimerPage(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_timer/lib/main.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_timer/app.dart';\n\nvoid main() => runApp(const App());\n"
  },
  {
    "path": "examples/flutter_timer/lib/ticker.dart",
    "content": "class Ticker {\n  const Ticker();\n  Stream<int> tick({required int ticks}) {\n    return Stream.periodic(\n      const Duration(seconds: 1),\n      (x) => ticks - x - 1,\n    ).take(ticks);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_timer/lib/timer/bloc/timer_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_timer/ticker.dart';\n\npart 'timer_event.dart';\npart 'timer_state.dart';\n\nclass TimerBloc extends Bloc<TimerEvent, TimerState> {\n  TimerBloc({required Ticker ticker})\n    : _ticker = ticker,\n      super(const TimerInitial(_duration)) {\n    on<TimerStarted>(_onStarted);\n    on<TimerPaused>(_onPaused);\n    on<TimerResumed>(_onResumed);\n    on<TimerReset>(_onReset);\n    on<_TimerTicked>(_onTicked);\n  }\n\n  final Ticker _ticker;\n  static const int _duration = 60;\n\n  StreamSubscription<int>? _tickerSubscription;\n\n  @override\n  Future<void> close() {\n    _tickerSubscription?.cancel();\n    return super.close();\n  }\n\n  void _onStarted(TimerStarted event, Emitter<TimerState> emit) {\n    emit(TimerRunInProgress(event.duration));\n    _tickerSubscription?.cancel();\n    _tickerSubscription = _ticker\n        .tick(ticks: event.duration)\n        .listen((duration) => add(_TimerTicked(duration: duration)));\n  }\n\n  void _onPaused(TimerPaused event, Emitter<TimerState> emit) {\n    if (state is TimerRunInProgress) {\n      _tickerSubscription?.pause();\n      emit(TimerRunPause(state.duration));\n    }\n  }\n\n  void _onResumed(TimerResumed resume, Emitter<TimerState> emit) {\n    if (state is TimerRunPause) {\n      _tickerSubscription?.resume();\n      emit(TimerRunInProgress(state.duration));\n    }\n  }\n\n  void _onReset(TimerReset event, Emitter<TimerState> emit) {\n    _tickerSubscription?.cancel();\n    emit(const TimerInitial(_duration));\n  }\n\n  void _onTicked(_TimerTicked event, Emitter<TimerState> emit) {\n    emit(\n      event.duration > 0\n          ? TimerRunInProgress(event.duration)\n          : const TimerRunComplete(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_timer/lib/timer/bloc/timer_event.dart",
    "content": "part of 'timer_bloc.dart';\n\nsealed class TimerEvent {\n  const TimerEvent();\n}\n\nfinal class TimerStarted extends TimerEvent {\n  const TimerStarted({required this.duration});\n  final int duration;\n}\n\nfinal class TimerPaused extends TimerEvent {\n  const TimerPaused();\n}\n\nfinal class TimerResumed extends TimerEvent {\n  const TimerResumed();\n}\n\nclass TimerReset extends TimerEvent {\n  const TimerReset();\n}\n\nclass _TimerTicked extends TimerEvent {\n  const _TimerTicked({required this.duration});\n  final int duration;\n}\n"
  },
  {
    "path": "examples/flutter_timer/lib/timer/bloc/timer_state.dart",
    "content": "part of 'timer_bloc.dart';\n\nsealed class TimerState extends Equatable {\n  const TimerState(this.duration);\n  final int duration;\n\n  @override\n  List<Object> get props => [duration];\n}\n\nfinal class TimerInitial extends TimerState {\n  const TimerInitial(super.duration);\n\n  @override\n  String toString() => 'TimerInitial { duration: $duration }';\n}\n\nfinal class TimerRunPause extends TimerState {\n  const TimerRunPause(super.duration);\n\n  @override\n  String toString() => 'TimerRunPause { duration: $duration }';\n}\n\nfinal class TimerRunInProgress extends TimerState {\n  const TimerRunInProgress(super.duration);\n\n  @override\n  String toString() => 'TimerRunInProgress { duration: $duration }';\n}\n\nfinal class TimerRunComplete extends TimerState {\n  const TimerRunComplete() : super(0);\n}\n"
  },
  {
    "path": "examples/flutter_timer/lib/timer/timer.dart",
    "content": "export 'bloc/timer_bloc.dart';\nexport 'view/timer_page.dart';\n"
  },
  {
    "path": "examples/flutter_timer/lib/timer/view/timer_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_timer/ticker.dart';\nimport 'package:flutter_timer/timer/timer.dart';\n\nclass TimerPage extends StatelessWidget {\n  const TimerPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => TimerBloc(ticker: const Ticker()),\n      child: const TimerView(),\n    );\n  }\n}\n\nclass TimerView extends StatelessWidget {\n  const TimerView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return const Scaffold(\n      body: Stack(\n        children: [\n          Background(),\n          Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            children: <Widget>[\n              Padding(\n                padding: EdgeInsets.symmetric(vertical: 100),\n                child: Center(child: TimerText()),\n              ),\n              Actions(),\n            ],\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass TimerText extends StatelessWidget {\n  const TimerText({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final duration = context.select((TimerBloc bloc) => bloc.state.duration);\n    final minutesStr = ((duration / 60) % 60).floor().toString().padLeft(\n      2,\n      '0',\n    );\n    final secondsStr = (duration % 60).toString().padLeft(2, '0');\n    return Text(\n      '$minutesStr:$secondsStr',\n      style: Theme.of(\n        context,\n      ).textTheme.displayLarge?.copyWith(fontWeight: FontWeight.w500),\n    );\n  }\n}\n\nclass Actions extends StatelessWidget {\n  const Actions({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<TimerBloc, TimerState>(\n      buildWhen: (prev, state) => prev.runtimeType != state.runtimeType,\n      builder: (context, state) {\n        return Row(\n          mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n          children: [\n            ...switch (state) {\n              TimerInitial() => [\n                FloatingActionButton(\n                  child: const Icon(Icons.play_arrow),\n                  onPressed: () => context.read<TimerBloc>().add(\n                    TimerStarted(duration: state.duration),\n                  ),\n                ),\n              ],\n              TimerRunInProgress() => [\n                FloatingActionButton(\n                  child: const Icon(Icons.pause),\n                  onPressed: () {\n                    context.read<TimerBloc>().add(const TimerPaused());\n                  },\n                ),\n                FloatingActionButton(\n                  child: const Icon(Icons.replay),\n                  onPressed: () {\n                    context.read<TimerBloc>().add(const TimerReset());\n                  },\n                ),\n              ],\n              TimerRunPause() => [\n                FloatingActionButton(\n                  child: const Icon(Icons.play_arrow),\n                  onPressed: () {\n                    context.read<TimerBloc>().add(const TimerResumed());\n                  },\n                ),\n                FloatingActionButton(\n                  child: const Icon(Icons.replay),\n                  onPressed: () {\n                    context.read<TimerBloc>().add(const TimerReset());\n                  },\n                ),\n              ],\n              TimerRunComplete() => [\n                FloatingActionButton(\n                  child: const Icon(Icons.replay),\n                  onPressed: () {\n                    context.read<TimerBloc>().add(const TimerReset());\n                  },\n                ),\n              ],\n            },\n          ],\n        );\n      },\n    );\n  }\n}\n\nclass Background extends StatelessWidget {\n  const Background({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return SizedBox.expand(\n      child: DecoratedBox(\n        decoration: BoxDecoration(\n          gradient: LinearGradient(\n            begin: Alignment.topCenter,\n            end: Alignment.bottomCenter,\n            colors: [\n              Colors.blue.shade50,\n              Colors.blue.shade500,\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_timer/macos/.gitignore",
    "content": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
  },
  {
    "path": "examples/flutter_timer/macos/Flutter/GeneratedPluginRegistrant.swift",
    "content": "//\n//  Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\n\nfunc RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {\n}\n"
  },
  {
    "path": "examples/flutter_timer/pubspec.yaml",
    "content": "name: flutter_timer\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "examples/flutter_timer/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_timer/test/app_test.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_timer/app.dart';\nimport 'package:flutter_timer/timer/timer.dart';\n\nvoid main() {\n  group('App', () {\n    testWidgets('renders TimerPage', (tester) async {\n      await tester.pumpWidget(const App());\n      expect(find.byType(TimerPage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_timer/test/ticker_test.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_timer/ticker.dart';\n\nvoid main() {\n  group('Ticker', () {\n    const ticker = Ticker();\n    test('ticker emits 3 ticks from 2-0 every second', () {\n      expectLater(ticker.tick(ticks: 3), emitsInOrder(<int>[2, 1, 0]));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_timer/test/timer/bloc/timer_bloc_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_timer/ticker.dart';\nimport 'package:flutter_timer/timer/timer.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockTicker extends Mock implements Ticker {}\n\nvoid main() {\n  group('TimerBloc', () {\n    late Ticker ticker;\n\n    setUp(() {\n      ticker = _MockTicker();\n      when(() => ticker.tick(ticks: 5)).thenAnswer(\n        (_) => Stream<int>.fromIterable([5, 4, 3, 2, 1]),\n      );\n    });\n\n    test('initial state is TimerInitial(60)', () {\n      expect(\n        TimerBloc(ticker: ticker).state,\n        TimerInitial(60),\n      );\n    });\n\n    blocTest<TimerBloc, TimerState>(\n      'emits TickerRunInProgress 5 times after timer started',\n      build: () => TimerBloc(ticker: ticker),\n      act: (bloc) => bloc.add(const TimerStarted(duration: 5)),\n      expect: () => [\n        TimerRunInProgress(5),\n        TimerRunInProgress(4),\n        TimerRunInProgress(3),\n        TimerRunInProgress(2),\n        TimerRunInProgress(1),\n      ],\n      verify: (_) => verify(() => ticker.tick(ticks: 5)).called(1),\n    );\n\n    blocTest<TimerBloc, TimerState>(\n      'emits [TickerRunPause(2)] when ticker is paused at 2',\n      build: () => TimerBloc(ticker: ticker),\n      seed: () => TimerRunInProgress(2),\n      act: (bloc) => bloc.add(TimerPaused()),\n      expect: () => [TimerRunPause(2)],\n    );\n\n    blocTest<TimerBloc, TimerState>(\n      'emits [TickerRunInProgress(5)] when ticker is resumed at 5',\n      build: () => TimerBloc(ticker: ticker),\n      seed: () => TimerRunPause(5),\n      act: (bloc) => bloc.add(TimerResumed()),\n      expect: () => [TimerRunInProgress(5)],\n    );\n\n    blocTest<TimerBloc, TimerState>(\n      'emits [TickerInitial(60)] when timer is restarted',\n      build: () => TimerBloc(ticker: ticker),\n      act: (bloc) => bloc.add(TimerReset()),\n      expect: () => [TimerInitial(60)],\n    );\n\n    blocTest<TimerBloc, TimerState>(\n      'emits [TimerRunInProgress(3)] when timer ticks to 3',\n      setUp: () {\n        when(() => ticker.tick(ticks: 3)).thenAnswer(\n          (_) => Stream<int>.value(3),\n        );\n      },\n      build: () => TimerBloc(ticker: ticker),\n      act: (bloc) => bloc.add(TimerStarted(duration: 3)),\n      expect: () => [TimerRunInProgress(3)],\n    );\n\n    blocTest<TimerBloc, TimerState>(\n      'emits [TimerRunInProgress(1), TimerRunComplete()] when timer ticks to 0',\n      setUp: () {\n        when(() => ticker.tick(ticks: 1)).thenAnswer(\n          (_) => Stream<int>.fromIterable([1, 0]),\n        );\n      },\n      build: () => TimerBloc(ticker: ticker),\n      act: (bloc) => bloc.add(TimerStarted(duration: 1)),\n      expect: () => [TimerRunInProgress(1), TimerRunComplete()],\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_timer/test/timer/bloc/timer_state_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_timer/timer/timer.dart';\n\nvoid main() {\n  group('TimerState', () {\n    group('TimerInitial', () {\n      test('supports value comparison', () {\n        expect(\n          TimerInitial(60),\n          TimerInitial(60),\n        );\n      });\n    });\n    group('TimerRunPause', () {\n      test('supports value comparison', () {\n        expect(\n          TimerRunPause(60),\n          TimerRunPause(60),\n        );\n      });\n    });\n    group('TimerRunInProgress', () {\n      test('supports value comparison', () {\n        expect(\n          TimerRunInProgress(60),\n          TimerRunInProgress(60),\n        );\n      });\n    });\n    group('TimerRunComplete', () {\n      test('supports value comparison', () {\n        expect(\n          TimerRunComplete(),\n          TimerRunComplete(),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_timer/test/timer/view/timer_page_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_timer/timer/timer.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockTimerBloc extends MockBloc<TimerEvent, TimerState>\n    implements TimerBloc {}\n\nextension on WidgetTester {\n  Future<void> pumpTimerView(TimerBloc timerBloc) {\n    return pumpWidget(\n      MaterialApp(\n        home: BlocProvider.value(value: timerBloc, child: const TimerView()),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  late TimerBloc timerBloc;\n\n  setUpAll(() {\n    registerFallbackValue(const TimerStarted(duration: 0));\n  });\n\n  setUp(() {\n    timerBloc = _MockTimerBloc();\n  });\n\n  tearDown(() => reset(timerBloc));\n\n  group('TimerPage', () {\n    testWidgets('renders TimerView', (tester) async {\n      await tester.pumpWidget(const MaterialApp(home: TimerPage()));\n      expect(find.byType(TimerView), findsOneWidget);\n    });\n  });\n\n  group('TimerView', () {\n    testWidgets('renders initial Timer view', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerInitial(60));\n      await tester.pumpTimerView(timerBloc);\n      expect(find.text('01:00'), findsOneWidget);\n      expect(find.byIcon(Icons.play_arrow), findsOneWidget);\n    });\n\n    testWidgets('renders pause and reset button when timer is in progress', (\n      tester,\n    ) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunInProgress(59));\n      await tester.pumpTimerView(timerBloc);\n      expect(find.text('00:59'), findsOneWidget);\n      expect(find.byIcon(Icons.pause), findsOneWidget);\n      expect(find.byIcon(Icons.replay), findsOneWidget);\n    });\n\n    testWidgets('renders play and reset button when timer is paused', (\n      tester,\n    ) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunPause(600));\n      await tester.pumpTimerView(timerBloc);\n      expect(find.text('10:00'), findsOneWidget);\n      expect(find.byIcon(Icons.play_arrow), findsOneWidget);\n      expect(find.byIcon(Icons.replay), findsOneWidget);\n    });\n\n    testWidgets('renders replay button when timer is finished', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunComplete());\n      await tester.pumpTimerView(timerBloc);\n      expect(find.text('00:00'), findsOneWidget);\n      expect(find.byIcon(Icons.replay), findsOneWidget);\n    });\n\n    testWidgets('timer started when play arrow button is pressed', (\n      tester,\n    ) async {\n      when(() => timerBloc.state).thenReturn(const TimerInitial(60));\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.play_arrow));\n      verify(\n        () => timerBloc.add(\n          any(\n            that: isA<TimerStarted>().having((e) => e.duration, 'duration', 60),\n          ),\n        ),\n      ).called(1);\n    });\n\n    testWidgets('timer pauses when pause button is pressed '\n        'while timer is in progress', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunInProgress(30));\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.pause));\n      verify(() => timerBloc.add(const TimerPaused())).called(1);\n    });\n\n    testWidgets('timer resets when replay button is pressed '\n        'while timer is in progress', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunInProgress(30));\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.replay));\n      verify(() => timerBloc.add(const TimerReset())).called(1);\n    });\n\n    testWidgets('timer resumes when play arrow button is pressed '\n        'while timer is paused', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunPause(30));\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.play_arrow));\n      verify(() => timerBloc.add(const TimerResumed())).called(1);\n    });\n\n    testWidgets('timer resets when reset button is pressed '\n        'while timer is paused', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunPause(30));\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.replay));\n      verify(() => timerBloc.add(const TimerReset())).called(1);\n    });\n\n    testWidgets('timer resets when reset button is pressed '\n        'when timer is finished', (tester) async {\n      when(() => timerBloc.state).thenReturn(const TimerRunComplete());\n      await tester.pumpTimerView(timerBloc);\n      await tester.tap(find.byIcon(Icons.replay));\n      verify(() => timerBloc.add(const TimerReset())).called(1);\n    });\n\n    testWidgets('actions are not rebuilt when timer is running', (\n      tester,\n    ) async {\n      final controller = StreamController<TimerState>();\n      whenListen(\n        timerBloc,\n        controller.stream,\n        initialState: const TimerRunInProgress(10),\n      );\n      await tester.pumpTimerView(timerBloc);\n\n      FloatingActionButton findPauseButton() {\n        return tester.widget<FloatingActionButton>(\n          find.byWidgetPredicate(\n            (widget) =>\n                widget is FloatingActionButton &&\n                widget.child is Icon &&\n                (widget.child! as Icon).icon == Icons.pause,\n          ),\n        );\n      }\n\n      final pauseButton = findPauseButton();\n      controller.add(const TimerRunInProgress(9));\n      await tester.pump();\n      expect(pauseButton, equals(findPauseButton()));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_timer/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_timer\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_timer</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_timer/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_timer\",\n    \"short_name\": \"flutter_timer\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_todos/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_todos/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_todos/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Launch development\",\n      \"request\": \"launch\",\n      \"type\": \"dart\",\n      \"program\": \"lib/main_development.dart\",\n      \"args\": [\n        \"--flavor\",\n        \"development\",\n        \"--target\",\n        \"lib/main_development.dart\"\n      ]\n    },\n    {\n      \"name\": \"Launch staging\",\n      \"request\": \"launch\",\n      \"type\": \"dart\",\n      \"program\": \"lib/main_staging.dart\",\n      \"args\": [\"--flavor\", \"staging\", \"--target\", \"lib/main_staging.dart\"]\n    },\n    {\n      \"name\": \"Launch production\",\n      \"request\": \"launch\",\n      \"type\": \"dart\",\n      \"program\": \"lib/main_production.dart\",\n      \"args\": [\"--flavor\", \"production\", \"--target\", \"lib/main_production.dart\"]\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/flutter_todos/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Very Good Ventures\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "examples/flutter_todos/README.md",
    "content": "# Flutter Todos\n\n![coverage][coverage_badge]\n[![License: MIT][license_badge]][license_link]\n\nGenerated by the [Very Good CLI][very_good_cli_link] 🤖\n\nAn example todos app that showcases bloc state management patterns.\n\n---\n\n## Getting Started 🚀\n\nThis project contains 3 flavors:\n\n- development\n- staging\n- production\n\nTo run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands:\n\n```sh\n# Development\n$ flutter run --flavor development --target lib/main_development.dart\n\n# Staging\n$ flutter run --flavor staging --target lib/main_staging.dart\n\n# Production\n$ flutter run --flavor production --target lib/main_production.dart\n```\n\n_\\*Flutter Todos works on iOS, Android, and Web._\n\n---\n\n## Running Tests 🧪\n\nTo run all unit and widget tests use the following command:\n\n```sh\n$ flutter test --coverage --test-randomize-ordering-seed random\n```\n\nTo view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov).\n\n```sh\n# Generate Coverage Report\n$ genhtml coverage/lcov.info -o coverage/\n\n# Open Coverage Report\n$ open coverage/index.html\n```\n\n---\n\n## Working with Translations 🌐\n\nThis project relies on [flutter_localizations][flutter_localizations_link] and follows the [official internationalization guide for Flutter][internationalization_link].\n\n### Adding Strings\n\n1. To add a new localizable string, open the `app_en.arb` file at `lib/l10n/arb/app_en.arb`.\n\n```arb\n{\n    \"@@locale\": \"en\",\n    \"counterAppBarTitle\": \"Counter\",\n    \"@counterAppBarTitle\": {\n        \"description\": \"Text shown in the AppBar of the Counter Page\"\n    }\n}\n```\n\n2. Then add a new key/value and description\n\n```arb\n{\n    \"@@locale\": \"en\",\n    \"counterAppBarTitle\": \"Counter\",\n    \"@counterAppBarTitle\": {\n        \"description\": \"Text shown in the AppBar of the Counter Page\"\n    },\n    \"helloWorld\": \"Hello World\",\n    \"@helloWorld\": {\n        \"description\": \"Hello World Text\"\n    }\n}\n```\n\n3. Use the new string\n\n```dart\nimport 'package:flutter_todos/l10n/l10n.dart';\n\n@override\nWidget build(BuildContext context) {\n  final l10n = context.l10n;\n  return Text(l10n.helloWorld);\n}\n```\n\n### Adding Supported Locales\n\nUpdate the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale.\n\n```xml\n    ...\n\n    <key>CFBundleLocalizations</key>\n\t<array>\n\t\t<string>en</string>\n\t\t<string>es</string>\n\t</array>\n\n    ...\n```\n\n### Adding Translations\n\n1. For each supported locale, add a new ARB file in `lib/l10n/arb`.\n\n```\n├── l10n\n│   ├── arb\n│   │   ├── app_en.arb\n│   │   └── app_es.arb\n```\n\n2. Add the translated strings to each `.arb` file:\n\n`app_en.arb`\n\n```arb\n{\n    \"@@locale\": \"en\",\n    \"counterAppBarTitle\": \"Counter\",\n    \"@counterAppBarTitle\": {\n        \"description\": \"Text shown in the AppBar of the Counter Page\"\n    }\n}\n```\n\n`app_es.arb`\n\n```arb\n{\n    \"@@locale\": \"es\",\n    \"counterAppBarTitle\": \"Contador\",\n    \"@counterAppBarTitle\": {\n        \"description\": \"Texto mostrado en la AppBar de la página del contador\"\n    }\n}\n```\n\n[coverage_badge]: coverage_badge.svg\n[flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html\n[internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization\n[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[license_link]: https://opensource.org/licenses/MIT\n[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli\n"
  },
  {
    "path": "examples/flutter_todos/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_todos/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_todos/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '12.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "examples/flutter_todos/l10n.yaml",
    "content": "arb-dir: lib/l10n\ntemplate-arb-file: app_en.arb\noutput-localization-file: app_localizations.dart\nnullable-getter: false\n"
  },
  {
    "path": "examples/flutter_todos/lib/app/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/home/home.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:flutter_todos/theme/theme.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass App extends StatelessWidget {\n  const App({required this.createTodosRepository, super.key});\n\n  final TodosRepository Function() createTodosRepository;\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider<TodosRepository>(\n      create: (_) => createTodosRepository(),\n      dispose: (repository) => repository.dispose(),\n      child: const AppView(),\n    );\n  }\n}\n\nclass AppView extends StatelessWidget {\n  const AppView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      theme: FlutterTodosTheme.light,\n      darkTheme: FlutterTodosTheme.dark,\n      localizationsDelegates: AppLocalizations.localizationsDelegates,\n      supportedLocales: AppLocalizations.supportedLocales,\n      home: const HomePage(),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/app/app_bloc_observer.dart",
    "content": "import 'dart:developer';\n\nimport 'package:bloc/bloc.dart';\n\nclass AppBlocObserver extends BlocObserver {\n  const AppBlocObserver();\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    log('onChange(${bloc.runtimeType}, $change)');\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    log('onError(${bloc.runtimeType}, $error, $stackTrace)');\n    super.onError(bloc, error, stackTrace);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/bootstrap.dart",
    "content": "import 'dart:developer';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_todos/app/app.dart';\nimport 'package:flutter_todos/app/app_bloc_observer.dart';\nimport 'package:todos_api/todos_api.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nvoid bootstrap({required TodosApi todosApi}) {\n  FlutterError.onError = (details) {\n    log(details.exceptionAsString(), stackTrace: details.stack);\n  };\n\n  PlatformDispatcher.instance.onError = (error, stack) {\n    log(error.toString(), stackTrace: stack);\n    return true;\n  };\n\n  Bloc.observer = const AppBlocObserver();\n\n  runApp(App(createTodosRepository: () => TodosRepository(todosApi: todosApi)));\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/bloc/edit_todo_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\npart 'edit_todo_event.dart';\npart 'edit_todo_state.dart';\n\nclass EditTodoBloc extends Bloc<EditTodoEvent, EditTodoState> {\n  EditTodoBloc({\n    required TodosRepository todosRepository,\n    required Todo? initialTodo,\n  }) : _todosRepository = todosRepository,\n       super(\n         EditTodoState(\n           initialTodo: initialTodo,\n           title: initialTodo?.title ?? '',\n           description: initialTodo?.description ?? '',\n         ),\n       ) {\n    on<EditTodoTitleChanged>(_onTitleChanged);\n    on<EditTodoDescriptionChanged>(_onDescriptionChanged);\n    on<EditTodoSubmitted>(_onSubmitted);\n  }\n\n  final TodosRepository _todosRepository;\n\n  void _onTitleChanged(\n    EditTodoTitleChanged event,\n    Emitter<EditTodoState> emit,\n  ) {\n    emit(state.copyWith(title: event.title));\n  }\n\n  void _onDescriptionChanged(\n    EditTodoDescriptionChanged event,\n    Emitter<EditTodoState> emit,\n  ) {\n    emit(state.copyWith(description: event.description));\n  }\n\n  Future<void> _onSubmitted(\n    EditTodoSubmitted event,\n    Emitter<EditTodoState> emit,\n  ) async {\n    emit(state.copyWith(status: EditTodoStatus.loading));\n    final todo = (state.initialTodo ?? Todo(title: '')).copyWith(\n      title: state.title,\n      description: state.description,\n    );\n\n    try {\n      await _todosRepository.saveTodo(todo);\n      emit(state.copyWith(status: EditTodoStatus.success));\n    } catch (e) {\n      emit(state.copyWith(status: EditTodoStatus.failure));\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/bloc/edit_todo_event.dart",
    "content": "part of 'edit_todo_bloc.dart';\n\nsealed class EditTodoEvent extends Equatable {\n  const EditTodoEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class EditTodoTitleChanged extends EditTodoEvent {\n  const EditTodoTitleChanged(this.title);\n\n  final String title;\n\n  @override\n  List<Object> get props => [title];\n}\n\nfinal class EditTodoDescriptionChanged extends EditTodoEvent {\n  const EditTodoDescriptionChanged(this.description);\n\n  final String description;\n\n  @override\n  List<Object> get props => [description];\n}\n\nfinal class EditTodoSubmitted extends EditTodoEvent {\n  const EditTodoSubmitted();\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/bloc/edit_todo_state.dart",
    "content": "part of 'edit_todo_bloc.dart';\n\nenum EditTodoStatus { initial, loading, success, failure }\n\nextension EditTodoStatusX on EditTodoStatus {\n  bool get isLoadingOrSuccess => [\n    EditTodoStatus.loading,\n    EditTodoStatus.success,\n  ].contains(this);\n}\n\nfinal class EditTodoState extends Equatable {\n  const EditTodoState({\n    this.status = EditTodoStatus.initial,\n    this.initialTodo,\n    this.title = '',\n    this.description = '',\n  });\n\n  final EditTodoStatus status;\n  final Todo? initialTodo;\n  final String title;\n  final String description;\n\n  bool get isNewTodo => initialTodo == null;\n\n  EditTodoState copyWith({\n    EditTodoStatus? status,\n    Todo? initialTodo,\n    String? title,\n    String? description,\n  }) {\n    return EditTodoState(\n      status: status ?? this.status,\n      initialTodo: initialTodo ?? this.initialTodo,\n      title: title ?? this.title,\n      description: description ?? this.description,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, initialTodo, title, description];\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/edit_todo.dart",
    "content": "export 'bloc/edit_todo_bloc.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/view/edit_todo_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass EditTodoPage extends StatelessWidget {\n  const EditTodoPage({super.key});\n\n  static Route<void> route({Todo? initialTodo}) {\n    return MaterialPageRoute(\n      fullscreenDialog: true,\n      builder: (context) => BlocProvider(\n        create: (context) => EditTodoBloc(\n          todosRepository: context.read<TodosRepository>(),\n          initialTodo: initialTodo,\n        ),\n        child: const EditTodoPage(),\n      ),\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<EditTodoBloc, EditTodoState>(\n      listenWhen: (previous, current) =>\n          previous.status != current.status &&\n          current.status == EditTodoStatus.success,\n      listener: (context, state) => Navigator.of(context).pop(),\n      child: const EditTodoView(),\n    );\n  }\n}\n\nclass EditTodoView extends StatelessWidget {\n  const EditTodoView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n    final status = context.select((EditTodoBloc bloc) => bloc.state.status);\n    final isNewTodo = context.select(\n      (EditTodoBloc bloc) => bloc.state.isNewTodo,\n    );\n\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(\n          isNewTodo\n              ? l10n.editTodoAddAppBarTitle\n              : l10n.editTodoEditAppBarTitle,\n        ),\n      ),\n      floatingActionButton: FloatingActionButton(\n        tooltip: l10n.editTodoSaveButtonTooltip,\n        shape: const ContinuousRectangleBorder(\n          borderRadius: BorderRadius.all(Radius.circular(32)),\n        ),\n        onPressed: status.isLoadingOrSuccess\n            ? null\n            : () => context.read<EditTodoBloc>().add(const EditTodoSubmitted()),\n        child: status.isLoadingOrSuccess\n            ? const CupertinoActivityIndicator()\n            : const Icon(Icons.check_rounded),\n      ),\n      body: const CupertinoScrollbar(\n        child: SingleChildScrollView(\n          child: Padding(\n            padding: EdgeInsets.all(16),\n            child: Column(\n              children: [_TitleField(), _DescriptionField()],\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass _TitleField extends StatelessWidget {\n  const _TitleField();\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n    final state = context.watch<EditTodoBloc>().state;\n    final hintText = state.initialTodo?.title ?? '';\n\n    return TextFormField(\n      key: const Key('editTodoView_title_textFormField'),\n      initialValue: state.title,\n      decoration: InputDecoration(\n        enabled: !state.status.isLoadingOrSuccess,\n        labelText: l10n.editTodoTitleLabel,\n        hintText: hintText,\n      ),\n      maxLength: 50,\n      inputFormatters: [\n        LengthLimitingTextInputFormatter(50),\n        FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9\\s]')),\n      ],\n      onChanged: (value) {\n        context.read<EditTodoBloc>().add(EditTodoTitleChanged(value));\n      },\n    );\n  }\n}\n\nclass _DescriptionField extends StatelessWidget {\n  const _DescriptionField();\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n\n    final state = context.watch<EditTodoBloc>().state;\n    final hintText = state.initialTodo?.description ?? '';\n\n    return TextFormField(\n      key: const Key('editTodoView_description_textFormField'),\n      initialValue: state.description,\n      decoration: InputDecoration(\n        enabled: !state.status.isLoadingOrSuccess,\n        labelText: l10n.editTodoDescriptionLabel,\n        hintText: hintText,\n      ),\n      maxLength: 300,\n      maxLines: 7,\n      inputFormatters: [\n        LengthLimitingTextInputFormatter(300),\n      ],\n      onChanged: (value) {\n        context.read<EditTodoBloc>().add(EditTodoDescriptionChanged(value));\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/edit_todo/view/view.dart",
    "content": "export 'edit_todo_page.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/home/cubit/home_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'home_state.dart';\n\nclass HomeCubit extends Cubit<HomeState> {\n  HomeCubit() : super(const HomeState());\n\n  void setTab(HomeTab tab) => emit(HomeState(tab: tab));\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/home/cubit/home_state.dart",
    "content": "part of 'home_cubit.dart';\n\nenum HomeTab { todos, stats }\n\nfinal class HomeState extends Equatable {\n  const HomeState({\n    this.tab = HomeTab.todos,\n  });\n\n  final HomeTab tab;\n\n  @override\n  List<Object> get props => [tab];\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/home/home.dart",
    "content": "export 'cubit/home_cubit.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/home/view/home_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\nimport 'package:flutter_todos/home/home.dart';\nimport 'package:flutter_todos/stats/stats.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => HomeCubit(),\n      child: const HomeView(),\n    );\n  }\n}\n\nclass HomeView extends StatelessWidget {\n  const HomeView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final selectedTab = context.select((HomeCubit cubit) => cubit.state.tab);\n\n    return Scaffold(\n      body: IndexedStack(\n        index: selectedTab.index,\n        children: const [TodosOverviewPage(), StatsPage()],\n      ),\n      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,\n      floatingActionButton: FloatingActionButton(\n        shape: const CircleBorder(),\n        key: const Key('homeView_addTodo_floatingActionButton'),\n        onPressed: () => Navigator.of(context).push(EditTodoPage.route()),\n        child: const Icon(Icons.add),\n      ),\n      bottomNavigationBar: BottomAppBar(\n        shape: const CircularNotchedRectangle(),\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.spaceEvenly,\n          children: [\n            _HomeTabButton(\n              groupValue: selectedTab,\n              value: HomeTab.todos,\n              icon: const Icon(Icons.list_rounded),\n            ),\n            _HomeTabButton(\n              groupValue: selectedTab,\n              value: HomeTab.stats,\n              icon: const Icon(Icons.show_chart_rounded),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass _HomeTabButton extends StatelessWidget {\n  const _HomeTabButton({\n    required this.groupValue,\n    required this.value,\n    required this.icon,\n  });\n\n  final HomeTab groupValue;\n  final HomeTab value;\n  final Widget icon;\n\n  @override\n  Widget build(BuildContext context) {\n    return IconButton(\n      onPressed: () => context.read<HomeCubit>().setTab(value),\n      iconSize: 32,\n      color: groupValue != value\n          ? null\n          : Theme.of(context).colorScheme.secondary,\n      icon: icon,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/home/view/view.dart",
    "content": "export 'home_page.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/l10n/app_en.arb",
    "content": "{\n  \"@@locale\": \"en\",\n  \"todosOverviewAppBarTitle\": \"Flutter Todos\",\n  \"@todosOverviewAppBarTitle\": {\n    \"description\": \"Title text shown in the AppBar of the Todos Overview Page\"\n  },\n  \"todosOverviewFilterTooltip\": \"Filter\",\n  \"@todosOverviewFilterTooltip\": {\n    \"description\": \"Tooltip text shown in the filter dropdown of the Todos Overview Page\"\n  },\n  \"todosOverviewFilterAll\": \"All\",\n  \"@todosOverviewFilterAll\": {\n    \"description\": \"Text shown in the filter dropdown of the Todos Overview Page for the option to display all todos\"\n  },\n  \"todosOverviewFilterActiveOnly\": \"Active only\",\n  \"@todosOverviewFilterActiveOnly\": {\n    \"description\": \"Text shown in the filter dropdown of the Todos Overview Page for the option to display active todos only\"\n  },\n  \"todosOverviewFilterCompletedOnly\": \"Completed only\",\n  \"@todosOverviewFilterCompletedOnly\": {\n    \"description\": \"Text shown in the filter dropdown of the Todos Overview Page for the option to display completed todos only\"\n  },\n  \"todosOverviewMarkAllCompleteButtonText\": \"Mark all complete\",\n  \"@todosOverviewMarkAllCompleteButtonText\": {\n    \"description\": \"Button text shown in the options dropdown of the Todos Overview Page that marks all current todos as complete\"\n  },\n  \"todosOverviewClearCompletedButtonText\": \"Clear completed\",\n  \"@todosOverviewClearCompletedButtonText\": {\n    \"description\": \"Button text shown in the options dropdown of the Todos Overview Page that deletes all completed todos\"\n  },\n  \"todosOverviewEmptyText\": \"No todos found with the selected filters.\",\n  \"@todosOverviewEmptyText\": {\n    \"description\": \"Text shown in the Todos Overview Page when no todos are found with the selected filters\"\n  },\n  \"todosOverviewTodoDeletedSnackbarText\": \"Todo \\\"{todoTitle}\\\" deleted.\",\n  \"@todosOverviewTodoDeletedSnackbarText\": {\n    \"description\": \"Snackbar text shown when a todo is deleted from the Todos Overview Page\",\n    \"placeholders\": {\n      \"todoTitle\": {\n        \"description\": \"The title of the todo that was deleted\"\n      }\n    }\n  },\n  \"todosOverviewUndoDeletionButtonText\": \"Undo\",\n  \"@todosOverviewUndoDeletionButtonText\": {\n    \"description\": \"Button text shown in the snackbar that undoes a deletion of a todo\"\n  },\n  \"todosOverviewErrorSnackbarText\": \"An error occurred while loading todos.\",\n  \"@todosOverviewErrorSnackbarText\": {\n    \"description\": \"Snackbar text shown when an error occurs while loading todos\"\n  },\n  \"todosOverviewOptionsTooltip\": \"Options\",\n  \"@todosOverviewOptionsTooltip\": {\n    \"description\": \"Tooltip text shown in the options dropdown of the Todos Overview Page\"\n  },\n  \"todosOverviewOptionsMarkAllComplete\": \"Mark all as completed\",\n  \"@todosOverviewOptionsMarkAllComplete\": {\n    \"description\": \"Button text shown in the options dropdown of the Todos Overview Page that marks all todos as complete\"\n  },\n  \"todosOverviewOptionsMarkAllIncomplete\": \"Mark all as incomplete\",\n  \"@todosOverviewOptionsMarkAllIncomplete\": {\n    \"description\": \"Button text shown in the options dropdown of the Todos Overview Page that marks all todos as incomplete\"\n  },\n  \"todosOverviewOptionsClearCompleted\": \"Clear completed\",\n  \"@todosOverviewOptionsClearCompleted\": {\n    \"description\": \"Button text shown in the options dropdown of the Todos Overview Page that deletes all completed todos\"\n  },\n  \"todoDetailsAppBarTitle\": \"Todo Details\",\n  \"@todoDetailsAppBarTitle\": {\n    \"description\": \"Title text shown in the AppBar of the Todo Details Page\"\n  },\n  \"todoDetailsDeleteButtonTooltip\": \"Delete\",\n  \"@todoDetailsDeleteButtonTooltip\": {\n    \"description\": \"Tooltip text shown in the delete button on the Todo Details Page\"\n  },\n  \"todoDetailsEditButtonTooltip\": \"Edit\",\n  \"@todoDetailsEditButtonTooltip\": {\n    \"description\": \"Tooltip text shown in the edit button on the Todo Details Page\"\n  },\n  \"editTodoEditAppBarTitle\": \"Edit Todo\",\n  \"@editTodoEditAppBarTitle\": {\n    \"description\": \"Title text shown in the AppBar of the Todo Edit Page when editing an existing todo\"\n  },\n  \"editTodoAddAppBarTitle\": \"Add Todo\",\n  \"@editTodoAddAppBarTitle\": {\n    \"description\": \"Title text shown in the AppBar of the Todo Edit Page when adding a new todo\"\n  },\n  \"editTodoTitleLabel\": \"Title\",\n  \"@editTodoTitleLabel\": {\n    \"description\": \"Label text shown in the title input field of the Todo Edit Page\"\n  },\n  \"editTodoDescriptionLabel\": \"Description\",\n  \"@editTodoDescriptionLabel\": {\n    \"description\": \"Label text shown in the description input field of the Todo Edit Page\"\n  },\n  \"editTodoSaveButtonTooltip\": \"Save changes\",\n  \"@editTodoSaveButtonTooltip\": {\n    \"description\": \"Tooltip text shown in the save button on the Todo Edit Page\"\n  },\n  \"statsAppBarTitle\": \"Stats\",\n  \"@statsAppBarTitle\": {\n    \"description\": \"Title text shown in the AppBar of the Stats Page\"\n  },\n  \"statsCompletedTodoCountLabel\": \"Completed todos\",\n  \"@statsCompletedTodoCountLabel\": {\n    \"description\": \"Label text shown in the completed todos count section of the Stats Page\"\n  },\n  \"statsActiveTodoCountLabel\": \"Active todos\",\n  \"@statsActiveTodoCountLabel\": {\n    \"description\": \"Label text shown in the active todos count section of the Stats Page\"\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/l10n/app_localizations.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_localizations/flutter_localizations.dart';\nimport 'package:intl/intl.dart' as intl;\n\nimport 'app_localizations_en.dart';\n\n// ignore_for_file: type=lint\n\n/// Callers can lookup localized strings with an instance of AppLocalizations\n/// returned by `AppLocalizations.of(context)`.\n///\n/// Applications need to include `AppLocalizations.delegate()` in their app's\n/// `localizationDelegates` list, and the locales they support in the app's\n/// `supportedLocales` list. For example:\n///\n/// ```dart\n/// import 'l10n/app_localizations.dart';\n///\n/// return MaterialApp(\n///   localizationsDelegates: AppLocalizations.localizationsDelegates,\n///   supportedLocales: AppLocalizations.supportedLocales,\n///   home: MyApplicationHome(),\n/// );\n/// ```\n///\n/// ## Update pubspec.yaml\n///\n/// Please make sure to update your pubspec.yaml to include the following\n/// packages:\n///\n/// ```yaml\n/// dependencies:\n///   # Internationalization support.\n///   flutter_localizations:\n///     sdk: flutter\n///   intl: any # Use the pinned version from flutter_localizations\n///\n///   # Rest of dependencies\n/// ```\n///\n/// ## iOS Applications\n///\n/// iOS applications define key application metadata, including supported\n/// locales, in an Info.plist file that is built into the application bundle.\n/// To configure the locales supported by your app, you’ll need to edit this\n/// file.\n///\n/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.\n/// Then, in the Project Navigator, open the Info.plist file under the Runner\n/// project’s Runner folder.\n///\n/// Next, select the Information Property List item, select Add Item from the\n/// Editor menu, then select Localizations from the pop-up menu.\n///\n/// Select and expand the newly-created Localizations item then, for each\n/// locale your application supports, add a new item and select the locale\n/// you wish to add from the pop-up menu in the Value field. This list should\n/// be consistent with the languages listed in the AppLocalizations.supportedLocales\n/// property.\nabstract class AppLocalizations {\n  AppLocalizations(String locale)\n    : localeName = intl.Intl.canonicalizedLocale(locale.toString());\n\n  final String localeName;\n\n  static AppLocalizations of(BuildContext context) {\n    return Localizations.of<AppLocalizations>(context, AppLocalizations)!;\n  }\n\n  static const LocalizationsDelegate<AppLocalizations> delegate =\n      _AppLocalizationsDelegate();\n\n  /// A list of this localizations delegate along with the default localizations\n  /// delegates.\n  ///\n  /// Returns a list of localizations delegates containing this delegate along with\n  /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,\n  /// and GlobalWidgetsLocalizations.delegate.\n  ///\n  /// Additional delegates can be added by appending to this list in\n  /// MaterialApp. This list does not have to be used at all if a custom list\n  /// of delegates is preferred or required.\n  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =\n      <LocalizationsDelegate<dynamic>>[\n        delegate,\n        GlobalMaterialLocalizations.delegate,\n        GlobalCupertinoLocalizations.delegate,\n        GlobalWidgetsLocalizations.delegate,\n      ];\n\n  /// A list of this localizations delegate's supported locales.\n  static const List<Locale> supportedLocales = <Locale>[Locale('en')];\n\n  /// Title text shown in the AppBar of the Todos Overview Page\n  ///\n  /// In en, this message translates to:\n  /// **'Flutter Todos'**\n  String get todosOverviewAppBarTitle;\n\n  /// Tooltip text shown in the filter dropdown of the Todos Overview Page\n  ///\n  /// In en, this message translates to:\n  /// **'Filter'**\n  String get todosOverviewFilterTooltip;\n\n  /// Text shown in the filter dropdown of the Todos Overview Page for the option to display all todos\n  ///\n  /// In en, this message translates to:\n  /// **'All'**\n  String get todosOverviewFilterAll;\n\n  /// Text shown in the filter dropdown of the Todos Overview Page for the option to display active todos only\n  ///\n  /// In en, this message translates to:\n  /// **'Active only'**\n  String get todosOverviewFilterActiveOnly;\n\n  /// Text shown in the filter dropdown of the Todos Overview Page for the option to display completed todos only\n  ///\n  /// In en, this message translates to:\n  /// **'Completed only'**\n  String get todosOverviewFilterCompletedOnly;\n\n  /// Button text shown in the options dropdown of the Todos Overview Page that marks all current todos as complete\n  ///\n  /// In en, this message translates to:\n  /// **'Mark all complete'**\n  String get todosOverviewMarkAllCompleteButtonText;\n\n  /// Button text shown in the options dropdown of the Todos Overview Page that deletes all completed todos\n  ///\n  /// In en, this message translates to:\n  /// **'Clear completed'**\n  String get todosOverviewClearCompletedButtonText;\n\n  /// Text shown in the Todos Overview Page when no todos are found with the selected filters\n  ///\n  /// In en, this message translates to:\n  /// **'No todos found with the selected filters.'**\n  String get todosOverviewEmptyText;\n\n  /// Snackbar text shown when a todo is deleted from the Todos Overview Page\n  ///\n  /// In en, this message translates to:\n  /// **'Todo \\\"{todoTitle}\\\" deleted.'**\n  String todosOverviewTodoDeletedSnackbarText(Object todoTitle);\n\n  /// Button text shown in the snackbar that undoes a deletion of a todo\n  ///\n  /// In en, this message translates to:\n  /// **'Undo'**\n  String get todosOverviewUndoDeletionButtonText;\n\n  /// Snackbar text shown when an error occurs while loading todos\n  ///\n  /// In en, this message translates to:\n  /// **'An error occurred while loading todos.'**\n  String get todosOverviewErrorSnackbarText;\n\n  /// Tooltip text shown in the options dropdown of the Todos Overview Page\n  ///\n  /// In en, this message translates to:\n  /// **'Options'**\n  String get todosOverviewOptionsTooltip;\n\n  /// Button text shown in the options dropdown of the Todos Overview Page that marks all todos as complete\n  ///\n  /// In en, this message translates to:\n  /// **'Mark all as completed'**\n  String get todosOverviewOptionsMarkAllComplete;\n\n  /// Button text shown in the options dropdown of the Todos Overview Page that marks all todos as incomplete\n  ///\n  /// In en, this message translates to:\n  /// **'Mark all as incomplete'**\n  String get todosOverviewOptionsMarkAllIncomplete;\n\n  /// Button text shown in the options dropdown of the Todos Overview Page that deletes all completed todos\n  ///\n  /// In en, this message translates to:\n  /// **'Clear completed'**\n  String get todosOverviewOptionsClearCompleted;\n\n  /// Title text shown in the AppBar of the Todo Details Page\n  ///\n  /// In en, this message translates to:\n  /// **'Todo Details'**\n  String get todoDetailsAppBarTitle;\n\n  /// Tooltip text shown in the delete button on the Todo Details Page\n  ///\n  /// In en, this message translates to:\n  /// **'Delete'**\n  String get todoDetailsDeleteButtonTooltip;\n\n  /// Tooltip text shown in the edit button on the Todo Details Page\n  ///\n  /// In en, this message translates to:\n  /// **'Edit'**\n  String get todoDetailsEditButtonTooltip;\n\n  /// Title text shown in the AppBar of the Todo Edit Page when editing an existing todo\n  ///\n  /// In en, this message translates to:\n  /// **'Edit Todo'**\n  String get editTodoEditAppBarTitle;\n\n  /// Title text shown in the AppBar of the Todo Edit Page when adding a new todo\n  ///\n  /// In en, this message translates to:\n  /// **'Add Todo'**\n  String get editTodoAddAppBarTitle;\n\n  /// Label text shown in the title input field of the Todo Edit Page\n  ///\n  /// In en, this message translates to:\n  /// **'Title'**\n  String get editTodoTitleLabel;\n\n  /// Label text shown in the description input field of the Todo Edit Page\n  ///\n  /// In en, this message translates to:\n  /// **'Description'**\n  String get editTodoDescriptionLabel;\n\n  /// Tooltip text shown in the save button on the Todo Edit Page\n  ///\n  /// In en, this message translates to:\n  /// **'Save changes'**\n  String get editTodoSaveButtonTooltip;\n\n  /// Title text shown in the AppBar of the Stats Page\n  ///\n  /// In en, this message translates to:\n  /// **'Stats'**\n  String get statsAppBarTitle;\n\n  /// Label text shown in the completed todos count section of the Stats Page\n  ///\n  /// In en, this message translates to:\n  /// **'Completed todos'**\n  String get statsCompletedTodoCountLabel;\n\n  /// Label text shown in the active todos count section of the Stats Page\n  ///\n  /// In en, this message translates to:\n  /// **'Active todos'**\n  String get statsActiveTodoCountLabel;\n}\n\nclass _AppLocalizationsDelegate\n    extends LocalizationsDelegate<AppLocalizations> {\n  const _AppLocalizationsDelegate();\n\n  @override\n  Future<AppLocalizations> load(Locale locale) {\n    return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));\n  }\n\n  @override\n  bool isSupported(Locale locale) =>\n      <String>['en'].contains(locale.languageCode);\n\n  @override\n  bool shouldReload(_AppLocalizationsDelegate old) => false;\n}\n\nAppLocalizations lookupAppLocalizations(Locale locale) {\n  // Lookup logic when only language code is specified.\n  switch (locale.languageCode) {\n    case 'en':\n      return AppLocalizationsEn();\n  }\n\n  throw FlutterError(\n    'AppLocalizations.delegate failed to load unsupported locale \"$locale\". This is likely '\n    'an issue with the localizations generation tool. Please file an issue '\n    'on GitHub with a reproducible sample app and the gen-l10n configuration '\n    'that was used.',\n  );\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/l10n/app_localizations_en.dart",
    "content": "// ignore: unused_import\nimport 'package:intl/intl.dart' as intl;\nimport 'app_localizations.dart';\n\n// ignore_for_file: type=lint\n\n/// The translations for English (`en`).\nclass AppLocalizationsEn extends AppLocalizations {\n  AppLocalizationsEn([String locale = 'en']) : super(locale);\n\n  @override\n  String get todosOverviewAppBarTitle => 'Flutter Todos';\n\n  @override\n  String get todosOverviewFilterTooltip => 'Filter';\n\n  @override\n  String get todosOverviewFilterAll => 'All';\n\n  @override\n  String get todosOverviewFilterActiveOnly => 'Active only';\n\n  @override\n  String get todosOverviewFilterCompletedOnly => 'Completed only';\n\n  @override\n  String get todosOverviewMarkAllCompleteButtonText => 'Mark all complete';\n\n  @override\n  String get todosOverviewClearCompletedButtonText => 'Clear completed';\n\n  @override\n  String get todosOverviewEmptyText =>\n      'No todos found with the selected filters.';\n\n  @override\n  String todosOverviewTodoDeletedSnackbarText(Object todoTitle) {\n    return 'Todo \\\"$todoTitle\\\" deleted.';\n  }\n\n  @override\n  String get todosOverviewUndoDeletionButtonText => 'Undo';\n\n  @override\n  String get todosOverviewErrorSnackbarText =>\n      'An error occurred while loading todos.';\n\n  @override\n  String get todosOverviewOptionsTooltip => 'Options';\n\n  @override\n  String get todosOverviewOptionsMarkAllComplete => 'Mark all as completed';\n\n  @override\n  String get todosOverviewOptionsMarkAllIncomplete => 'Mark all as incomplete';\n\n  @override\n  String get todosOverviewOptionsClearCompleted => 'Clear completed';\n\n  @override\n  String get todoDetailsAppBarTitle => 'Todo Details';\n\n  @override\n  String get todoDetailsDeleteButtonTooltip => 'Delete';\n\n  @override\n  String get todoDetailsEditButtonTooltip => 'Edit';\n\n  @override\n  String get editTodoEditAppBarTitle => 'Edit Todo';\n\n  @override\n  String get editTodoAddAppBarTitle => 'Add Todo';\n\n  @override\n  String get editTodoTitleLabel => 'Title';\n\n  @override\n  String get editTodoDescriptionLabel => 'Description';\n\n  @override\n  String get editTodoSaveButtonTooltip => 'Save changes';\n\n  @override\n  String get statsAppBarTitle => 'Stats';\n\n  @override\n  String get statsCompletedTodoCountLabel => 'Completed todos';\n\n  @override\n  String get statsActiveTodoCountLabel => 'Active todos';\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/l10n/l10n.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_todos/l10n/app_localizations.dart';\n\nexport 'package:flutter_todos/l10n/app_localizations.dart';\n\nextension AppLocalizationsX on BuildContext {\n  AppLocalizations get l10n => AppLocalizations.of(this);\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/main_development.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_todos/bootstrap.dart';\nimport 'package:local_storage_todos_api/local_storage_todos_api.dart';\n\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n\n  final todosApi = LocalStorageTodosApi(\n    plugin: await SharedPreferences.getInstance(),\n  );\n\n  bootstrap(todosApi: todosApi);\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/main_production.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_todos/bootstrap.dart';\nimport 'package:local_storage_todos_api/local_storage_todos_api.dart';\n\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n\n  final todosApi = LocalStorageTodosApi(\n    plugin: await SharedPreferences.getInstance(),\n  );\n\n  bootstrap(todosApi: todosApi);\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/main_staging.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_todos/bootstrap.dart';\nimport 'package:local_storage_todos_api/local_storage_todos_api.dart';\n\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n\n  final todosApi = LocalStorageTodosApi(\n    plugin: await SharedPreferences.getInstance(),\n  );\n\n  bootstrap(todosApi: todosApi);\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/bloc/stats_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\npart 'stats_event.dart';\npart 'stats_state.dart';\n\nclass StatsBloc extends Bloc<StatsEvent, StatsState> {\n  StatsBloc({\n    required TodosRepository todosRepository,\n  }) : _todosRepository = todosRepository,\n       super(const StatsState()) {\n    on<StatsSubscriptionRequested>(_onSubscriptionRequested);\n  }\n\n  final TodosRepository _todosRepository;\n\n  Future<void> _onSubscriptionRequested(\n    StatsSubscriptionRequested event,\n    Emitter<StatsState> emit,\n  ) async {\n    emit(state.copyWith(status: StatsStatus.loading));\n\n    await emit.forEach<List<Todo>>(\n      _todosRepository.getTodos(),\n      onData: (todos) => state.copyWith(\n        status: StatsStatus.success,\n        completedTodos: todos.where((todo) => todo.isCompleted).length,\n        activeTodos: todos.where((todo) => !todo.isCompleted).length,\n      ),\n      onError: (_, _) => state.copyWith(status: StatsStatus.failure),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/bloc/stats_event.dart",
    "content": "part of 'stats_bloc.dart';\n\nsealed class StatsEvent extends Equatable {\n  const StatsEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class StatsSubscriptionRequested extends StatsEvent {\n  const StatsSubscriptionRequested();\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/bloc/stats_state.dart",
    "content": "part of 'stats_bloc.dart';\n\nenum StatsStatus { initial, loading, success, failure }\n\nfinal class StatsState extends Equatable {\n  const StatsState({\n    this.status = StatsStatus.initial,\n    this.completedTodos = 0,\n    this.activeTodos = 0,\n  });\n\n  final StatsStatus status;\n  final int completedTodos;\n  final int activeTodos;\n\n  @override\n  List<Object> get props => [status, completedTodos, activeTodos];\n\n  StatsState copyWith({\n    StatsStatus? status,\n    int? completedTodos,\n    int? activeTodos,\n  }) {\n    return StatsState(\n      status: status ?? this.status,\n      completedTodos: completedTodos ?? this.completedTodos,\n      activeTodos: activeTodos ?? this.activeTodos,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/stats.dart",
    "content": "export 'bloc/stats_bloc.dart';\nexport 'view/view.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/view/stats_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:flutter_todos/stats/stats.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass StatsPage extends StatelessWidget {\n  const StatsPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (context) => StatsBloc(\n        todosRepository: context.read<TodosRepository>(),\n      )..add(const StatsSubscriptionRequested()),\n      child: const StatsView(),\n    );\n  }\n}\n\nclass StatsView extends StatelessWidget {\n  const StatsView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n    final state = context.watch<StatsBloc>().state;\n    final textTheme = Theme.of(context).textTheme;\n\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(l10n.statsAppBarTitle),\n      ),\n      body: Column(\n        children: [\n          ListTile(\n            key: const Key('statsView_completedTodos_listTile'),\n            leading: const Icon(Icons.check_rounded),\n            title: Text(l10n.statsCompletedTodoCountLabel),\n            trailing: Text(\n              '${state.completedTodos}',\n              style: textTheme.headlineSmall,\n            ),\n          ),\n          ListTile(\n            key: const Key('statsView_activeTodos_listTile'),\n            leading: const Icon(Icons.radio_button_unchecked_rounded),\n            title: Text(l10n.statsActiveTodoCountLabel),\n            trailing: Text(\n              '${state.activeTodos}',\n              style: textTheme.headlineSmall,\n            ),\n          ),\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/stats/view/view.dart",
    "content": "export 'stats_page.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/theme/theme.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass FlutterTodosTheme {\n  static ThemeData get light {\n    return ThemeData(\n      appBarTheme: const AppBarTheme(\n        backgroundColor: Color.fromARGB(255, 117, 208, 247),\n      ),\n      colorScheme: ColorScheme.fromSeed(\n        seedColor: const Color(0xFF13B9FF),\n      ),\n      snackBarTheme: const SnackBarThemeData(\n        behavior: SnackBarBehavior.floating,\n      ),\n    );\n  }\n\n  static ThemeData get dark {\n    return ThemeData(\n      appBarTheme: const AppBarTheme(\n        backgroundColor: Color.fromARGB(255, 16, 46, 59),\n      ),\n      colorScheme: ColorScheme.fromSeed(\n        brightness: Brightness.dark,\n        seedColor: const Color(0xFF13B9FF),\n      ),\n      snackBarTheme: const SnackBarThemeData(\n        behavior: SnackBarBehavior.floating,\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/bloc/todos_overview_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\npart 'todos_overview_event.dart';\npart 'todos_overview_state.dart';\n\nclass TodosOverviewBloc extends Bloc<TodosOverviewEvent, TodosOverviewState> {\n  TodosOverviewBloc({\n    required TodosRepository todosRepository,\n  }) : _todosRepository = todosRepository,\n       super(const TodosOverviewState()) {\n    on<TodosOverviewSubscriptionRequested>(_onSubscriptionRequested);\n    on<TodosOverviewTodoCompletionToggled>(_onTodoCompletionToggled);\n    on<TodosOverviewTodoDeleted>(_onTodoDeleted);\n    on<TodosOverviewUndoDeletionRequested>(_onUndoDeletionRequested);\n    on<TodosOverviewFilterChanged>(_onFilterChanged);\n    on<TodosOverviewToggleAllRequested>(_onToggleAllRequested);\n    on<TodosOverviewClearCompletedRequested>(_onClearCompletedRequested);\n  }\n\n  final TodosRepository _todosRepository;\n\n  Future<void> _onSubscriptionRequested(\n    TodosOverviewSubscriptionRequested event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    emit(state.copyWith(status: () => TodosOverviewStatus.loading));\n\n    await emit.forEach<List<Todo>>(\n      _todosRepository.getTodos(),\n      onData: (todos) => state.copyWith(\n        status: () => TodosOverviewStatus.success,\n        todos: () => todos,\n      ),\n      onError: (_, _) => state.copyWith(\n        status: () => TodosOverviewStatus.failure,\n      ),\n    );\n  }\n\n  Future<void> _onTodoCompletionToggled(\n    TodosOverviewTodoCompletionToggled event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    final newTodo = event.todo.copyWith(isCompleted: event.isCompleted);\n    await _todosRepository.saveTodo(newTodo);\n  }\n\n  Future<void> _onTodoDeleted(\n    TodosOverviewTodoDeleted event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    emit(state.copyWith(lastDeletedTodo: () => event.todo));\n    await _todosRepository.deleteTodo(event.todo.id);\n  }\n\n  Future<void> _onUndoDeletionRequested(\n    TodosOverviewUndoDeletionRequested event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    assert(\n      state.lastDeletedTodo != null,\n      'Last deleted todo can not be null.',\n    );\n\n    final todo = state.lastDeletedTodo!;\n    emit(state.copyWith(lastDeletedTodo: () => null));\n    await _todosRepository.saveTodo(todo);\n  }\n\n  void _onFilterChanged(\n    TodosOverviewFilterChanged event,\n    Emitter<TodosOverviewState> emit,\n  ) {\n    emit(state.copyWith(filter: () => event.filter));\n  }\n\n  Future<void> _onToggleAllRequested(\n    TodosOverviewToggleAllRequested event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    final areAllCompleted = state.todos.every((todo) => todo.isCompleted);\n    await _todosRepository.completeAll(isCompleted: !areAllCompleted);\n  }\n\n  Future<void> _onClearCompletedRequested(\n    TodosOverviewClearCompletedRequested event,\n    Emitter<TodosOverviewState> emit,\n  ) async {\n    await _todosRepository.clearCompleted();\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/bloc/todos_overview_event.dart",
    "content": "part of 'todos_overview_bloc.dart';\n\nsealed class TodosOverviewEvent extends Equatable {\n  const TodosOverviewEvent();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class TodosOverviewSubscriptionRequested extends TodosOverviewEvent {\n  const TodosOverviewSubscriptionRequested();\n}\n\nfinal class TodosOverviewTodoCompletionToggled extends TodosOverviewEvent {\n  const TodosOverviewTodoCompletionToggled({\n    required this.todo,\n    required this.isCompleted,\n  });\n\n  final Todo todo;\n  final bool isCompleted;\n\n  @override\n  List<Object> get props => [todo, isCompleted];\n}\n\nfinal class TodosOverviewTodoDeleted extends TodosOverviewEvent {\n  const TodosOverviewTodoDeleted(this.todo);\n\n  final Todo todo;\n\n  @override\n  List<Object> get props => [todo];\n}\n\nfinal class TodosOverviewUndoDeletionRequested extends TodosOverviewEvent {\n  const TodosOverviewUndoDeletionRequested();\n}\n\nclass TodosOverviewFilterChanged extends TodosOverviewEvent {\n  const TodosOverviewFilterChanged(this.filter);\n\n  final TodosViewFilter filter;\n\n  @override\n  List<Object> get props => [filter];\n}\n\nclass TodosOverviewToggleAllRequested extends TodosOverviewEvent {\n  const TodosOverviewToggleAllRequested();\n}\n\nclass TodosOverviewClearCompletedRequested extends TodosOverviewEvent {\n  const TodosOverviewClearCompletedRequested();\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/bloc/todos_overview_state.dart",
    "content": "part of 'todos_overview_bloc.dart';\n\nenum TodosOverviewStatus { initial, loading, success, failure }\n\nfinal class TodosOverviewState extends Equatable {\n  const TodosOverviewState({\n    this.status = TodosOverviewStatus.initial,\n    this.todos = const [],\n    this.filter = TodosViewFilter.all,\n    this.lastDeletedTodo,\n  });\n\n  final TodosOverviewStatus status;\n  final List<Todo> todos;\n  final TodosViewFilter filter;\n  final Todo? lastDeletedTodo;\n\n  Iterable<Todo> get filteredTodos => filter.applyAll(todos);\n\n  TodosOverviewState copyWith({\n    TodosOverviewStatus Function()? status,\n    List<Todo> Function()? todos,\n    TodosViewFilter Function()? filter,\n    Todo? Function()? lastDeletedTodo,\n  }) {\n    return TodosOverviewState(\n      status: status != null ? status() : this.status,\n      todos: todos != null ? todos() : this.todos,\n      filter: filter != null ? filter() : this.filter,\n      lastDeletedTodo: lastDeletedTodo != null\n          ? lastDeletedTodo()\n          : this.lastDeletedTodo,\n    );\n  }\n\n  @override\n  List<Object?> get props => [\n    status,\n    todos,\n    filter,\n    lastDeletedTodo,\n  ];\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/models/models.dart",
    "content": "export 'todos_view_filter.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/models/todos_view_filter.dart",
    "content": "import 'package:todos_repository/todos_repository.dart';\n\nenum TodosViewFilter { all, activeOnly, completedOnly }\n\nextension TodosViewFilterX on TodosViewFilter {\n  bool apply(Todo todo) {\n    switch (this) {\n      case TodosViewFilter.all:\n        return true;\n      case TodosViewFilter.activeOnly:\n        return !todo.isCompleted;\n      case TodosViewFilter.completedOnly:\n        return todo.isCompleted;\n    }\n  }\n\n  Iterable<Todo> applyAll(Iterable<Todo> todos) {\n    return todos.where(apply);\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/todos_overview.dart",
    "content": "export 'bloc/todos_overview_bloc.dart';\nexport 'models/models.dart';\nexport 'view/view.dart';\nexport 'widgets/widgets.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/view/todos_overview_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/edit_todo/view/edit_todo_page.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass TodosOverviewPage extends StatelessWidget {\n  const TodosOverviewPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (context) => TodosOverviewBloc(\n        todosRepository: context.read<TodosRepository>(),\n      )..add(const TodosOverviewSubscriptionRequested()),\n      child: const TodosOverviewView(),\n    );\n  }\n}\n\nclass TodosOverviewView extends StatelessWidget {\n  const TodosOverviewView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(l10n.todosOverviewAppBarTitle),\n        actions: const [\n          TodosOverviewFilterButton(),\n          TodosOverviewOptionsButton(),\n        ],\n      ),\n      body: MultiBlocListener(\n        listeners: [\n          BlocListener<TodosOverviewBloc, TodosOverviewState>(\n            listenWhen: (previous, current) =>\n                previous.status != current.status,\n            listener: (context, state) {\n              if (state.status == TodosOverviewStatus.failure) {\n                ScaffoldMessenger.of(context)\n                  ..hideCurrentSnackBar()\n                  ..showSnackBar(\n                    SnackBar(\n                      content: Text(l10n.todosOverviewErrorSnackbarText),\n                    ),\n                  );\n              }\n            },\n          ),\n          BlocListener<TodosOverviewBloc, TodosOverviewState>(\n            listenWhen: (previous, current) =>\n                previous.lastDeletedTodo != current.lastDeletedTodo &&\n                current.lastDeletedTodo != null,\n            listener: (context, state) {\n              final deletedTodo = state.lastDeletedTodo!;\n              final messenger = ScaffoldMessenger.of(context);\n              messenger\n                ..hideCurrentSnackBar()\n                ..showSnackBar(\n                  SnackBar(\n                    content: Text(\n                      l10n.todosOverviewTodoDeletedSnackbarText(\n                        deletedTodo.title,\n                      ),\n                    ),\n                    action: SnackBarAction(\n                      label: l10n.todosOverviewUndoDeletionButtonText,\n                      onPressed: () {\n                        messenger.hideCurrentSnackBar();\n                        context.read<TodosOverviewBloc>().add(\n                          const TodosOverviewUndoDeletionRequested(),\n                        );\n                      },\n                    ),\n                  ),\n                );\n            },\n          ),\n        ],\n        child: BlocBuilder<TodosOverviewBloc, TodosOverviewState>(\n          builder: (context, state) {\n            if (state.todos.isEmpty) {\n              if (state.status == TodosOverviewStatus.loading) {\n                return const Center(child: CupertinoActivityIndicator());\n              } else if (state.status != TodosOverviewStatus.success) {\n                return const SizedBox();\n              } else {\n                return Center(\n                  child: Text(\n                    l10n.todosOverviewEmptyText,\n                    style: Theme.of(context).textTheme.bodySmall,\n                  ),\n                );\n              }\n            }\n\n            return CupertinoScrollbar(\n              child: ListView.builder(\n                itemCount: state.filteredTodos.length,\n                itemBuilder: (_, index) {\n                  final todo = state.filteredTodos.elementAt(index);\n                  return TodoListTile(\n                    todo: todo,\n                    onToggleCompleted: (isCompleted) {\n                      context.read<TodosOverviewBloc>().add(\n                        TodosOverviewTodoCompletionToggled(\n                          todo: todo,\n                          isCompleted: isCompleted,\n                        ),\n                      );\n                    },\n                    onDismissed: (_) {\n                      context.read<TodosOverviewBloc>().add(\n                        TodosOverviewTodoDeleted(todo),\n                      );\n                    },\n                    onTap: () {\n                      Navigator.of(context).push(\n                        EditTodoPage.route(initialTodo: todo),\n                      );\n                    },\n                  );\n                },\n              ),\n            );\n          },\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/view/view.dart",
    "content": "export 'todos_overview_page.dart';\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/widgets/todo_list_tile.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass TodoListTile extends StatelessWidget {\n  const TodoListTile({\n    required this.todo,\n    super.key,\n    this.onToggleCompleted,\n    this.onDismissed,\n    this.onTap,\n  });\n\n  final Todo todo;\n  final ValueChanged<bool>? onToggleCompleted;\n  final DismissDirectionCallback? onDismissed;\n  final VoidCallback? onTap;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    final captionColor = theme.textTheme.bodySmall?.color;\n\n    return Dismissible(\n      key: Key('todoListTile_dismissible_${todo.id}'),\n      onDismissed: onDismissed,\n      direction: DismissDirection.endToStart,\n      background: Container(\n        alignment: Alignment.centerRight,\n        color: theme.colorScheme.error,\n        padding: const EdgeInsets.symmetric(horizontal: 16),\n        child: const Icon(\n          Icons.delete,\n          color: Color(0xAAFFFFFF),\n        ),\n      ),\n      child: ListTile(\n        onTap: onTap,\n        title: Text(\n          todo.title,\n          maxLines: 1,\n          overflow: TextOverflow.ellipsis,\n          style: !todo.isCompleted\n              ? null\n              : TextStyle(\n                  color: captionColor,\n                  decoration: TextDecoration.lineThrough,\n                ),\n        ),\n        subtitle: Text(\n          todo.description,\n          maxLines: 1,\n          overflow: TextOverflow.ellipsis,\n        ),\n        leading: Checkbox(\n          shape: const ContinuousRectangleBorder(\n            borderRadius: BorderRadius.all(Radius.circular(8)),\n          ),\n          value: todo.isCompleted,\n          onChanged: onToggleCompleted == null\n              ? null\n              : (value) => onToggleCompleted!(value!),\n        ),\n        trailing: onTap == null ? null : const Icon(Icons.chevron_right),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/widgets/todos_overview_filter_button.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\n\nclass TodosOverviewFilterButton extends StatelessWidget {\n  const TodosOverviewFilterButton({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n\n    final activeFilter = context.select(\n      (TodosOverviewBloc bloc) => bloc.state.filter,\n    );\n\n    return PopupMenuButton<TodosViewFilter>(\n      shape: const ContinuousRectangleBorder(\n        borderRadius: BorderRadius.all(Radius.circular(16)),\n      ),\n      initialValue: activeFilter,\n      tooltip: l10n.todosOverviewFilterTooltip,\n      onSelected: (filter) {\n        context.read<TodosOverviewBloc>().add(\n          TodosOverviewFilterChanged(filter),\n        );\n      },\n      itemBuilder: (context) {\n        return [\n          PopupMenuItem(\n            value: TodosViewFilter.all,\n            child: Text(l10n.todosOverviewFilterAll),\n          ),\n          PopupMenuItem(\n            value: TodosViewFilter.activeOnly,\n            child: Text(l10n.todosOverviewFilterActiveOnly),\n          ),\n          PopupMenuItem(\n            value: TodosViewFilter.completedOnly,\n            child: Text(l10n.todosOverviewFilterCompletedOnly),\n          ),\n        ];\n      },\n      icon: const Icon(Icons.filter_list_rounded),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/widgets/todos_overview_options_button.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\n\n@visibleForTesting\nenum TodosOverviewOption { toggleAll, clearCompleted }\n\nclass TodosOverviewOptionsButton extends StatelessWidget {\n  const TodosOverviewOptionsButton({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final l10n = context.l10n;\n\n    final todos = context.select((TodosOverviewBloc bloc) => bloc.state.todos);\n    final hasTodos = todos.isNotEmpty;\n    final completedTodosAmount = todos.where((todo) => todo.isCompleted).length;\n\n    return PopupMenuButton<TodosOverviewOption>(\n      shape: const ContinuousRectangleBorder(\n        borderRadius: BorderRadius.all(Radius.circular(16)),\n      ),\n      tooltip: l10n.todosOverviewOptionsTooltip,\n      onSelected: (options) {\n        switch (options) {\n          case TodosOverviewOption.toggleAll:\n            context.read<TodosOverviewBloc>().add(\n              const TodosOverviewToggleAllRequested(),\n            );\n          case TodosOverviewOption.clearCompleted:\n            context.read<TodosOverviewBloc>().add(\n              const TodosOverviewClearCompletedRequested(),\n            );\n        }\n      },\n      itemBuilder: (context) {\n        return [\n          PopupMenuItem(\n            value: TodosOverviewOption.toggleAll,\n            enabled: hasTodos,\n            child: Text(\n              completedTodosAmount == todos.length\n                  ? l10n.todosOverviewOptionsMarkAllIncomplete\n                  : l10n.todosOverviewOptionsMarkAllComplete,\n            ),\n          ),\n          PopupMenuItem(\n            value: TodosOverviewOption.clearCompleted,\n            enabled: hasTodos && completedTodosAmount > 0,\n            child: Text(l10n.todosOverviewOptionsClearCompleted),\n          ),\n        ];\n      },\n      icon: const Icon(Icons.more_vert_rounded),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/lib/todos_overview/widgets/widgets.dart",
    "content": "export 'todo_list_tile.dart';\nexport 'todos_overview_filter_button.dart';\nexport 'todos_overview_options_button.dart';\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# VSCode related\n.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Web related\nlib/generated_plugin_registrant.dart\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/README.md",
    "content": "# local_storage_todos_api\n\n[![License: MIT][license_badge]][license_link]\n\nA Very Good Project created by Very Good CLI.\n\n[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[license_link]: https://opensource.org/licenses/MIT\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/lib/local_storage_todos_api.dart",
    "content": "/// A Flutter implementation of the TodosApi that uses local storage.\nlibrary local_storage_todos_api;\n\nexport 'package:shared_preferences/shared_preferences.dart'\n    show SharedPreferences;\n\nexport 'src/local_storage_todos_api.dart';\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/lib/src/local_storage_todos_api.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\n\nimport 'package:meta/meta.dart';\nimport 'package:rxdart/subjects.dart';\nimport 'package:shared_preferences/shared_preferences.dart';\nimport 'package:todos_api/todos_api.dart';\n\n/// {@template local_storage_todos_api}\n/// A Flutter implementation of the [TodosApi] that uses local storage.\n/// {@endtemplate}\nclass LocalStorageTodosApi extends TodosApi {\n  /// {@macro local_storage_todos_api}\n  LocalStorageTodosApi({\n    required SharedPreferences plugin,\n  }) : _plugin = plugin {\n    _init();\n  }\n\n  final SharedPreferences _plugin;\n\n  late final _todoStreamController = BehaviorSubject<List<Todo>>.seeded(\n    const [],\n  );\n\n  /// The key used for storing the todos locally.\n  ///\n  /// This is only exposed for testing and shouldn't be used by consumers of\n  /// this library.\n  @visibleForTesting\n  static const kTodosCollectionKey = '__todos_collection_key__';\n\n  String? _getValue(String key) => _plugin.getString(key);\n  Future<void> _setValue(String key, String value) =>\n      _plugin.setString(key, value);\n\n  void _init() {\n    final todosJson = _getValue(kTodosCollectionKey);\n    if (todosJson != null) {\n      final todos =\n          List<Map<dynamic, dynamic>>.from(\n                json.decode(todosJson) as List,\n              )\n              .map(\n                (jsonMap) => Todo.fromJson(Map<String, dynamic>.from(jsonMap)),\n              )\n              .toList();\n      _todoStreamController.add(todos);\n    } else {\n      _todoStreamController.add(const []);\n    }\n  }\n\n  @override\n  Stream<List<Todo>> getTodos() => _todoStreamController.asBroadcastStream();\n\n  @override\n  Future<void> saveTodo(Todo todo) {\n    final todos = [..._todoStreamController.value];\n    final todoIndex = todos.indexWhere((t) => t.id == todo.id);\n    if (todoIndex >= 0) {\n      todos[todoIndex] = todo;\n    } else {\n      todos.add(todo);\n    }\n\n    _todoStreamController.add(todos);\n    return _setValue(kTodosCollectionKey, json.encode(todos));\n  }\n\n  @override\n  Future<void> deleteTodo(String id) async {\n    final todos = [..._todoStreamController.value];\n    final todoIndex = todos.indexWhere((t) => t.id == id);\n    if (todoIndex == -1) {\n      throw TodoNotFoundException();\n    } else {\n      todos.removeAt(todoIndex);\n      _todoStreamController.add(todos);\n      return _setValue(kTodosCollectionKey, json.encode(todos));\n    }\n  }\n\n  @override\n  Future<int> clearCompleted() async {\n    final todos = [..._todoStreamController.value];\n    final initialLength = todos.length;\n\n    todos.removeWhere((t) => t.isCompleted);\n    final completedTodosAmount = initialLength - todos.length;\n\n    _todoStreamController.add(todos);\n    await _setValue(kTodosCollectionKey, json.encode(todos));\n    return completedTodosAmount;\n  }\n\n  @override\n  Future<int> completeAll({required bool isCompleted}) async {\n    final todos = [..._todoStreamController.value];\n    final changedTodosAmount = todos\n        .where((t) => t.isCompleted != isCompleted)\n        .length;\n    final newTodos = [\n      for (final todo in todos) todo.copyWith(isCompleted: isCompleted),\n    ];\n    _todoStreamController.add(newTodos);\n    await _setValue(kTodosCollectionKey, json.encode(newTodos));\n    return changedTodosAmount;\n  }\n\n  @override\n  Future<void> close() {\n    return _todoStreamController.close();\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/pubspec.yaml",
    "content": "name: local_storage_todos_api\ndescription: A Flutter implementation of the TodosApi that uses local storage.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  meta: ^1.8.0\n  rxdart: ^0.28.0\n  shared_preferences: ^2.0.0\n  todos_api:\n    path: ../todos_api\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  mocktail: ^1.0.0\n"
  },
  {
    "path": "examples/flutter_todos/packages/local_storage_todos_api/test/local_storage_todos_api_test.dart",
    "content": "import 'dart:convert';\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:local_storage_todos_api/local_storage_todos_api.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_api/todos_api.dart';\n\nclass MockSharedPreferences extends Mock implements SharedPreferences {}\n\nvoid main() {\n  group('LocalStorageTodosApi', () {\n    late SharedPreferences plugin;\n\n    final todos = [\n      Todo(\n        id: '1',\n        title: 'title 1',\n        description: 'description 1',\n      ),\n      Todo(\n        id: '2',\n        title: 'title 2',\n        description: 'description 2',\n      ),\n      Todo(\n        id: '3',\n        title: 'title 3',\n        description: 'description 3',\n        isCompleted: true,\n      ),\n    ];\n\n    setUp(() {\n      plugin = MockSharedPreferences();\n      when(() => plugin.getString(any())).thenReturn(json.encode(todos));\n      when(() => plugin.setString(any(), any())).thenAnswer((_) async => true);\n    });\n\n    LocalStorageTodosApi createSubject() {\n      return LocalStorageTodosApi(\n        plugin: plugin,\n      );\n    }\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(\n          createSubject,\n          returnsNormally,\n        );\n      });\n\n      group('initializes the todos stream', () {\n        test('with existing todos if present', () {\n          final subject = createSubject();\n\n          expect(subject.getTodos(), emits(todos));\n          verify(\n            () => plugin.getString(\n              LocalStorageTodosApi.kTodosCollectionKey,\n            ),\n          ).called(1);\n        });\n\n        test('with empty list if no todos present', () {\n          when(() => plugin.getString(any())).thenReturn(null);\n\n          final subject = createSubject();\n\n          expect(subject.getTodos(), emits(const <Todo>[]));\n          verify(\n            () => plugin.getString(\n              LocalStorageTodosApi.kTodosCollectionKey,\n            ),\n          ).called(1);\n        });\n      });\n    });\n\n    test('getTodos returns stream of current list todos', () {\n      expect(\n        createSubject().getTodos(),\n        emits(todos),\n      );\n    });\n\n    group('saveTodo', () {\n      test('saves new todos', () {\n        final newTodo = Todo(\n          id: '4',\n          title: 'title 4',\n          description: 'description 4',\n        );\n\n        final newTodos = [...todos, newTodo];\n\n        final subject = createSubject();\n\n        expect(subject.saveTodo(newTodo), completes);\n        expect(subject.getTodos(), emits(newTodos));\n\n        verify(\n          () => plugin.setString(\n            LocalStorageTodosApi.kTodosCollectionKey,\n            json.encode(newTodos),\n          ),\n        ).called(1);\n      });\n\n      test('updates existing todos', () {\n        final updatedTodo = Todo(\n          id: '1',\n          title: 'new title 1',\n          description: 'new description 1',\n          isCompleted: true,\n        );\n        final newTodos = [updatedTodo, ...todos.sublist(1)];\n\n        final subject = createSubject();\n\n        expect(subject.saveTodo(updatedTodo), completes);\n        expect(subject.getTodos(), emits(newTodos));\n\n        verify(\n          () => plugin.setString(\n            LocalStorageTodosApi.kTodosCollectionKey,\n            json.encode(newTodos),\n          ),\n        ).called(1);\n      });\n    });\n\n    group('deleteTodo', () {\n      test('deletes existing todos', () {\n        final newTodos = todos.sublist(1);\n\n        final subject = createSubject();\n\n        expect(subject.deleteTodo(todos[0].id), completes);\n        expect(subject.getTodos(), emits(newTodos));\n\n        verify(\n          () => plugin.setString(\n            LocalStorageTodosApi.kTodosCollectionKey,\n            json.encode(newTodos),\n          ),\n        ).called(1);\n      });\n\n      test(\n        'throws TodoNotFoundException if todo '\n        'with provided id is not found',\n        () {\n          final subject = createSubject();\n\n          expect(\n            () => subject.deleteTodo('non-existing-id'),\n            throwsA(isA<TodoNotFoundException>()),\n          );\n        },\n      );\n    });\n\n    group('clearCompleted', () {\n      test('deletes all completed todos', () {\n        final newTodos = todos.where((todo) => !todo.isCompleted).toList();\n        final deletedTodosAmount = todos.length - newTodos.length;\n\n        final subject = createSubject();\n\n        expect(\n          subject.clearCompleted(),\n          completion(equals(deletedTodosAmount)),\n        );\n        expect(subject.getTodos(), emits(newTodos));\n\n        verify(\n          () => plugin.setString(\n            LocalStorageTodosApi.kTodosCollectionKey,\n            json.encode(newTodos),\n          ),\n        ).called(1);\n      });\n    });\n\n    group('completeAll', () {\n      test('sets isCompleted on all todos to provided value', () {\n        final newTodos = todos\n            .map((todo) => todo.copyWith(isCompleted: true))\n            .toList();\n        final changedTodosAmount = todos\n            .where((todo) => !todo.isCompleted)\n            .length;\n\n        final subject = createSubject();\n\n        expect(\n          subject.completeAll(isCompleted: true),\n          completion(equals(changedTodosAmount)),\n        );\n        expect(subject.getTodos(), emits(newTodos));\n\n        verify(\n          () => plugin.setString(\n            LocalStorageTodosApi.kTodosCollectionKey,\n            json.encode(newTodos),\n          ),\n        ).called(1);\n      });\n    });\n\n    group('close', () {\n      test('closes the instance', () async {\n        final subject = createSubject();\n\n        await subject.close();\n\n        expect(\n          () => subject.saveTodo(\n            Todo(id: '1', title: 'title 1'),\n          ),\n          throwsStateError,\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/.gitignore",
    "content": "# See https://www.dartlang.org/guides/libraries/private-files\n\n# Files and directories created by pub\n.dart_tool/\n.packages\nbuild/\npubspec.lock"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/README.md",
    "content": "# todos_api\n\n[![License: MIT][license_badge]][license_link]\n\nA Very Good Project created by Very Good CLI.\n\n[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[license_link]: https://opensource.org/licenses/MIT"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/src/models/json_map.dart",
    "content": "/// The type definition for a JSON-serializable [Map].\ntypedef JsonMap = Map<String, dynamic>;\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/src/models/models.dart",
    "content": "export 'json_map.dart';\nexport 'todo.dart';\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/src/models/todo.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:meta/meta.dart';\nimport 'package:todos_api/todos_api.dart';\nimport 'package:uuid/uuid.dart';\n\npart 'todo.g.dart';\n\n/// {@template todo_item}\n/// A single `todo` item.\n///\n/// Contains a [title], [description] and [id], in addition to a [isCompleted]\n/// flag.\n///\n/// If an [id] is provided, it cannot be empty. If no [id] is provided, one\n/// will be generated.\n///\n/// [Todo]s are immutable and can be copied using [copyWith], in addition to\n/// being serialized and deserialized using [toJson] and [fromJson]\n/// respectively.\n/// {@endtemplate}\n@immutable\n@JsonSerializable()\nclass Todo extends Equatable {\n  /// {@macro todo_item}\n  Todo({\n    required this.title,\n    String? id,\n    this.description = '',\n    this.isCompleted = false,\n  }) : assert(\n         id == null || id.isNotEmpty,\n         'id must either be null or not empty',\n       ),\n       id = id ?? const Uuid().v4();\n\n  /// The unique identifier of the `todo`.\n  ///\n  /// Cannot be empty.\n  final String id;\n\n  /// The title of the `todo`.\n  ///\n  /// Note that the title may be empty.\n  final String title;\n\n  /// The description of the `todo`.\n  ///\n  /// Defaults to an empty string.\n  final String description;\n\n  /// Whether the `todo` is completed.\n  ///\n  /// Defaults to `false`.\n  final bool isCompleted;\n\n  /// Returns a copy of this `todo` with the given values updated.\n  ///\n  /// {@macro todo_item}\n  Todo copyWith({\n    String? id,\n    String? title,\n    String? description,\n    bool? isCompleted,\n  }) {\n    return Todo(\n      id: id ?? this.id,\n      title: title ?? this.title,\n      description: description ?? this.description,\n      isCompleted: isCompleted ?? this.isCompleted,\n    );\n  }\n\n  /// Deserializes the given [JsonMap] into a [Todo].\n  static Todo fromJson(JsonMap json) => _$TodoFromJson(json);\n\n  /// Converts this [Todo] into a [JsonMap].\n  JsonMap toJson() => _$TodoToJson(this);\n\n  @override\n  List<Object> get props => [id, title, description, isCompleted];\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/src/models/todo.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'todo.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTodo _$TodoFromJson(Map<String, dynamic> json) => Todo(\n  id: json['id'] as String?,\n  title: json['title'] as String,\n  description: json['description'] as String? ?? '',\n  isCompleted: json['isCompleted'] as bool? ?? false,\n);\n\nMap<String, dynamic> _$TodoToJson(Todo instance) => <String, dynamic>{\n  'id': instance.id,\n  'title': instance.title,\n  'description': instance.description,\n  'isCompleted': instance.isCompleted,\n};\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/src/todos_api.dart",
    "content": "import 'package:todos_api/todos_api.dart';\n\n/// {@template todos_api}\n/// The interface for an API that provides access to a list of todos.\n/// {@endtemplate}\nabstract class TodosApi {\n  /// {@macro todos_api}\n  const TodosApi();\n\n  /// Provides a [Stream] of all todos.\n  Stream<List<Todo>> getTodos();\n\n  /// Saves a [todo].\n  ///\n  /// If a [todo] with the same id already exists, it will be replaced.\n  Future<void> saveTodo(Todo todo);\n\n  /// Deletes the `todo` with the given id.\n  ///\n  /// If no `todo` with the given id exists, a [TodoNotFoundException] error is\n  /// thrown.\n  Future<void> deleteTodo(String id);\n\n  /// Deletes all completed todos.\n  ///\n  /// Returns the number of deleted todos.\n  Future<int> clearCompleted();\n\n  /// Sets the `isCompleted` state of all todos to the given value.\n  ///\n  /// Returns the number of updated todos.\n  Future<int> completeAll({required bool isCompleted});\n\n  /// Closes the client and frees up any resources.\n  Future<void> close();\n}\n\n/// Error thrown when a [Todo] with a given id is not found.\nclass TodoNotFoundException implements Exception {}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/lib/todos_api.dart",
    "content": "/// The interface and models for an API providing access to todos.\nlibrary todos_api;\n\nexport 'src/models/models.dart';\nexport 'src/todos_api.dart';\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/pubspec.yaml",
    "content": "name: todos_api\ndescription: The interface and models for an API providing access to todos.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  equatable: ^2.0.0\n  json_annotation: ^4.6.0\n  meta: ^1.7.0\n  uuid: ^4.5.1\n\ndev_dependencies:\n  build_runner: ^2.2.0\n  json_serializable: ^6.3.1\n  test: ^1.21.4\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/test/models/todo_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\nimport 'package:test/test.dart';\nimport 'package:todos_api/todos_api.dart';\n\nvoid main() {\n  group('Todo', () {\n    Todo createSubject({\n      String? id = '1',\n      String title = 'title',\n      String description = 'description',\n      bool isCompleted = true,\n    }) {\n      return Todo(\n        id: id,\n        title: title,\n        description: description,\n        isCompleted: isCompleted,\n      );\n    }\n\n    group('constructor', () {\n      test('works correctly', () {\n        expect(\n          createSubject,\n          returnsNormally,\n        );\n      });\n\n      test('throws AssertionError when id is empty', () {\n        expect(\n          () => createSubject(id: ''),\n          throwsA(isA<AssertionError>()),\n        );\n      });\n\n      test('sets id if not provided', () {\n        expect(\n          createSubject(id: null).id,\n          isNotEmpty,\n        );\n      });\n    });\n\n    test('supports value equality', () {\n      expect(\n        createSubject(),\n        equals(createSubject()),\n      );\n    });\n\n    test('props are correct', () {\n      expect(\n        createSubject().props,\n        equals([\n          '1', // id\n          'title', // title\n          'description', // description\n          true, // isCompleted\n        ]),\n      );\n    });\n\n    group('copyWith', () {\n      test('returns the same object if not arguments are provided', () {\n        expect(\n          createSubject().copyWith(),\n          equals(createSubject()),\n        );\n      });\n\n      test('retains the old value for every parameter if null is provided', () {\n        expect(\n          createSubject().copyWith(\n            id: null,\n            title: null,\n            description: null,\n            isCompleted: null,\n          ),\n          equals(createSubject()),\n        );\n      });\n\n      test('replaces every non-null parameter', () {\n        expect(\n          createSubject().copyWith(\n            id: '2',\n            title: 'new title',\n            description: 'new description',\n            isCompleted: false,\n          ),\n          equals(\n            createSubject(\n              id: '2',\n              title: 'new title',\n              description: 'new description',\n              isCompleted: false,\n            ),\n          ),\n        );\n      });\n    });\n\n    group('fromJson', () {\n      test('works correctly', () {\n        expect(\n          Todo.fromJson(<String, dynamic>{\n            'id': '1',\n            'title': 'title',\n            'description': 'description',\n            'isCompleted': true,\n          }),\n          equals(createSubject()),\n        );\n      });\n    });\n\n    group('toJson', () {\n      test('works correctly', () {\n        expect(\n          createSubject().toJson(),\n          equals(<String, dynamic>{\n            'id': '1',\n            'title': 'title',\n            'description': 'description',\n            'isCompleted': true,\n          }),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_api/test/todos_api_test.dart",
    "content": "import 'package:test/test.dart';\nimport 'package:todos_api/todos_api.dart';\n\nclass TestTodosApi extends TodosApi {\n  TestTodosApi() : super();\n\n  @override\n  dynamic noSuchMethod(Invocation invocation) {\n    return super.noSuchMethod(invocation);\n  }\n}\n\nvoid main() {\n  group('TodosApi', () {\n    test('can be constructed', () {\n      expect(TestTodosApi.new, returnsNormally);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/.gitignore",
    "content": "# See https://www.dartlang.org/guides/libraries/private-files\n\n# Files and directories created by pub\n.dart_tool/\n.packages\nbuild/\npubspec.lock"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/README.md",
    "content": "# todos_repository\n\n[![License: MIT][license_badge]][license_link]\n\nA Very Good Project created by Very Good CLI.\n\n[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[license_link]: https://opensource.org/licenses/MIT"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/lib/src/todos_repository.dart",
    "content": "import 'package:todos_api/todos_api.dart';\n\n/// {@template todos_repository}\n/// A repository that handles `todo` related requests.\n/// {@endtemplate}\nclass TodosRepository {\n  /// {@macro todos_repository}\n  const TodosRepository({\n    required TodosApi todosApi,\n  }) : _todosApi = todosApi;\n\n  final TodosApi _todosApi;\n\n  /// Provides a [Stream] of all todos.\n  Stream<List<Todo>> getTodos() => _todosApi.getTodos();\n\n  /// Saves a [todo].\n  ///\n  /// If a [todo] with the same id already exists, it will be replaced.\n  Future<void> saveTodo(Todo todo) => _todosApi.saveTodo(todo);\n\n  /// Deletes the `todo` with the given id.\n  ///\n  /// If no `todo` with the given id exists, a [TodoNotFoundException] error is\n  /// thrown.\n  Future<void> deleteTodo(String id) => _todosApi.deleteTodo(id);\n\n  /// Deletes all completed todos.\n  ///\n  /// Returns the number of deleted todos.\n  Future<int> clearCompleted() => _todosApi.clearCompleted();\n\n  /// Sets the `isCompleted` state of all todos to the given value.\n  ///\n  /// Returns the number of updated todos.\n  Future<int> completeAll({required bool isCompleted}) =>\n      _todosApi.completeAll(isCompleted: isCompleted);\n\n  /// Disposes any resources managed by the repository.\n  void dispose() => _todosApi.close();\n}\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/lib/todos_repository.dart",
    "content": "/// A repository that handles `todo` related requests.\nlibrary todos_repository;\n\nexport 'package:todos_api/todos_api.dart' show Todo;\nexport 'src/todos_repository.dart';\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/pubspec.yaml",
    "content": "name: todos_repository\ndescription: A repository that handles todo related requests.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  todos_api:\n    path: ../todos_api\n\ndev_dependencies:\n  mocktail: ^1.0.0\n  test: ^1.21.4\n"
  },
  {
    "path": "examples/flutter_todos/packages/todos_repository/test/todos_repository_test.dart",
    "content": "import 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\nimport 'package:todos_api/todos_api.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass MockTodosApi extends Mock implements TodosApi {}\n\nclass FakeTodo extends Fake implements Todo {}\n\nvoid main() {\n  group('TodosRepository', () {\n    late TodosApi api;\n\n    final todos = [\n      Todo(\n        id: '1',\n        title: 'title 1',\n        description: 'description 1',\n      ),\n      Todo(\n        id: '2',\n        title: 'title 2',\n        description: 'description 2',\n      ),\n      Todo(\n        id: '3',\n        title: 'title 3',\n        description: 'description 3',\n        isCompleted: true,\n      ),\n    ];\n\n    setUpAll(() {\n      registerFallbackValue(FakeTodo());\n    });\n\n    setUp(() {\n      api = MockTodosApi();\n      when(() => api.getTodos()).thenAnswer((_) => Stream.value(todos));\n      when(() => api.saveTodo(any())).thenAnswer((_) async {});\n      when(() => api.deleteTodo(any())).thenAnswer((_) async {});\n      when(\n        () => api.clearCompleted(),\n      ).thenAnswer((_) async => todos.where((todo) => todo.isCompleted).length);\n      when(\n        () => api.completeAll(isCompleted: any(named: 'isCompleted')),\n      ).thenAnswer((_) async => 0);\n      when(() => api.close()).thenAnswer((_) async {});\n    });\n\n    TodosRepository createSubject() => TodosRepository(todosApi: api);\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(\n          createSubject,\n          returnsNormally,\n        );\n      });\n    });\n\n    group('getTodos', () {\n      test('makes correct api request', () {\n        final subject = createSubject();\n\n        expect(\n          subject.getTodos(),\n          isNot(throwsA(anything)),\n        );\n\n        verify(() => api.getTodos()).called(1);\n      });\n\n      test('returns stream of current list todos', () {\n        expect(\n          createSubject().getTodos(),\n          emits(todos),\n        );\n      });\n    });\n\n    group('saveTodo', () {\n      test('makes correct api request', () {\n        final newTodo = Todo(\n          id: '4',\n          title: 'title 4',\n          description: 'description 4',\n        );\n\n        final subject = createSubject();\n\n        expect(subject.saveTodo(newTodo), completes);\n\n        verify(() => api.saveTodo(newTodo)).called(1);\n      });\n    });\n\n    group('deleteTodo', () {\n      test('makes correct api request', () {\n        final subject = createSubject();\n\n        expect(subject.deleteTodo(todos[0].id), completes);\n\n        verify(() => api.deleteTodo(todos[0].id)).called(1);\n      });\n    });\n\n    group('clearCompleted', () {\n      test('makes correct request', () {\n        final subject = createSubject();\n\n        expect(subject.clearCompleted(), completes);\n\n        verify(() => api.clearCompleted()).called(1);\n      });\n    });\n\n    group('completeAll', () {\n      test('makes correct request', () {\n        final subject = createSubject();\n\n        expect(subject.completeAll(isCompleted: true), completes);\n\n        verify(() => api.completeAll(isCompleted: true)).called(1);\n      });\n    });\n\n    group('dispose', () {\n      test('closes the underlying api client', () {\n        final subject = createSubject();\n        verifyNever(api.close);\n        subject.dispose();\n        verify(api.close).called(1);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/pubspec.yaml",
    "content": "name: flutter_todos\ndescription: An example todos app that showcases bloc state management patterns.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  flutter_localizations:\n    sdk: flutter\n  intl: ^0.20.2\n  local_storage_todos_api:\n    path: packages/local_storage_todos_api\n  todos_api:\n    path: packages/todos_api\n  todos_repository:\n    path: packages/todos_repository\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  flutter_test:\n    sdk: flutter\n  mockingjay: ^2.0.0\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n  generate: true\n"
  },
  {
    "path": "examples/flutter_todos/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_todos/test/app_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/app/app.dart';\nimport 'package:flutter_todos/home/home.dart';\nimport 'package:flutter_todos/theme/theme.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport 'helpers/helpers.dart';\n\nvoid main() {\n  late TodosRepository todosRepository;\n\n  setUp(() {\n    todosRepository = MockTodosRepository();\n    when(\n      () => todosRepository.getTodos(),\n    ).thenAnswer((_) => const Stream.empty());\n  });\n\n  group('App', () {\n    testWidgets('renders AppView', (tester) async {\n      await tester.pumpWidget(\n        App(createTodosRepository: () => todosRepository),\n      );\n\n      expect(find.byType(AppView), findsOneWidget);\n    });\n  });\n\n  group('AppView', () {\n    testWidgets('renders MaterialApp with correct themes', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: todosRepository,\n          child: const AppView(),\n        ),\n      );\n\n      expect(find.byType(MaterialApp), findsOneWidget);\n\n      final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));\n      expect(materialApp.theme, equals(FlutterTodosTheme.light));\n      expect(materialApp.darkTheme, equals(FlutterTodosTheme.dark));\n    });\n\n    testWidgets('renders HomePage', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: todosRepository,\n          child: const AppView(),\n        ),\n      );\n\n      expect(find.byType(HomePage), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/edit_todo/bloc/edit_todo_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass MockTodosRepository extends Mock implements TodosRepository {}\n\nclass FakeTodo extends Fake implements Todo {}\n\nvoid main() {\n  group('EditTodoBloc', () {\n    late TodosRepository todosRepository;\n\n    setUpAll(() {\n      registerFallbackValue(FakeTodo());\n    });\n\n    setUp(() {\n      todosRepository = MockTodosRepository();\n    });\n\n    EditTodoBloc buildBloc() {\n      return EditTodoBloc(\n        todosRepository: todosRepository,\n        initialTodo: null,\n      );\n    }\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(buildBloc, returnsNormally);\n      });\n\n      test('has correct initial state', () {\n        expect(\n          buildBloc().state,\n          equals(const EditTodoState()),\n        );\n      });\n    });\n\n    group('EditTodoTitleChanged', () {\n      blocTest<EditTodoBloc, EditTodoState>(\n        'emits new state with updated title',\n        build: buildBloc,\n        act: (bloc) => bloc.add(const EditTodoTitleChanged('newtitle')),\n        expect: () => const [\n          EditTodoState(title: 'newtitle'),\n        ],\n      );\n    });\n\n    group('EditTodoDescriptionChanged', () {\n      blocTest<EditTodoBloc, EditTodoState>(\n        'emits new state with updated description',\n        build: buildBloc,\n        act: (bloc) =>\n            bloc.add(const EditTodoDescriptionChanged('newdescription')),\n        expect: () => const [\n          EditTodoState(description: 'newdescription'),\n        ],\n      );\n    });\n\n    group('EditTodoSubmitted', () {\n      blocTest<EditTodoBloc, EditTodoState>(\n        'attempts to save new todo to repository '\n        'if no initial todo was provided',\n        setUp: () {\n          when(() => todosRepository.saveTodo(any())).thenAnswer((_) async {});\n        },\n        build: buildBloc,\n        seed: () => const EditTodoState(\n          title: 'title',\n          description: 'description',\n        ),\n        act: (bloc) => bloc.add(const EditTodoSubmitted()),\n        expect: () => const [\n          EditTodoState(\n            status: EditTodoStatus.loading,\n            title: 'title',\n            description: 'description',\n          ),\n          EditTodoState(\n            status: EditTodoStatus.success,\n            title: 'title',\n            description: 'description',\n          ),\n        ],\n        verify: (bloc) {\n          verify(\n            () => todosRepository.saveTodo(\n              any(\n                that: isA<Todo>()\n                    .having((t) => t.title, 'title', equals('title'))\n                    .having(\n                      (t) => t.description,\n                      'description',\n                      equals('description'),\n                    ),\n              ),\n            ),\n          ).called(1);\n        },\n      );\n\n      blocTest<EditTodoBloc, EditTodoState>(\n        'attempts to save updated todo to repository '\n        'if an initial todo was provided',\n        setUp: () {\n          when(() => todosRepository.saveTodo(any())).thenAnswer((_) async {});\n        },\n        build: buildBloc,\n        seed: () => EditTodoState(\n          initialTodo: Todo(\n            id: 'initial-id',\n            title: 'initial-title',\n          ),\n          title: 'title',\n          description: 'description',\n        ),\n        act: (bloc) => bloc.add(const EditTodoSubmitted()),\n        expect: () => [\n          EditTodoState(\n            status: EditTodoStatus.loading,\n            initialTodo: Todo(\n              id: 'initial-id',\n              title: 'initial-title',\n            ),\n            title: 'title',\n            description: 'description',\n          ),\n          EditTodoState(\n            status: EditTodoStatus.success,\n            initialTodo: Todo(\n              id: 'initial-id',\n              title: 'initial-title',\n            ),\n            title: 'title',\n            description: 'description',\n          ),\n        ],\n        verify: (bloc) {\n          verify(\n            () => todosRepository.saveTodo(\n              any(\n                that: isA<Todo>()\n                    .having((t) => t.id, 'id', equals('initial-id'))\n                    .having((t) => t.title, 'title', equals('title'))\n                    .having(\n                      (t) => t.description,\n                      'description',\n                      equals('description'),\n                    ),\n              ),\n            ),\n          );\n        },\n      );\n\n      blocTest<EditTodoBloc, EditTodoState>(\n        'emits new state with error if save to repository fails',\n        build: () {\n          when(\n            () => todosRepository.saveTodo(any()),\n          ).thenThrow(Exception('oops'));\n          return buildBloc();\n        },\n        seed: () => const EditTodoState(\n          title: 'title',\n          description: 'description',\n        ),\n        act: (bloc) => bloc.add(const EditTodoSubmitted()),\n        expect: () => const [\n          EditTodoState(\n            status: EditTodoStatus.loading,\n            title: 'title',\n            description: 'description',\n          ),\n          EditTodoState(\n            status: EditTodoStatus.failure,\n            title: 'title',\n            description: 'description',\n          ),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/edit_todo/bloc/edit_todo_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\n\nvoid main() {\n  group('EditTodoEvent', () {\n    group('EditTodoTitleChanged', () {\n      test('supports value equality', () {\n        expect(\n          EditTodoTitleChanged('title'),\n          equals(EditTodoTitleChanged('title')),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          EditTodoTitleChanged('title').props,\n          equals(<Object?>[\n            'title', // title\n          ]),\n        );\n      });\n    });\n\n    group('EditTodoDescriptionChanged', () {\n      test('supports value equality', () {\n        expect(\n          EditTodoDescriptionChanged('description'),\n          equals(EditTodoDescriptionChanged('description')),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          EditTodoDescriptionChanged('description').props,\n          equals(<Object?>[\n            'description', // description\n          ]),\n        );\n      });\n    });\n\n    group('EditTodoSubmitted', () {\n      test('supports value equality', () {\n        expect(\n          EditTodoSubmitted(),\n          equals(EditTodoSubmitted()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          EditTodoSubmitted().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/edit_todo/bloc/edit_todo_state_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nvoid main() {\n  group('EditTodoState', () {\n    final mockInitialTodo = Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n    );\n\n    EditTodoState createSubject({\n      EditTodoStatus status = EditTodoStatus.initial,\n      Todo? initialTodo,\n      String title = '',\n      String description = '',\n    }) {\n      return EditTodoState(\n        status: status,\n        initialTodo: initialTodo,\n        title: title,\n        description: description,\n      );\n    }\n\n    test('supports value equality', () {\n      expect(\n        createSubject(),\n        equals(createSubject()),\n      );\n    });\n\n    test('props are correct', () {\n      expect(\n        createSubject(\n          status: EditTodoStatus.initial,\n          initialTodo: mockInitialTodo,\n          title: 'title',\n          description: 'description',\n        ).props,\n        equals(<Object?>[\n          EditTodoStatus.initial, // status\n          mockInitialTodo, // initialTodo\n          'title', // title\n          'description', // description\n        ]),\n      );\n    });\n\n    test('isNewTodo returns true when a new todo is being created', () {\n      expect(\n        createSubject(\n          initialTodo: null,\n        ).isNewTodo,\n        isTrue,\n      );\n    });\n\n    group('copyWith', () {\n      test('returns the same object if not arguments are provided', () {\n        expect(\n          createSubject().copyWith(),\n          equals(createSubject()),\n        );\n      });\n\n      test('retains the old value for every parameter if null is provided', () {\n        expect(\n          createSubject().copyWith(\n            status: null,\n            initialTodo: null,\n            title: null,\n            description: null,\n          ),\n          equals(createSubject()),\n        );\n      });\n\n      test('replaces every non-null parameter', () {\n        expect(\n          createSubject().copyWith(\n            status: EditTodoStatus.success,\n            initialTodo: mockInitialTodo,\n            title: 'title',\n            description: 'description',\n          ),\n          equals(\n            createSubject(\n              status: EditTodoStatus.success,\n              initialTodo: mockInitialTodo,\n              title: 'title',\n              description: 'description',\n            ),\n          ),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/edit_todo/view/edit_todo_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/edit_todo/edit_todo.dart';\nimport 'package:mockingjay/mockingjay.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockEditTodoBloc extends MockBloc<EditTodoEvent, EditTodoState>\n    implements EditTodoBloc {}\n\nvoid main() {\n  final mockTodo = Todo(\n    id: '1',\n    title: 'title 1',\n    description: 'description 1',\n  );\n\n  late MockNavigator navigator;\n  late EditTodoBloc editTodoBloc;\n\n  setUp(() {\n    navigator = MockNavigator();\n    when(() => navigator.canPop()).thenReturn(false);\n    when(() => navigator.push<void>(any())).thenAnswer((_) async {});\n\n    editTodoBloc = MockEditTodoBloc();\n    when(() => editTodoBloc.state).thenReturn(\n      EditTodoState(\n        initialTodo: mockTodo,\n        title: mockTodo.title,\n        description: mockTodo.description,\n      ),\n    );\n  });\n\n  group('EditTodoPage', () {\n    Widget buildSubject() {\n      return MockNavigatorProvider(\n        navigator: navigator,\n        child: BlocProvider.value(\n          value: editTodoBloc,\n          child: const EditTodoPage(),\n        ),\n      );\n    }\n\n    group('route', () {\n      testWidgets('renders EditTodoPage', (tester) async {\n        await tester.pumpRoute(EditTodoPage.route());\n        expect(find.byType(EditTodoPage), findsOneWidget);\n      });\n\n      testWidgets('supports providing an initial todo', (tester) async {\n        await tester.pumpRoute(\n          EditTodoPage.route(\n            initialTodo: Todo(\n              id: 'initial-id',\n              title: 'initial',\n            ),\n          ),\n        );\n        expect(find.byType(EditTodoPage), findsOneWidget);\n        expect(\n          find.byWidgetPredicate(\n            (w) => w is EditableText && w.controller.text == 'initial',\n          ),\n          findsOneWidget,\n        );\n      });\n    });\n\n    testWidgets('renders EditTodoView', (tester) async {\n      await tester.pumpApp(buildSubject());\n\n      expect(find.byType(EditTodoView), findsOneWidget);\n    });\n\n    testWidgets(\n      'pops when a todo was saved successfully',\n      (tester) async {\n        whenListen<EditTodoState>(\n          editTodoBloc,\n          Stream.fromIterable(const [\n            EditTodoState(),\n            EditTodoState(\n              status: EditTodoStatus.success,\n            ),\n          ]),\n        );\n        await tester.pumpApp(buildSubject());\n\n        verify(() => navigator.pop<Object?>(any<dynamic>())).called(1);\n      },\n    );\n  });\n\n  group('EditTodoView', () {\n    const titleTextFormField = Key('editTodoView_title_textFormField');\n    const descriptionTextFormField = Key(\n      'editTodoView_description_textFormField',\n    );\n\n    Widget buildSubject() {\n      return MockNavigatorProvider(\n        navigator: navigator,\n        child: BlocProvider.value(\n          value: editTodoBloc,\n          child: const EditTodoView(),\n        ),\n      );\n    }\n\n    testWidgets(\n      'renders AppBar with title text for new todos '\n      'when a new todo is being created',\n      (tester) async {\n        when(() => editTodoBloc.state).thenReturn(const EditTodoState());\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byType(AppBar), findsOneWidget);\n        expect(\n          find.descendant(\n            of: find.byType(AppBar),\n            matching: find.text(l10n.editTodoAddAppBarTitle),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    testWidgets(\n      'renders AppBar with title text for editing todos '\n      'when an existing todo is being edited',\n      (tester) async {\n        when(() => editTodoBloc.state).thenReturn(\n          EditTodoState(\n            initialTodo: Todo(title: 'title'),\n          ),\n        );\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byType(AppBar), findsOneWidget);\n        expect(\n          find.descendant(\n            of: find.byType(AppBar),\n            matching: find.text(l10n.editTodoEditAppBarTitle),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    group('title text form field', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byKey(titleTextFormField), findsOneWidget);\n      });\n\n      testWidgets('is disabled when loading', (tester) async {\n        when(() => editTodoBloc.state).thenReturn(\n          const EditTodoState(\n            status: EditTodoStatus.loading,\n          ),\n        );\n        await tester.pumpApp(buildSubject());\n\n        final textField = tester.widget<TextFormField>(\n          find.byKey(descriptionTextFormField),\n        );\n        expect(textField.enabled, false);\n      });\n\n      testWidgets(\n        'adds EditTodoTitleChanged '\n        'to EditTodoBloc '\n        'when a new value is entered',\n        (tester) async {\n          await tester.pumpApp(buildSubject());\n          await tester.enterText(\n            find.byKey(titleTextFormField),\n            'newtitle',\n          );\n\n          verify(\n            () => editTodoBloc.add(const EditTodoTitleChanged('newtitle')),\n          ).called(1);\n        },\n      );\n    });\n\n    group('description text form field', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byKey(descriptionTextFormField), findsOneWidget);\n      });\n\n      testWidgets('is disabled when loading', (tester) async {\n        when(() => editTodoBloc.state).thenReturn(\n          const EditTodoState(\n            status: EditTodoStatus.loading,\n          ),\n        );\n        await tester.pumpApp(buildSubject());\n\n        final textField = tester.widget<TextFormField>(\n          find.byKey(titleTextFormField),\n        );\n        expect(textField.enabled, false);\n      });\n\n      testWidgets(\n        'adds EditTodoDescriptionChanged '\n        'to EditTodoBloc '\n        'when a new value is entered',\n        (tester) async {\n          await tester.pumpApp(buildSubject());\n          await tester.enterText(\n            find.byKey(descriptionTextFormField),\n            'newdescription',\n          );\n\n          verify(\n            () => editTodoBloc.add(\n              const EditTodoDescriptionChanged('newdescription'),\n            ),\n          ).called(1);\n        },\n      );\n    });\n\n    group('save fab', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(\n          find.descendant(\n            of: find.byType(FloatingActionButton),\n            matching: find.byTooltip(l10n.editTodoSaveButtonTooltip),\n          ),\n          findsOneWidget,\n        );\n      });\n\n      testWidgets(\n        'adds EditTodoSubmitted '\n        'to EditTodoBloc '\n        'when tapped',\n        (tester) async {\n          await tester.pumpApp(buildSubject());\n          await tester.tap(find.byType(FloatingActionButton));\n\n          verify(() => editTodoBloc.add(const EditTodoSubmitted())).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/helpers/finders.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\n\nextension ExtraFinders on CommonFinders {\n  /// Finds a widget by a specific type [T].\n  ///\n  /// ```dart\n  /// find.bySpecificType<Foo<Bar>>()\n  /// ```\n  Finder bySpecificType<T>() => find.byType(T);\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/helpers/helpers.dart",
    "content": "export 'finders.dart';\nexport 'l10n.dart';\nexport 'pump_app.dart';\n"
  },
  {
    "path": "examples/flutter_todos/test/helpers/l10n.dart",
    "content": "import 'package:flutter_todos/l10n/app_localizations.dart';\nimport 'package:flutter_todos/l10n/app_localizations_en.dart';\n\nAppLocalizations get l10n => AppLocalizationsEn();\n"
  },
  {
    "path": "examples/flutter_todos/test/helpers/pump_app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_localizations/flutter_localizations.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/l10n/l10n.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass MockTodosRepository extends Mock implements TodosRepository {}\n\nextension PumpApp on WidgetTester {\n  Future<void> pumpApp(\n    Widget widget, {\n    TodosRepository? todosRepository,\n  }) {\n    return pumpWidget(\n      RepositoryProvider.value(\n        value: todosRepository ?? MockTodosRepository(),\n        child: MaterialApp(\n          localizationsDelegates: const [\n            AppLocalizations.delegate,\n            GlobalMaterialLocalizations.delegate,\n          ],\n          supportedLocales: AppLocalizations.supportedLocales,\n          home: Scaffold(body: widget),\n        ),\n      ),\n    );\n  }\n\n  Future<void> pumpRoute(\n    Route<dynamic> route, {\n    TodosRepository? todosRepository,\n  }) {\n    return pumpApp(\n      Navigator(onGenerateRoute: (_) => route),\n      todosRepository: todosRepository,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/home/cubit/home_cubit_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/home/home.dart';\n\nvoid main() {\n  group('HomeCubit', () {\n    HomeCubit buildCubit() => HomeCubit();\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(buildCubit, returnsNormally);\n      });\n\n      test('has correct initial state', () {\n        expect(\n          buildCubit().state,\n          equals(const HomeState()),\n        );\n      });\n    });\n\n    group('setTab', () {\n      blocTest<HomeCubit, HomeState>(\n        'sets tab to given value',\n        build: buildCubit,\n        act: (cubit) => cubit.setTab(HomeTab.stats),\n        expect: () => [\n          const HomeState(tab: HomeTab.stats),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/home/view/home_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/home/home.dart';\nimport 'package:flutter_todos/stats/stats.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:mockingjay/mockingjay.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockHomeCubit extends MockCubit<HomeState> implements HomeCubit {}\n\nvoid main() {\n  late TodosRepository todosRepository;\n\n  group('HomePage', () {\n    setUp(() {\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    testWidgets('renders HomeView', (tester) async {\n      await tester.pumpApp(\n        const HomePage(),\n        todosRepository: todosRepository,\n      );\n\n      expect(find.byType(HomeView), findsOneWidget);\n    });\n  });\n\n  group('HomeView', () {\n    const addTodoFloatingActionButtonKey = Key(\n      'homeView_addTodo_floatingActionButton',\n    );\n\n    late MockNavigator navigator;\n    late HomeCubit cubit;\n\n    setUp(() {\n      navigator = MockNavigator();\n      when(() => navigator.canPop()).thenReturn(false);\n      when(() => navigator.push<void>(any())).thenAnswer((_) async {});\n\n      cubit = MockHomeCubit();\n      when(() => cubit.state).thenReturn(const HomeState());\n\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    Widget buildSubject() {\n      return MockNavigatorProvider(\n        navigator: navigator,\n        child: BlocProvider.value(\n          value: cubit,\n          child: const HomeView(),\n        ),\n      );\n    }\n\n    testWidgets(\n      'renders TodosOverviewPage '\n      'when tab is set to HomeTab.todos',\n      (tester) async {\n        when(() => cubit.state).thenReturn(const HomeState());\n\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        expect(find.byType(TodosOverviewPage), findsOneWidget);\n      },\n    );\n\n    testWidgets(\n      'renders StatsPage '\n      'when tab is set to HomeTab.stats',\n      (tester) async {\n        when(() => cubit.state).thenReturn(const HomeState(tab: HomeTab.stats));\n\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        expect(find.byType(StatsPage), findsOneWidget);\n      },\n    );\n\n    testWidgets(\n      'calls setTab with HomeTab.todos on HomeCubit '\n      'when todos navigation button is pressed',\n      (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        await tester.tap(find.byIcon(Icons.list_rounded));\n\n        verify(() => cubit.setTab(HomeTab.todos)).called(1);\n      },\n    );\n\n    testWidgets(\n      'calls setTab with HomeTab.stats on HomeCubit '\n      'when stats navigation button is pressed',\n      (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        await tester.tap(find.byIcon(Icons.show_chart_rounded));\n\n        verify(() => cubit.setTab(HomeTab.stats)).called(1);\n      },\n    );\n\n    group('add todo floating action button', () {\n      testWidgets(\n        'is rendered',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          expect(\n            find.byKey(addTodoFloatingActionButtonKey),\n            findsOneWidget,\n          );\n\n          final addTodoFloatingActionButton = tester.widget(\n            find.byKey(addTodoFloatingActionButtonKey),\n          );\n          expect(\n            addTodoFloatingActionButton,\n            isA<FloatingActionButton>(),\n          );\n        },\n      );\n\n      testWidgets('renders add icon', (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        expect(\n          find.descendant(\n            of: find.byKey(addTodoFloatingActionButtonKey),\n            matching: find.byIcon(Icons.add),\n          ),\n          findsOneWidget,\n        );\n      });\n\n      testWidgets(\n        'navigates to the EditTodoPage when pressed',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          await tester.tap(find.byKey(addTodoFloatingActionButtonKey));\n\n          verify(\n            () => navigator.push<void>(any(that: isRoute<void>())),\n          ).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/stats/bloc/stats_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/stats/stats.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass MockTodosRepository extends Mock implements TodosRepository {}\n\nvoid main() {\n  final todo = Todo(\n    id: '1',\n    title: 'title 1',\n    description: 'description 1',\n  );\n\n  group('StatsBloc', () {\n    late TodosRepository todosRepository;\n\n    setUp(() {\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    StatsBloc buildBloc() => StatsBloc(todosRepository: todosRepository);\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(buildBloc, returnsNormally);\n      });\n\n      test('has correct initial state', () {\n        expect(buildBloc().state, equals(const StatsState()));\n      });\n    });\n\n    group('StatsSubscriptionRequested', () {\n      blocTest<StatsBloc, StatsState>(\n        'starts listening to repository getTodos stream',\n        build: buildBloc,\n        act: (bloc) => bloc.add(const StatsSubscriptionRequested()),\n        verify: (bloc) {\n          verify(() => todosRepository.getTodos()).called(1);\n        },\n      );\n\n      blocTest<StatsBloc, StatsState>(\n        'emits state with updated status, completed todo and active todo count '\n        'when repository getTodos stream emits new todos',\n        setUp: () {\n          when(\n            todosRepository.getTodos,\n          ).thenAnswer((_) => Stream.value([todo]));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(const StatsSubscriptionRequested()),\n        expect: () => [\n          const StatsState(status: StatsStatus.loading),\n          const StatsState(\n            status: StatsStatus.success,\n            activeTodos: 1,\n          ),\n        ],\n      );\n\n      blocTest<StatsBloc, StatsState>(\n        'emits state with failure status '\n        'when repository getTodos stream emits error',\n        setUp: () {\n          when(\n            () => todosRepository.getTodos(),\n          ).thenAnswer((_) => Stream.error(Exception('oops')));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(const StatsSubscriptionRequested()),\n        expect: () => [\n          const StatsState(status: StatsStatus.loading),\n          const StatsState(status: StatsStatus.failure),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/stats/bloc/stats_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/stats/stats.dart';\n\nvoid main() {\n  group('StatsEvent', () {\n    group('StatsSubscriptionRequested', () {\n      test('supports value equality', () {\n        expect(\n          StatsSubscriptionRequested(),\n          equals(StatsSubscriptionRequested()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          StatsSubscriptionRequested().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/stats/bloc/stats_state_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/stats/stats.dart';\n\nvoid main() {\n  group('StatsState', () {\n    StatsState createSubject({\n      StatsStatus status = StatsStatus.initial,\n      int completedTodos = 0,\n      int activeTodos = 0,\n    }) {\n      return StatsState(\n        status: status,\n        completedTodos: completedTodos,\n        activeTodos: activeTodos,\n      );\n    }\n\n    test('supports value equality', () {\n      expect(\n        createSubject(),\n        equals(createSubject()),\n      );\n    });\n\n    test('props are correct', () {\n      expect(\n        createSubject(\n          status: StatsStatus.initial,\n          completedTodos: 1,\n          activeTodos: 2,\n        ).props,\n        equals(<Object?>[\n          StatsStatus.initial, // status\n          1, // completedTodos\n          2, // activeTodos\n        ]),\n      );\n    });\n\n    group('copyWith', () {\n      test('returns the same object if not arguments are provided', () {\n        expect(\n          createSubject().copyWith(),\n          equals(createSubject()),\n        );\n      });\n\n      test('retains the old value for every parameter if null is provided', () {\n        expect(\n          createSubject().copyWith(\n            status: null,\n            completedTodos: null,\n            activeTodos: null,\n          ),\n          equals(createSubject()),\n        );\n      });\n\n      test('replaces every non-null parameter', () {\n        expect(\n          createSubject().copyWith(\n            status: StatsStatus.success,\n            completedTodos: 1,\n            activeTodos: 2,\n          ),\n          equals(\n            createSubject(\n              status: StatsStatus.success,\n              completedTodos: 1,\n              activeTodos: 2,\n            ),\n          ),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/stats/view/stats_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/stats/stats.dart';\nimport 'package:mockingjay/mockingjay.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockStatsBloc extends MockBloc<StatsEvent, StatsState>\n    implements StatsBloc {}\n\nvoid main() {\n  group('StatsPage', () {\n    late TodosRepository todosRepository;\n\n    setUp(() {\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    testWidgets('renders StatsView', (tester) async {\n      await tester.pumpApp(\n        const StatsPage(),\n        todosRepository: todosRepository,\n      );\n\n      expect(find.byType(StatsView), findsOneWidget);\n    });\n\n    testWidgets(\n      'subscribes to todos from repository on initialization',\n      (tester) async {\n        await tester.pumpApp(\n          const StatsPage(),\n          todosRepository: todosRepository,\n        );\n\n        verify(() => todosRepository.getTodos()).called(1);\n      },\n    );\n  });\n\n  group('StatsView', () {\n    const completedTodosListTileKey = Key('statsView_completedTodos_listTile');\n    const activeTodosListTileKey = Key('statsView_activeTodos_listTile');\n\n    late MockNavigator navigator;\n    late StatsBloc statsBloc;\n\n    setUp(() {\n      navigator = MockNavigator();\n      when(() => navigator.push(any())).thenAnswer((_) async => null);\n      when(() => navigator.canPop()).thenReturn(false);\n\n      statsBloc = MockStatsBloc();\n      when(() => statsBloc.state).thenReturn(\n        const StatsState(status: StatsStatus.success),\n      );\n    });\n\n    Widget buildSubject() {\n      return MockNavigatorProvider(\n        navigator: navigator,\n        child: BlocProvider.value(\n          value: statsBloc,\n          child: const StatsView(),\n        ),\n      );\n    }\n\n    testWidgets(\n      'renders AppBar with title text',\n      (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byType(AppBar), findsOneWidget);\n        expect(\n          find.descendant(\n            of: find.byType(AppBar),\n            matching: find.text(l10n.statsAppBarTitle),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    testWidgets(\n      'renders completed todos ListTile '\n      'with correct icon, label and value',\n      (tester) async {\n        const completedTodos = 42;\n        when(() => statsBloc.state).thenReturn(\n          const StatsState(\n            status: StatsStatus.success,\n            completedTodos: completedTodos,\n          ),\n        );\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byKey(completedTodosListTileKey), findsOneWidget);\n\n        expect(\n          find.descendant(\n            of: find.byKey(completedTodosListTileKey),\n            matching: find.text(l10n.statsCompletedTodoCountLabel),\n          ),\n          findsOneWidget,\n        );\n\n        expect(\n          find.descendant(\n            of: find.byKey(completedTodosListTileKey),\n            matching: find.byIcon(Icons.check_rounded),\n          ),\n          findsOneWidget,\n        );\n\n        expect(\n          find.descendant(\n            of: find.byKey(completedTodosListTileKey),\n            matching: find.text('$completedTodos'),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    testWidgets(\n      'renders active todos ListTile '\n      'with correct icon, label and value',\n      (tester) async {\n        const activeTodos = 42;\n        when(() => statsBloc.state).thenReturn(\n          const StatsState(\n            status: StatsStatus.success,\n            activeTodos: activeTodos,\n          ),\n        );\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byKey(activeTodosListTileKey), findsOneWidget);\n\n        expect(\n          find.descendant(\n            of: find.byKey(activeTodosListTileKey),\n            matching: find.text(l10n.statsActiveTodoCountLabel),\n          ),\n          findsOneWidget,\n        );\n\n        expect(\n          find.descendant(\n            of: find.byKey(activeTodosListTileKey),\n            matching: find.byIcon(Icons.radio_button_unchecked_rounded),\n          ),\n          findsOneWidget,\n        );\n\n        expect(\n          find.descendant(\n            of: find.byKey(activeTodosListTileKey),\n            matching: find.text('$activeTodos'),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/bloc/todos_overview_bloc_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nclass MockTodosRepository extends Mock implements TodosRepository {}\n\nclass FakeTodo extends Fake implements Todo {}\n\nvoid main() {\n  final mockTodos = [\n    Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n    ),\n    Todo(\n      id: '2',\n      title: 'title 2',\n      description: 'description 2',\n    ),\n    Todo(\n      id: '3',\n      title: 'title 3',\n      description: 'description 3',\n      isCompleted: true,\n    ),\n  ];\n\n  group('TodosOverviewBloc', () {\n    late TodosRepository todosRepository;\n\n    setUpAll(() {\n      registerFallbackValue(FakeTodo());\n    });\n\n    setUp(() {\n      todosRepository = MockTodosRepository();\n      when(\n        () => todosRepository.getTodos(),\n      ).thenAnswer((_) => Stream.value(mockTodos));\n      when(() => todosRepository.saveTodo(any())).thenAnswer((_) async {});\n    });\n\n    TodosOverviewBloc buildBloc() {\n      return TodosOverviewBloc(todosRepository: todosRepository);\n    }\n\n    group('constructor', () {\n      test('works properly', () => expect(buildBloc, returnsNormally));\n\n      test('has correct initial state', () {\n        expect(\n          buildBloc().state,\n          equals(const TodosOverviewState()),\n        );\n      });\n    });\n\n    group('TodosOverviewSubscriptionRequested', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'starts listening to repository getTodos stream',\n        build: buildBloc,\n        act: (bloc) => bloc.add(const TodosOverviewSubscriptionRequested()),\n        verify: (_) {\n          verify(() => todosRepository.getTodos()).called(1);\n        },\n      );\n\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'emits state with updated status and todos '\n        'when repository getTodos stream emits new todos',\n        build: buildBloc,\n        act: (bloc) => bloc.add(const TodosOverviewSubscriptionRequested()),\n        expect: () => [\n          const TodosOverviewState(\n            status: TodosOverviewStatus.loading,\n          ),\n          TodosOverviewState(\n            status: TodosOverviewStatus.success,\n            todos: mockTodos,\n          ),\n        ],\n      );\n\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'emits state with failure status '\n        'when repository getTodos stream emits error',\n        setUp: () {\n          when(\n            () => todosRepository.getTodos(),\n          ).thenAnswer((_) => Stream.error(Exception('oops')));\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(const TodosOverviewSubscriptionRequested()),\n        expect: () => [\n          const TodosOverviewState(status: TodosOverviewStatus.loading),\n          const TodosOverviewState(status: TodosOverviewStatus.failure),\n        ],\n      );\n    });\n\n    group('TodosOverviewTodoCompletionToggled', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'saves todo with isCompleted set to event isCompleted flag',\n        build: buildBloc,\n        seed: () => TodosOverviewState(todos: mockTodos),\n        act: (bloc) => bloc.add(\n          TodosOverviewTodoCompletionToggled(\n            todo: mockTodos.first,\n            isCompleted: true,\n          ),\n        ),\n        verify: (_) {\n          verify(\n            () => todosRepository.saveTodo(\n              mockTodos.first.copyWith(isCompleted: true),\n            ),\n          ).called(1);\n        },\n      );\n    });\n\n    group('TodosOverviewTodoDeleted', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'deletes todo using repository',\n        setUp: () {\n          when(\n            () => todosRepository.deleteTodo(any()),\n          ).thenAnswer((_) async {});\n        },\n        build: buildBloc,\n        seed: () => TodosOverviewState(todos: mockTodos),\n        act: (bloc) => bloc.add(TodosOverviewTodoDeleted(mockTodos.first)),\n        verify: (_) {\n          verify(\n            () => todosRepository.deleteTodo(mockTodos.first.id),\n          ).called(1);\n        },\n      );\n    });\n\n    group('TodosOverviewUndoDeletionRequested', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'restores last deleted undo and clears lastDeletedUndo field',\n        build: buildBloc,\n        seed: () => TodosOverviewState(lastDeletedTodo: mockTodos.first),\n        act: (bloc) => bloc.add(const TodosOverviewUndoDeletionRequested()),\n        expect: () => const [TodosOverviewState()],\n        verify: (_) {\n          verify(() => todosRepository.saveTodo(mockTodos.first)).called(1);\n        },\n      );\n    });\n\n    group('TodosOverviewFilterChanged', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'emits state with updated filter',\n        build: buildBloc,\n        act: (bloc) => bloc.add(\n          const TodosOverviewFilterChanged(TodosViewFilter.completedOnly),\n        ),\n        expect: () => const [\n          TodosOverviewState(filter: TodosViewFilter.completedOnly),\n        ],\n      );\n    });\n\n    group('TodosOverviewToggleAllRequested', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'toggles all todos to completed when some or none are uncompleted',\n        setUp: () {\n          when(\n            () => todosRepository.completeAll(\n              isCompleted: any(named: 'isCompleted'),\n            ),\n          ).thenAnswer((_) async => 0);\n        },\n        build: buildBloc,\n        seed: () => TodosOverviewState(todos: mockTodos),\n        act: (bloc) => bloc.add(const TodosOverviewToggleAllRequested()),\n        verify: (_) {\n          verify(\n            () => todosRepository.completeAll(isCompleted: true),\n          ).called(1);\n        },\n      );\n\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'toggles all todos to uncompleted when all are completed',\n        setUp: () {\n          when(\n            () => todosRepository.completeAll(\n              isCompleted: any(named: 'isCompleted'),\n            ),\n          ).thenAnswer((_) async => 0);\n        },\n        build: buildBloc,\n        seed: () => TodosOverviewState(\n          todos: mockTodos\n              .map((todo) => todo.copyWith(isCompleted: true))\n              .toList(),\n        ),\n        act: (bloc) => bloc.add(const TodosOverviewToggleAllRequested()),\n        verify: (_) {\n          verify(\n            () => todosRepository.completeAll(isCompleted: false),\n          ).called(1);\n        },\n      );\n    });\n\n    group('TodosOverviewClearCompletedRequested', () {\n      blocTest<TodosOverviewBloc, TodosOverviewState>(\n        'clears completed todos using repository',\n        setUp: () {\n          when(\n            () => todosRepository.clearCompleted(),\n          ).thenAnswer((_) async => 0);\n        },\n        build: buildBloc,\n        act: (bloc) => bloc.add(const TodosOverviewClearCompletedRequested()),\n        verify: (_) {\n          verify(() => todosRepository.clearCompleted()).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/bloc/todos_overview_event_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nvoid main() {\n  group('TodosOverviewEvent', () {\n    final mockTodo = Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n    );\n\n    group('TodosOverviewSubscriptionRequested', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewSubscriptionRequested(),\n          equals(TodosOverviewSubscriptionRequested()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewSubscriptionRequested().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n\n    group('TodosOverviewTodoCompletionToggled', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewTodoCompletionToggled(\n            todo: mockTodo,\n            isCompleted: true,\n          ),\n          equals(\n            TodosOverviewTodoCompletionToggled(\n              todo: mockTodo,\n              isCompleted: true,\n            ),\n          ),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewTodoCompletionToggled(\n            todo: mockTodo,\n            isCompleted: true,\n          ).props,\n          equals(<Object?>[\n            mockTodo, // `todo`\n            true, // isCompleted\n          ]),\n        );\n      });\n    });\n\n    group('TodosOverviewTodoDeleted', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewTodoDeleted(mockTodo),\n          equals(TodosOverviewTodoDeleted(mockTodo)),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewTodoDeleted(mockTodo).props,\n          equals(<Object?>[\n            mockTodo, // `todo`\n          ]),\n        );\n      });\n    });\n\n    group('TodosOverviewUndoDeletionRequested', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewUndoDeletionRequested(),\n          equals(TodosOverviewUndoDeletionRequested()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewUndoDeletionRequested().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n\n    group('TodosOverviewFilterChanged', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewFilterChanged(TodosViewFilter.all),\n          equals(TodosOverviewFilterChanged(TodosViewFilter.all)),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewFilterChanged(TodosViewFilter.all).props,\n          equals(<Object?>[\n            TodosViewFilter.all, // filter\n          ]),\n        );\n      });\n    });\n\n    group('TodosOverviewToggleAllRequested', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewToggleAllRequested(),\n          equals(TodosOverviewToggleAllRequested()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewToggleAllRequested().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n\n    group('TodosOverviewClearCompletedRequested', () {\n      test('supports value equality', () {\n        expect(\n          TodosOverviewClearCompletedRequested(),\n          equals(TodosOverviewClearCompletedRequested()),\n        );\n      });\n\n      test('props are correct', () {\n        expect(\n          TodosOverviewClearCompletedRequested().props,\n          equals(<Object?>[]),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/bloc/todos_overview_state_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\n\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nvoid main() {\n  final mockTodo = Todo(\n    id: '1',\n    title: 'title 1',\n    description: 'description 1',\n  );\n  final mockTodos = [mockTodo];\n  group('TodosOverviewState', () {\n    TodosOverviewState createSubject({\n      TodosOverviewStatus status = TodosOverviewStatus.initial,\n      List<Todo>? todos,\n      TodosViewFilter filter = TodosViewFilter.all,\n      Todo? lastDeletedTodo,\n    }) {\n      return TodosOverviewState(\n        status: status,\n        todos: todos ?? mockTodos,\n        filter: filter,\n        lastDeletedTodo: lastDeletedTodo,\n      );\n    }\n\n    test('supports value equality', () {\n      expect(\n        createSubject(),\n        equals(createSubject()),\n      );\n    });\n\n    test('props are correct', () {\n      expect(\n        createSubject(\n          status: TodosOverviewStatus.initial,\n          todos: mockTodos,\n          filter: TodosViewFilter.all,\n          lastDeletedTodo: null,\n        ).props,\n        equals(<Object?>[\n          TodosOverviewStatus.initial, // status\n          mockTodos, // todos\n          TodosViewFilter.all, // filter\n          null, // lastDeletedTodo\n        ]),\n      );\n    });\n\n    test('filteredTodos returns todos filtered by filter', () {\n      expect(\n        createSubject(\n          todos: mockTodos,\n          filter: TodosViewFilter.completedOnly,\n        ).filteredTodos,\n        equals(mockTodos.where((todo) => todo.isCompleted).toList()),\n      );\n    });\n\n    group('copyWith', () {\n      test('returns the same object if not arguments are provided', () {\n        expect(\n          createSubject().copyWith(),\n          equals(createSubject()),\n        );\n      });\n\n      test('retains the old value for every parameter if null is provided', () {\n        expect(\n          createSubject().copyWith(\n            status: null,\n            todos: null,\n            filter: null,\n            lastDeletedTodo: null,\n          ),\n          equals(createSubject()),\n        );\n      });\n\n      test('replaces every non-null parameter', () {\n        expect(\n          createSubject().copyWith(\n            status: () => TodosOverviewStatus.success,\n            todos: () => [],\n            filter: () => TodosViewFilter.completedOnly,\n            lastDeletedTodo: () => mockTodo,\n          ),\n          equals(\n            createSubject(\n              status: TodosOverviewStatus.success,\n              todos: [],\n              filter: TodosViewFilter.completedOnly,\n              lastDeletedTodo: mockTodo,\n            ),\n          ),\n        );\n      });\n    });\n\n    test('can copyWith null lastDeletedTodo', () {\n      expect(\n        createSubject(lastDeletedTodo: mockTodo).copyWith(\n          lastDeletedTodo: () => null,\n        ),\n        equals(createSubject(lastDeletedTodo: null)),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/models/todos_view_filter_test.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nvoid main() {\n  group('TodosViewFilter', () {\n    final completedTodo = Todo(\n      id: '0',\n      title: 'completed',\n      isCompleted: true,\n    );\n\n    final incompleteTodo = Todo(\n      id: '1',\n      title: 'incomplete',\n    );\n\n    group('apply', () {\n      test('always returns true when filter is .all', () {\n        expect(\n          TodosViewFilter.all.apply(completedTodo),\n          isTrue,\n        );\n        expect(\n          TodosViewFilter.all.apply(incompleteTodo),\n          isTrue,\n        );\n      });\n\n      test(\n        'returns true when filter is .activeOnly '\n        'and the todo is incomplete',\n        () {\n          expect(\n            TodosViewFilter.activeOnly.apply(completedTodo),\n            isFalse,\n          );\n          expect(\n            TodosViewFilter.activeOnly.apply(incompleteTodo),\n            isTrue,\n          );\n        },\n      );\n\n      test('returns true when filter is .completedOnly '\n          'and the todo is completed', () {\n        expect(\n          TodosViewFilter.completedOnly.apply(incompleteTodo),\n          isFalse,\n        );\n        expect(\n          TodosViewFilter.completedOnly.apply(completedTodo),\n          isTrue,\n        );\n      });\n    });\n\n    group('applyAll', () {\n      test('correctly filters provided iterable based on selected filter', () {\n        final allTodos = [completedTodo, incompleteTodo];\n\n        expect(\n          TodosViewFilter.all.applyAll(allTodos),\n          equals(allTodos),\n        );\n        expect(\n          TodosViewFilter.activeOnly.applyAll(allTodos),\n          equals([incompleteTodo]),\n        );\n        expect(\n          TodosViewFilter.completedOnly.applyAll(allTodos),\n          equals([completedTodo]),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/view/todos_overview_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:mockingjay/mockingjay.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockTodosRepository extends Mock implements TodosRepository {}\n\nclass MockTodosOverviewBloc\n    extends MockBloc<TodosOverviewEvent, TodosOverviewState>\n    implements TodosOverviewBloc {}\n\nvoid main() {\n  final mockTodos = [\n    Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n    ),\n    Todo(\n      id: '2',\n      title: 'title 2',\n      description: 'description 2',\n    ),\n    Todo(\n      id: '3',\n      title: 'title 3',\n      description: 'description 3',\n      isCompleted: true,\n    ),\n  ];\n\n  late TodosRepository todosRepository;\n\n  group('TodosOverviewPage', () {\n    setUp(() {\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    testWidgets('renders TodosOverviewView', (tester) async {\n      await tester.pumpApp(\n        const TodosOverviewPage(),\n        todosRepository: todosRepository,\n      );\n\n      expect(find.byType(TodosOverviewView), findsOneWidget);\n    });\n\n    testWidgets(\n      'subscribes to todos from repository on initialization',\n      (tester) async {\n        await tester.pumpApp(\n          const TodosOverviewPage(),\n          todosRepository: todosRepository,\n        );\n\n        verify(() => todosRepository.getTodos()).called(1);\n      },\n    );\n  });\n\n  group('TodosOverviewView', () {\n    late MockNavigator navigator;\n    late TodosOverviewBloc todosOverviewBloc;\n\n    setUp(() {\n      navigator = MockNavigator();\n      when(() => navigator.canPop()).thenReturn(false);\n      when(() => navigator.push<void>(any())).thenAnswer((_) async {});\n\n      todosOverviewBloc = MockTodosOverviewBloc();\n      when(() => todosOverviewBloc.state).thenReturn(\n        TodosOverviewState(\n          status: TodosOverviewStatus.success,\n          todos: mockTodos,\n        ),\n      );\n\n      todosRepository = MockTodosRepository();\n      when(todosRepository.getTodos).thenAnswer((_) => const Stream.empty());\n    });\n\n    Widget buildSubject() {\n      return MockNavigatorProvider(\n        navigator: navigator,\n        child: BlocProvider.value(\n          value: todosOverviewBloc,\n          child: const TodosOverviewView(),\n        ),\n      );\n    }\n\n    testWidgets(\n      'renders AppBar with title text',\n      (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        expect(find.byType(AppBar), findsOneWidget);\n        expect(\n          find.descendant(\n            of: find.byType(AppBar),\n            matching: find.text(l10n.todosOverviewAppBarTitle),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    testWidgets(\n      'renders error snackbar '\n      'when status changes to failure',\n      (tester) async {\n        whenListen<TodosOverviewState>(\n          todosOverviewBloc,\n          Stream.fromIterable([\n            const TodosOverviewState(),\n            const TodosOverviewState(\n              status: TodosOverviewStatus.failure,\n            ),\n          ]),\n        );\n\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n        await tester.pumpAndSettle();\n\n        expect(find.byType(SnackBar), findsOneWidget);\n        expect(\n          find.descendant(\n            of: find.byType(SnackBar),\n            matching: find.text(l10n.todosOverviewErrorSnackbarText),\n          ),\n          findsOneWidget,\n        );\n      },\n    );\n\n    group('TodoDeletionConfirmationSnackBar', () {\n      setUp(() {\n        when(() => todosOverviewBloc.state).thenReturn(\n          TodosOverviewState(\n            lastDeletedTodo: mockTodos.first,\n          ),\n        );\n        whenListen<TodosOverviewState>(\n          todosOverviewBloc,\n          Stream.fromIterable([\n            const TodosOverviewState(),\n            TodosOverviewState(\n              lastDeletedTodo: mockTodos.first,\n            ),\n          ]),\n        );\n      });\n\n      testWidgets('is rendered when lastDeletedTodo changes', (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n        await tester.pumpAndSettle();\n\n        expect(find.byType(SnackBar), findsOneWidget);\n\n        final snackBar = tester.widget<SnackBar>(find.byType(SnackBar));\n\n        expect(\n          snackBar.content,\n          isA<Text>().having(\n            (text) => text.data,\n            'text',\n            contains(mockTodos.first.title),\n          ),\n        );\n      });\n\n      testWidgets(\n        'adds TodosOverviewUndoDeletionRequested '\n        'to TodosOverviewBloc '\n        'when onUndo is called',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n          await tester.pumpAndSettle();\n\n          final snackBarAction = tester.widget<SnackBarAction>(\n            find.byType(SnackBarAction),\n          );\n\n          snackBarAction.onPressed();\n\n          verify(\n            () => todosOverviewBloc.add(\n              const TodosOverviewUndoDeletionRequested(),\n            ),\n          ).called(1);\n        },\n      );\n    });\n\n    group('when todos is empty', () {\n      setUp(() {\n        when(\n          () => todosOverviewBloc.state,\n        ).thenReturn(const TodosOverviewState());\n      });\n\n      testWidgets(\n        'renders nothing '\n        'when status is initial or error',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          expect(find.byType(ListView), findsNothing);\n          expect(find.byType(CupertinoActivityIndicator), findsNothing);\n        },\n      );\n\n      testWidgets(\n        'renders loading indicator '\n        'when status is loading',\n        (tester) async {\n          when(() => todosOverviewBloc.state).thenReturn(\n            const TodosOverviewState(status: TodosOverviewStatus.loading),\n          );\n\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          expect(find.byType(CupertinoActivityIndicator), findsOneWidget);\n        },\n      );\n\n      testWidgets(\n        'renders todos empty text '\n        'when status is success',\n        (tester) async {\n          when(() => todosOverviewBloc.state).thenReturn(\n            const TodosOverviewState(\n              status: TodosOverviewStatus.success,\n            ),\n          );\n\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          expect(find.text(l10n.todosOverviewEmptyText), findsOneWidget);\n        },\n      );\n    });\n\n    group('when todos is not empty', () {\n      setUp(() {\n        when(() => todosOverviewBloc.state).thenReturn(\n          TodosOverviewState(\n            status: TodosOverviewStatus.success,\n            todos: mockTodos,\n          ),\n        );\n      });\n\n      testWidgets('renders ListView with TodoListTiles', (tester) async {\n        await tester.pumpApp(\n          buildSubject(),\n          todosRepository: todosRepository,\n        );\n\n        expect(find.byType(ListView), findsOneWidget);\n        expect(find.byType(TodoListTile), findsNWidgets(mockTodos.length));\n      });\n\n      testWidgets(\n        'adds TodosOverviewTodoCompletionToggled '\n        'to TodosOverviewBloc '\n        'when TodoListTile.onToggleCompleted is called',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          final todo = mockTodos.first;\n\n          final todoListTile = tester.widget<TodoListTile>(\n            find.byType(TodoListTile).first,\n          );\n          todoListTile.onToggleCompleted!(!todo.isCompleted);\n\n          verify(\n            () => todosOverviewBloc.add(\n              TodosOverviewTodoCompletionToggled(\n                todo: todo,\n                isCompleted: !todo.isCompleted,\n              ),\n            ),\n          ).called(1);\n        },\n      );\n\n      testWidgets(\n        'adds TodosOverviewTodoDeleted '\n        'to TodosOverviewBloc '\n        'when TodoListTile.onDismissed is called',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          final todo = mockTodos.first;\n\n          final todoListTile = tester.widget<TodoListTile>(\n            find.byType(TodoListTile).first,\n          );\n          todoListTile.onDismissed!(DismissDirection.startToEnd);\n\n          verify(\n            () => todosOverviewBloc.add(TodosOverviewTodoDeleted(todo)),\n          ).called(1);\n        },\n      );\n\n      testWidgets(\n        'navigates to EditTodoPage '\n        'when TodoListTile.onTap is called',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(),\n            todosRepository: todosRepository,\n          );\n\n          final todoListTile = tester.widget<TodoListTile>(\n            find.byType(TodoListTile).first,\n          );\n          todoListTile.onTap!();\n\n          verify(\n            () => navigator.push<void>(any(that: isRoute<void>())),\n          ).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/widgets/todo_list_tile_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nvoid main() {\n  group('TodoListTile', () {\n    final uncompletedTodo = Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n    );\n    final completedTodo = Todo(\n      id: '1',\n      title: 'title 1',\n      description: 'description 1',\n      isCompleted: true,\n    );\n    final onToggleCompletedCalls = <bool>[];\n    final dismissibleKey = Key(\n      'todoListTile_dismissible_${uncompletedTodo.id}',\n    );\n\n    late int onDismissedCallCount;\n    late int onTapCallCount;\n\n    Widget buildSubject({Todo? todo}) {\n      return TodoListTile(\n        todo: todo ?? uncompletedTodo,\n        onToggleCompleted: onToggleCompletedCalls.add,\n        onDismissed: (_) => onDismissedCallCount++,\n        onTap: () => onTapCallCount++,\n      );\n    }\n\n    setUp(() {\n      onDismissedCallCount = 0;\n      onTapCallCount = 0;\n    });\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(\n          () => TodoListTile(todo: uncompletedTodo),\n          returnsNormally,\n        );\n      });\n    });\n\n    group('checkbox', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byType(Checkbox), findsOneWidget);\n      });\n\n      testWidgets('is checked when todo is completed', (tester) async {\n        await tester.pumpApp(\n          buildSubject(todo: completedTodo),\n        );\n\n        final checkbox = tester.widget<Checkbox>(find.byType(Checkbox));\n        expect(checkbox.value, isTrue);\n      });\n\n      testWidgets('is unchecked when todo is not completed', (tester) async {\n        await tester.pumpApp(\n          buildSubject(todo: uncompletedTodo),\n        );\n\n        final checkbox = tester.widget<Checkbox>(find.byType(Checkbox));\n        expect(checkbox.value, isFalse);\n      });\n\n      testWidgets(\n        'calls onToggleCompleted with correct value when tapped',\n        (tester) async {\n          await tester.pumpApp(\n            buildSubject(todo: uncompletedTodo),\n          );\n\n          await tester.tap(find.byType(Checkbox));\n\n          expect(onToggleCompletedCalls, equals([true]));\n        },\n      );\n    });\n\n    group('dismissible', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.byType(Dismissible), findsOneWidget);\n        expect(find.byKey(dismissibleKey), findsOneWidget);\n      });\n\n      testWidgets('calls onDismissed when swiped to the left', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        await tester.fling(\n          find.byKey(dismissibleKey),\n          const Offset(-300, 0),\n          3000,\n        );\n        await tester.pumpAndSettle();\n\n        expect(onDismissedCallCount, equals(1));\n      });\n    });\n\n    testWidgets('calls onTap when pressed', (tester) async {\n      await tester.pumpApp(buildSubject());\n\n      await tester.tap(find.byType(TodoListTile));\n\n      expect(onTapCallCount, equals(1));\n    });\n\n    group('todo title', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.text(uncompletedTodo.title), findsOneWidget);\n      });\n\n      testWidgets('is struckthrough when todo is completed', (tester) async {\n        await tester.pumpApp(\n          buildSubject(todo: completedTodo),\n        );\n\n        final text = tester.widget<Text>(find.text(completedTodo.title));\n        expect(\n          text.style,\n          isA<TextStyle>().having(\n            (s) => s.decoration,\n            'decoration',\n            TextDecoration.lineThrough,\n          ),\n        );\n      });\n    });\n\n    group('todo description', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(find.text(uncompletedTodo.description), findsOneWidget);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/widgets/todos_overview_filter_button_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart';\nimport 'package:mocktail/mocktail.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockTodosOverviewBloc\n    extends MockBloc<TodosOverviewEvent, TodosOverviewState>\n    implements TodosOverviewBloc {}\n\nextension on CommonFinders {\n  Finder filterMenuItem({\n    required TodosViewFilter filter,\n    required String title,\n  }) {\n    return find.descendant(\n      of: find.byWidgetPredicate(\n        (w) => w is PopupMenuItem && w.value == filter,\n      ),\n      matching: find.text(title),\n    );\n  }\n}\n\nextension on WidgetTester {\n  Future<void> openPopup() async {\n    await tap(find.byType(TodosOverviewFilterButton));\n    await pumpAndSettle();\n  }\n}\n\nvoid main() {\n  group('TodosOverviewFilterButton', () {\n    late TodosOverviewBloc todosOverviewBloc;\n\n    setUp(() {\n      todosOverviewBloc = MockTodosOverviewBloc();\n      when(() => todosOverviewBloc.state).thenReturn(\n        const TodosOverviewState(\n          status: TodosOverviewStatus.success,\n          todos: [],\n        ),\n      );\n    });\n\n    Widget buildSubject() {\n      return BlocProvider.value(\n        value: todosOverviewBloc,\n        child: const TodosOverviewFilterButton(),\n      );\n    }\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(\n          () => const TodosOverviewFilterButton(),\n          returnsNormally,\n        );\n      });\n    });\n\n    testWidgets('renders filter list icon', (tester) async {\n      await tester.pumpApp(buildSubject());\n\n      expect(\n        find.byIcon(Icons.filter_list_rounded),\n        findsOneWidget,\n      );\n    });\n\n    group('internal PopupMenuButton', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(\n          find.bySpecificType<PopupMenuButton<TodosViewFilter>>(),\n          findsOneWidget,\n        );\n      });\n\n      testWidgets('has initial value set to active filter', (tester) async {\n        when(() => todosOverviewBloc.state).thenReturn(\n          const TodosOverviewState(\n            filter: TodosViewFilter.completedOnly,\n          ),\n        );\n\n        await tester.pumpApp(buildSubject());\n\n        final popupMenuButton = tester.widget<PopupMenuButton<TodosViewFilter>>(\n          find.bySpecificType<PopupMenuButton<TodosViewFilter>>(),\n        );\n        expect(\n          popupMenuButton.initialValue,\n          equals(TodosViewFilter.completedOnly),\n        );\n      });\n\n      testWidgets(\n        'renders items for each filter type when pressed',\n        (tester) async {\n          await tester.pumpApp(buildSubject());\n          await tester.openPopup();\n\n          expect(\n            find.filterMenuItem(\n              filter: TodosViewFilter.all,\n              title: l10n.todosOverviewFilterAll,\n            ),\n            findsOneWidget,\n          );\n          expect(\n            find.filterMenuItem(\n              filter: TodosViewFilter.activeOnly,\n              title: l10n.todosOverviewFilterActiveOnly,\n            ),\n            findsOneWidget,\n          );\n          expect(\n            find.filterMenuItem(\n              filter: TodosViewFilter.completedOnly,\n              title: l10n.todosOverviewFilterCompletedOnly,\n            ),\n            findsOneWidget,\n          );\n        },\n      );\n\n      testWidgets(\n        'adds TodosOverviewFilterChanged '\n        'to TodosOverviewBloc '\n        'when new filter is pressed',\n        (tester) async {\n          when(() => todosOverviewBloc.state).thenReturn(\n            const TodosOverviewState(\n              filter: TodosViewFilter.all,\n            ),\n          );\n\n          await tester.pumpApp(buildSubject());\n          await tester.openPopup();\n\n          await tester.tap(find.text(l10n.todosOverviewFilterCompletedOnly));\n          await tester.pumpAndSettle();\n\n          verify(\n            () => todosOverviewBloc.add(\n              const TodosOverviewFilterChanged(TodosViewFilter.completedOnly),\n            ),\n          ).called(1);\n        },\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/test/todos_overview/widgets/todos_overview_options_button_test.dart",
    "content": "// ignore_for_file: avoid_redundant_argument_values\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_todos/todos_overview/todos_overview.dart'\n    hide TodosViewFilter;\nimport 'package:mocktail/mocktail.dart';\nimport 'package:todos_repository/todos_repository.dart';\n\nimport '../../helpers/helpers.dart';\n\nclass MockTodosOverviewBloc\n    extends MockBloc<TodosOverviewEvent, TodosOverviewState>\n    implements TodosOverviewBloc {}\n\nextension on CommonFinders {\n  Finder optionMenuItem({\n    required String title,\n    bool enabled = true,\n  }) {\n    return find.descendant(\n      of: find.byWidgetPredicate(\n        (w) => w is PopupMenuItem && w.enabled == enabled,\n      ),\n      matching: find.text(title),\n    );\n  }\n}\n\nextension on WidgetTester {\n  Future<void> openPopup() async {\n    await tap(find.byType(TodosOverviewOptionsButton));\n    await pumpAndSettle();\n  }\n}\n\nvoid main() {\n  group('TodosOverviewOptionsButton', () {\n    late TodosOverviewBloc todosOverviewBloc;\n\n    setUp(() {\n      todosOverviewBloc = MockTodosOverviewBloc();\n      when(() => todosOverviewBloc.state).thenReturn(\n        const TodosOverviewState(\n          status: TodosOverviewStatus.success,\n          todos: [],\n        ),\n      );\n    });\n\n    Widget buildSubject() {\n      return BlocProvider.value(\n        value: todosOverviewBloc,\n        child: const TodosOverviewOptionsButton(),\n      );\n    }\n\n    group('constructor', () {\n      test('works properly', () {\n        expect(\n          () => const TodosOverviewOptionsButton(),\n          returnsNormally,\n        );\n      });\n    });\n\n    group('internal PopupMenuButton', () {\n      testWidgets('is rendered', (tester) async {\n        await tester.pumpApp(buildSubject());\n\n        expect(\n          find.bySpecificType<PopupMenuButton<TodosOverviewOption>>(),\n          findsOneWidget,\n        );\n        expect(\n          find.byTooltip(l10n.todosOverviewOptionsTooltip),\n          findsOneWidget,\n        );\n      });\n\n      group('mark all todos button', () {\n        testWidgets('is disabled when there are no todos', (tester) async {\n          when(\n            () => todosOverviewBloc.state,\n          ).thenReturn(const TodosOverviewState(todos: []));\n          await tester.pumpApp(buildSubject());\n          await tester.openPopup();\n\n          expect(\n            find.optionMenuItem(\n              title: l10n.todosOverviewOptionsMarkAllIncomplete,\n              enabled: false,\n            ),\n            findsOneWidget,\n          );\n        });\n\n        testWidgets(\n          'renders mark all complete button '\n          'when not all todos are marked completed',\n          (tester) async {\n            when(() => todosOverviewBloc.state).thenReturn(\n              TodosOverviewState(\n                todos: [\n                  Todo(title: 'a', isCompleted: true),\n                  Todo(title: 'b', isCompleted: false),\n                ],\n              ),\n            );\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            expect(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsMarkAllComplete,\n              ),\n              findsOneWidget,\n            );\n          },\n        );\n\n        testWidgets(\n          'renders mark all incomplete button '\n          'when all todos are marked completed',\n          (tester) async {\n            when(() => todosOverviewBloc.state).thenReturn(\n              TodosOverviewState(\n                todos: [\n                  Todo(title: 'a', isCompleted: true),\n                  Todo(title: 'b', isCompleted: true),\n                ],\n              ),\n            );\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            expect(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsMarkAllIncomplete,\n              ),\n              findsOneWidget,\n            );\n          },\n        );\n\n        testWidgets(\n          'adds TodosOverviewToggleAllRequested '\n          'to TodosOverviewBloc '\n          'when tapped',\n          (tester) async {\n            when(() => todosOverviewBloc.state).thenReturn(\n              TodosOverviewState(\n                todos: [\n                  Todo(title: 'a', isCompleted: true),\n                  Todo(title: 'b', isCompleted: false),\n                ],\n              ),\n            );\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            await tester.tap(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsMarkAllComplete,\n              ),\n            );\n\n            verify(\n              () => todosOverviewBloc.add(\n                const TodosOverviewToggleAllRequested(),\n              ),\n            ).called(1);\n          },\n        );\n      });\n\n      group('clear completed button', () {\n        testWidgets(\n          'is disabled when there are no completed todos',\n          (tester) async {\n            when(\n              () => todosOverviewBloc.state,\n            ).thenReturn(const TodosOverviewState(todos: []));\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            expect(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsClearCompleted,\n                enabled: false,\n              ),\n              findsOneWidget,\n            );\n          },\n        );\n\n        testWidgets(\n          'renders clear completed button '\n          'when there are completed todos',\n          (tester) async {\n            when(() => todosOverviewBloc.state).thenReturn(\n              TodosOverviewState(\n                todos: [\n                  Todo(title: 'a', isCompleted: true),\n                  Todo(title: 'b', isCompleted: false),\n                ],\n              ),\n            );\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            expect(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsClearCompleted,\n                enabled: true,\n              ),\n              findsOneWidget,\n            );\n          },\n        );\n\n        testWidgets(\n          'adds TodosOverviewClearCompletedRequested '\n          'to TodosOverviewBloc '\n          'when tapped',\n          (tester) async {\n            when(() => todosOverviewBloc.state).thenReturn(\n              TodosOverviewState(\n                todos: [\n                  Todo(title: 'a', isCompleted: true),\n                  Todo(title: 'b', isCompleted: false),\n                ],\n              ),\n            );\n            await tester.pumpApp(buildSubject());\n            await tester.openPopup();\n\n            await tester.tap(\n              find.optionMenuItem(\n                title: l10n.todosOverviewOptionsClearCompleted,\n              ),\n            );\n\n            verify(\n              () => todosOverviewBloc.add(\n                const TodosOverviewClearCompletedRequested(),\n              ),\n            ).called(1);\n          },\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_todos/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_todos\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_todos</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_todos/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_todos\",\n    \"short_name\": \"flutter_todos\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_weather/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_weather/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_weather/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_weather\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_weather/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nanalyzer:\n  exclude:\n    - lib/**/*.g.dart\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_weather/build.yaml",
    "content": "targets:\n  $default:\n    builders:\n      json_serializable:\n        options:\n          field_rename: snake\n          checked: true\n          explicit_to_json: true\n"
  },
  {
    "path": "examples/flutter_weather/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_weather/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '13.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "examples/flutter_weather/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:google_fonts/google_fonts.dart';\nimport 'package:weather_repository/weather_repository.dart'\n    show WeatherRepository;\n\nclass WeatherApp extends StatelessWidget {\n  const WeatherApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider(\n      create: (_) => WeatherRepository(),\n      dispose: (repository) => repository.dispose(),\n      child: BlocProvider(\n        create: (context) => WeatherCubit(context.read<WeatherRepository>()),\n        child: const WeatherAppView(),\n      ),\n    );\n  }\n}\n\nclass WeatherAppView extends StatelessWidget {\n  const WeatherAppView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final seedColor = context.select(\n      (WeatherCubit cubit) => cubit.state.weather.toColor,\n    );\n    return MaterialApp(\n      theme: ThemeData(\n        appBarTheme: const AppBarTheme(\n          backgroundColor: Colors.transparent,\n          elevation: 0,\n        ),\n        colorScheme: ColorScheme.fromSeed(seedColor: seedColor),\n        textTheme: GoogleFonts.rajdhaniTextTheme(),\n      ),\n      home: const WeatherPage(),\n    );\n  }\n}\n\nextension on Weather {\n  Color get toColor {\n    switch (condition) {\n      case WeatherCondition.clear:\n        return Colors.yellow;\n      case WeatherCondition.snowy:\n        return Colors.lightBlueAccent;\n      case WeatherCondition.cloudy:\n        return Colors.blueGrey;\n      case WeatherCondition.rainy:\n        return Colors.indigoAccent;\n      case WeatherCondition.unknown:\n        return Colors.cyan;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/main.dart",
    "content": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_weather/app.dart';\nimport 'package:flutter_weather/weather_bloc_observer.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:path_provider/path_provider.dart';\n\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  Bloc.observer = const WeatherBlocObserver();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const WeatherApp());\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/search/search.dart",
    "content": "export 'view/search_page.dart';\n"
  },
  {
    "path": "examples/flutter_weather/lib/search/view/search_page.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass SearchPage extends StatefulWidget {\n  const SearchPage._();\n\n  static Route<String> route() {\n    return MaterialPageRoute(builder: (_) => const SearchPage._());\n  }\n\n  @override\n  State<SearchPage> createState() => _SearchPageState();\n}\n\nclass _SearchPageState extends State<SearchPage> {\n  final TextEditingController _textController = TextEditingController();\n\n  String get _text => _textController.text;\n\n  @override\n  void dispose() {\n    _textController.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('City Search')),\n      body: Row(\n        children: [\n          Expanded(\n            child: Padding(\n              padding: const EdgeInsets.all(8),\n              child: TextField(\n                controller: _textController,\n                decoration: const InputDecoration(\n                  labelText: 'City',\n                  hintText: 'Chicago',\n                ),\n              ),\n            ),\n          ),\n          IconButton(\n            key: const Key('searchPage_search_iconButton'),\n            icon: const Icon(Icons.search, semanticLabel: 'Submit'),\n            onPressed: () => Navigator.of(context).pop(_text),\n          ),\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/settings/settings.dart",
    "content": "export 'view/settings_page.dart';\n"
  },
  {
    "path": "examples/flutter_weather/lib/settings/view/settings_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nclass SettingsPage extends StatelessWidget {\n  const SettingsPage._();\n\n  static Route<void> route() {\n    return MaterialPageRoute<void>(\n      builder: (_) => const SettingsPage._(),\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Settings')),\n      body: ListView(\n        children: <Widget>[\n          BlocBuilder<WeatherCubit, WeatherState>(\n            buildWhen: (previous, current) =>\n                previous.temperatureUnits != current.temperatureUnits,\n            builder: (context, state) {\n              return ListTile(\n                title: const Text('Temperature Units'),\n                isThreeLine: true,\n                subtitle: const Text(\n                  'Use metric measurements for temperature units.',\n                ),\n                trailing: Switch(\n                  value: state.temperatureUnits.isCelsius,\n                  onChanged: (_) => context.read<WeatherCubit>().toggleUnits(),\n                ),\n              );\n            },\n          ),\n        ],\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/cubit/weather_cubit.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:weather_repository/weather_repository.dart'\n    show WeatherRepository;\n\npart 'weather_cubit.g.dart';\npart 'weather_state.dart';\n\nclass WeatherCubit extends HydratedCubit<WeatherState> {\n  WeatherCubit(this._weatherRepository) : super(WeatherState());\n\n  final WeatherRepository _weatherRepository;\n\n  Future<void> fetchWeather(String? city) async {\n    if (city == null || city.isEmpty) return;\n\n    emit(state.copyWith(status: WeatherStatus.loading));\n\n    try {\n      final weather = Weather.fromRepository(\n        await _weatherRepository.getWeather(city),\n      );\n      final units = state.temperatureUnits;\n      final value = units.isFahrenheit\n          ? weather.temperature.value.toFahrenheit()\n          : weather.temperature.value;\n\n      emit(\n        state.copyWith(\n          status: WeatherStatus.success,\n          temperatureUnits: units,\n          weather: weather.copyWith(temperature: Temperature(value: value)),\n        ),\n      );\n    } on Exception {\n      emit(state.copyWith(status: WeatherStatus.failure));\n    }\n  }\n\n  Future<void> refreshWeather() async {\n    if (!state.status.isSuccess) return;\n    if (state.weather == Weather.empty) return;\n    try {\n      final weather = Weather.fromRepository(\n        await _weatherRepository.getWeather(state.weather.location),\n      );\n      final units = state.temperatureUnits;\n      final value = units.isFahrenheit\n          ? weather.temperature.value.toFahrenheit()\n          : weather.temperature.value;\n\n      emit(\n        state.copyWith(\n          status: WeatherStatus.success,\n          temperatureUnits: units,\n          weather: weather.copyWith(temperature: Temperature(value: value)),\n        ),\n      );\n    } on Exception {\n      emit(state);\n    }\n  }\n\n  void toggleUnits() {\n    final units = state.temperatureUnits.isFahrenheit\n        ? TemperatureUnits.celsius\n        : TemperatureUnits.fahrenheit;\n\n    if (!state.status.isSuccess) {\n      emit(state.copyWith(temperatureUnits: units));\n      return;\n    }\n\n    final weather = state.weather;\n    if (weather != Weather.empty) {\n      final temperature = weather.temperature;\n      final value = units.isCelsius\n          ? temperature.value.toCelsius()\n          : temperature.value.toFahrenheit();\n      emit(\n        state.copyWith(\n          temperatureUnits: units,\n          weather: weather.copyWith(temperature: Temperature(value: value)),\n        ),\n      );\n    }\n  }\n\n  @override\n  WeatherState fromJson(Map<String, dynamic> json) =>\n      WeatherState.fromJson(json);\n\n  @override\n  Map<String, dynamic> toJson(WeatherState state) => state.toJson();\n}\n\nextension TemperatureConversion on double {\n  double toFahrenheit() => (this * 9 / 5) + 32;\n  double toCelsius() => (this - 32) * 5 / 9;\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/cubit/weather_cubit.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'weather_cubit.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nWeatherState _$WeatherStateFromJson(Map<String, dynamic> json) =>\n    $checkedCreate(\n      'WeatherState',\n      json,\n      ($checkedConvert) {\n        final val = WeatherState(\n          status: $checkedConvert(\n            'status',\n            (v) =>\n                $enumDecodeNullable(_$WeatherStatusEnumMap, v) ??\n                WeatherStatus.initial,\n          ),\n          temperatureUnits: $checkedConvert(\n            'temperature_units',\n            (v) =>\n                $enumDecodeNullable(_$TemperatureUnitsEnumMap, v) ??\n                TemperatureUnits.celsius,\n          ),\n          weather: $checkedConvert(\n            'weather',\n            (v) =>\n                v == null ? null : Weather.fromJson(v as Map<String, dynamic>),\n          ),\n        );\n        return val;\n      },\n      fieldKeyMap: const {'temperatureUnits': 'temperature_units'},\n    );\n\nMap<String, dynamic> _$WeatherStateToJson(\n  WeatherState instance,\n) => <String, dynamic>{\n  'status': _$WeatherStatusEnumMap[instance.status]!,\n  'weather': instance.weather.toJson(),\n  'temperature_units': _$TemperatureUnitsEnumMap[instance.temperatureUnits]!,\n};\n\nconst _$WeatherStatusEnumMap = {\n  WeatherStatus.initial: 'initial',\n  WeatherStatus.loading: 'loading',\n  WeatherStatus.success: 'success',\n  WeatherStatus.failure: 'failure',\n};\n\nconst _$TemperatureUnitsEnumMap = {\n  TemperatureUnits.fahrenheit: 'fahrenheit',\n  TemperatureUnits.celsius: 'celsius',\n};\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/cubit/weather_state.dart",
    "content": "part of 'weather_cubit.dart';\n\nenum WeatherStatus { initial, loading, success, failure }\n\nextension WeatherStatusX on WeatherStatus {\n  bool get isInitial => this == WeatherStatus.initial;\n  bool get isLoading => this == WeatherStatus.loading;\n  bool get isSuccess => this == WeatherStatus.success;\n  bool get isFailure => this == WeatherStatus.failure;\n}\n\n@JsonSerializable()\nfinal class WeatherState extends Equatable {\n  WeatherState({\n    this.status = WeatherStatus.initial,\n    this.temperatureUnits = TemperatureUnits.celsius,\n    Weather? weather,\n  }) : weather = weather ?? Weather.empty;\n\n  factory WeatherState.fromJson(Map<String, dynamic> json) =>\n      _$WeatherStateFromJson(json);\n\n  final WeatherStatus status;\n  final Weather weather;\n  final TemperatureUnits temperatureUnits;\n\n  WeatherState copyWith({\n    WeatherStatus? status,\n    TemperatureUnits? temperatureUnits,\n    Weather? weather,\n  }) {\n    return WeatherState(\n      status: status ?? this.status,\n      temperatureUnits: temperatureUnits ?? this.temperatureUnits,\n      weather: weather ?? this.weather,\n    );\n  }\n\n  Map<String, dynamic> toJson() => _$WeatherStateToJson(this);\n\n  @override\n  List<Object?> get props => [status, temperatureUnits, weather];\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/models/models.dart",
    "content": "export 'weather.dart';\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/models/weather.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:weather_repository/weather_repository.dart' hide Weather;\nimport 'package:weather_repository/weather_repository.dart'\n    as weather_repository;\n\npart 'weather.g.dart';\n\nenum TemperatureUnits { fahrenheit, celsius }\n\nextension TemperatureUnitsX on TemperatureUnits {\n  bool get isFahrenheit => this == TemperatureUnits.fahrenheit;\n  bool get isCelsius => this == TemperatureUnits.celsius;\n}\n\n@JsonSerializable()\nclass Temperature extends Equatable {\n  const Temperature({required this.value});\n\n  factory Temperature.fromJson(Map<String, dynamic> json) =>\n      _$TemperatureFromJson(json);\n\n  final double value;\n\n  Map<String, dynamic> toJson() => _$TemperatureToJson(this);\n\n  @override\n  List<Object> get props => [value];\n}\n\n@JsonSerializable()\nclass Weather extends Equatable {\n  const Weather({\n    required this.condition,\n    required this.lastUpdated,\n    required this.location,\n    required this.temperature,\n  });\n\n  factory Weather.fromJson(Map<String, dynamic> json) =>\n      _$WeatherFromJson(json);\n\n  factory Weather.fromRepository(weather_repository.Weather weather) {\n    return Weather(\n      condition: weather.condition,\n      lastUpdated: DateTime.now(),\n      location: weather.location,\n      temperature: Temperature(value: weather.temperature),\n    );\n  }\n\n  static final empty = Weather(\n    condition: WeatherCondition.unknown,\n    lastUpdated: DateTime(0),\n    temperature: const Temperature(value: 0),\n    location: '--',\n  );\n\n  final WeatherCondition condition;\n  final DateTime lastUpdated;\n  final String location;\n  final Temperature temperature;\n\n  @override\n  List<Object> get props => [condition, lastUpdated, location, temperature];\n\n  Map<String, dynamic> toJson() => _$WeatherToJson(this);\n\n  Weather copyWith({\n    WeatherCondition? condition,\n    DateTime? lastUpdated,\n    String? location,\n    Temperature? temperature,\n  }) {\n    return Weather(\n      condition: condition ?? this.condition,\n      lastUpdated: lastUpdated ?? this.lastUpdated,\n      location: location ?? this.location,\n      temperature: temperature ?? this.temperature,\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/models/weather.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'weather.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTemperature _$TemperatureFromJson(Map<String, dynamic> json) => $checkedCreate(\n  'Temperature',\n  json,\n  ($checkedConvert) {\n    final val = Temperature(\n      value: $checkedConvert('value', (v) => (v as num).toDouble()),\n    );\n    return val;\n  },\n);\n\nMap<String, dynamic> _$TemperatureToJson(Temperature instance) =>\n    <String, dynamic>{\n      'value': instance.value,\n    };\n\nWeather _$WeatherFromJson(Map<String, dynamic> json) => $checkedCreate(\n  'Weather',\n  json,\n  ($checkedConvert) {\n    final val = Weather(\n      condition: $checkedConvert(\n        'condition',\n        (v) => $enumDecode(_$WeatherConditionEnumMap, v),\n      ),\n      lastUpdated: $checkedConvert(\n        'last_updated',\n        (v) => DateTime.parse(v as String),\n      ),\n      location: $checkedConvert('location', (v) => v as String),\n      temperature: $checkedConvert(\n        'temperature',\n        (v) => Temperature.fromJson(v as Map<String, dynamic>),\n      ),\n    );\n    return val;\n  },\n  fieldKeyMap: const {'lastUpdated': 'last_updated'},\n);\n\nMap<String, dynamic> _$WeatherToJson(Weather instance) => <String, dynamic>{\n  'condition': _$WeatherConditionEnumMap[instance.condition]!,\n  'last_updated': instance.lastUpdated.toIso8601String(),\n  'location': instance.location,\n  'temperature': instance.temperature.toJson(),\n};\n\nconst _$WeatherConditionEnumMap = {\n  WeatherCondition.clear: 'clear',\n  WeatherCondition.rainy: 'rainy',\n  WeatherCondition.cloudy: 'cloudy',\n  WeatherCondition.snowy: 'snowy',\n  WeatherCondition.unknown: 'unknown',\n};\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/view/weather_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_weather/search/search.dart';\nimport 'package:flutter_weather/settings/settings.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nclass WeatherPage extends StatelessWidget {\n  const WeatherPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      extendBodyBehindAppBar: true,\n      appBar: AppBar(\n        actions: [\n          IconButton(\n            icon: const Icon(Icons.settings),\n            onPressed: () => Navigator.of(context).push<void>(\n              SettingsPage.route(),\n            ),\n          ),\n        ],\n      ),\n      body: Center(\n        child: BlocBuilder<WeatherCubit, WeatherState>(\n          builder: (context, state) {\n            return switch (state.status) {\n              WeatherStatus.initial => const WeatherEmpty(),\n              WeatherStatus.loading => const WeatherLoading(),\n              WeatherStatus.failure => const WeatherError(),\n              WeatherStatus.success => WeatherPopulated(\n                weather: state.weather,\n                units: state.temperatureUnits,\n                onRefresh: () {\n                  return context.read<WeatherCubit>().refreshWeather();\n                },\n              ),\n            };\n          },\n        ),\n      ),\n      floatingActionButton: FloatingActionButton(\n        child: const Icon(Icons.search, semanticLabel: 'Search'),\n        onPressed: () async {\n          final city = await Navigator.of(context).push(SearchPage.route());\n          if (!context.mounted) return;\n          await context.read<WeatherCubit>().fetchWeather(city);\n        },\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/weather.dart",
    "content": "export 'package:weather_repository/weather_repository.dart'\n    show WeatherCondition;\nexport 'cubit/weather_cubit.dart';\nexport 'models/models.dart';\nexport 'view/weather_page.dart';\nexport 'widgets/widgets.dart';\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/widgets/weather_empty.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass WeatherEmpty extends StatelessWidget {\n  const WeatherEmpty({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Column(\n      mainAxisSize: MainAxisSize.min,\n      children: [\n        const Text('🏙️', style: TextStyle(fontSize: 64)),\n        Text(\n          'Please Select a City!',\n          style: theme.textTheme.headlineSmall,\n        ),\n      ],\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/widgets/weather_error.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass WeatherError extends StatelessWidget {\n  const WeatherError({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Column(\n      mainAxisSize: MainAxisSize.min,\n      children: [\n        const Text('🙈', style: TextStyle(fontSize: 64)),\n        Text(\n          'Something went wrong!',\n          style: theme.textTheme.headlineSmall,\n        ),\n      ],\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/widgets/weather_loading.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass WeatherLoading extends StatelessWidget {\n  const WeatherLoading({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Column(\n      mainAxisSize: MainAxisSize.min,\n      children: [\n        const Text('⛅', style: TextStyle(fontSize: 64)),\n        Text(\n          'Loading Weather',\n          style: theme.textTheme.headlineSmall,\n        ),\n        const Padding(\n          padding: EdgeInsets.all(16),\n          child: CircularProgressIndicator(),\n        ),\n      ],\n    );\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/widgets/weather_populated.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nclass WeatherPopulated extends StatelessWidget {\n  const WeatherPopulated({\n    required this.weather,\n    required this.units,\n    required this.onRefresh,\n    super.key,\n  });\n\n  final Weather weather;\n  final TemperatureUnits units;\n  final ValueGetter<Future<void>> onRefresh;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Stack(\n      children: [\n        _WeatherBackground(),\n        RefreshIndicator(\n          onRefresh: onRefresh,\n          child: Align(\n            alignment: const Alignment(0, -1 / 3),\n            child: SingleChildScrollView(\n              physics: const AlwaysScrollableScrollPhysics(),\n              clipBehavior: Clip.none,\n              child: Column(\n                mainAxisSize: MainAxisSize.min,\n                children: [\n                  const SizedBox(height: 48),\n                  _WeatherIcon(condition: weather.condition),\n                  Text(\n                    weather.location,\n                    style: theme.textTheme.displayMedium?.copyWith(\n                      fontWeight: FontWeight.w200,\n                    ),\n                  ),\n                  Text(\n                    weather.formattedTemperature(units),\n                    style: theme.textTheme.displaySmall?.copyWith(\n                      fontWeight: FontWeight.bold,\n                    ),\n                  ),\n                  Text(\n                    '''Last Updated at ${TimeOfDay.fromDateTime(weather.lastUpdated).format(context)}''',\n                  ),\n                ],\n              ),\n            ),\n          ),\n        ),\n      ],\n    );\n  }\n}\n\nclass _WeatherIcon extends StatelessWidget {\n  const _WeatherIcon({required this.condition});\n\n  static const _iconSize = 75.0;\n\n  final WeatherCondition condition;\n\n  @override\n  Widget build(BuildContext context) {\n    return Text(\n      condition.toEmoji,\n      style: const TextStyle(fontSize: _iconSize),\n    );\n  }\n}\n\nextension on WeatherCondition {\n  String get toEmoji {\n    switch (this) {\n      case WeatherCondition.clear:\n        return '☀️';\n      case WeatherCondition.rainy:\n        return '🌧️';\n      case WeatherCondition.cloudy:\n        return '☁️';\n      case WeatherCondition.snowy:\n        return '🌨️';\n      case WeatherCondition.unknown:\n        return '❓';\n    }\n  }\n}\n\nclass _WeatherBackground extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final color = Theme.of(context).colorScheme.primaryContainer;\n    return SizedBox.expand(\n      child: DecoratedBox(\n        decoration: BoxDecoration(\n          gradient: LinearGradient(\n            begin: Alignment.topCenter,\n            end: Alignment.bottomCenter,\n            stops: const [0.25, 0.75, 0.90, 1.0],\n            colors: [\n              color,\n              color.brighten(),\n              color.brighten(33),\n              color.brighten(50),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nextension on Color {\n  Color brighten([int percent = 10]) {\n    assert(\n      1 <= percent && percent <= 100,\n      'percentage must be between 1 and 100',\n    );\n    final p = percent / 100;\n    final alpha = a.round();\n    final red = r.round();\n    final green = g.round();\n    final blue = b.round();\n    return Color.fromARGB(\n      alpha,\n      red + ((255 - red) * p).round(),\n      green + ((255 - green) * p).round(),\n      blue + ((255 - blue) * p).round(),\n    );\n  }\n}\n\nextension on Weather {\n  String formattedTemperature(TemperatureUnits units) {\n    return '''${temperature.value.toStringAsPrecision(2)}°${units.isCelsius ? 'C' : 'F'}''';\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather/widgets/widgets.dart",
    "content": "export 'weather_empty.dart';\nexport 'weather_error.dart';\nexport 'weather_loading.dart';\nexport 'weather_populated.dart';\n"
  },
  {
    "path": "examples/flutter_weather/lib/weather_bloc_observer.dart",
    "content": "import 'dart:developer';\n\nimport 'package:bloc/bloc.dart';\n\nclass WeatherBlocObserver extends BlocObserver {\n  const WeatherBlocObserver();\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    super.onEvent(bloc, event);\n    log('onEvent $event');\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    log('onChange $change');\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    log('onTransition $transition');\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    super.onError(bloc, error, stackTrace);\n    log('onError $error');\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/linux/.gitignore",
    "content": "flutter/ephemeral\n"
  },
  {
    "path": "examples/flutter_weather/linux/flutter/generated_plugin_registrant.cc",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#include \"generated_plugin_registrant.h\"\n\n\nvoid fl_register_plugins(FlPluginRegistry* registry) {\n}\n"
  },
  {
    "path": "examples/flutter_weather/linux/flutter/generated_plugin_registrant.h",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#ifndef GENERATED_PLUGIN_REGISTRANT_\n#define GENERATED_PLUGIN_REGISTRANT_\n\n#include <flutter_linux/flutter_linux.h>\n\n// Registers Flutter plugins.\nvoid fl_register_plugins(FlPluginRegistry* registry);\n\n#endif  // GENERATED_PLUGIN_REGISTRANT_\n"
  },
  {
    "path": "examples/flutter_weather/linux/flutter/generated_plugins.cmake",
    "content": "#\n# Generated file, do not edit.\n#\n\nlist(APPEND FLUTTER_PLUGIN_LIST\n)\n\nlist(APPEND FLUTTER_FFI_PLUGIN_LIST\n)\n\nset(PLUGIN_BUNDLED_LIBRARIES)\n\nforeach(plugin ${FLUTTER_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})\n  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})\nendforeach(plugin)\n\nforeach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})\nendforeach(ffi_plugin)\n"
  },
  {
    "path": "examples/flutter_weather/macos/.gitignore",
    "content": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
  },
  {
    "path": "examples/flutter_weather/macos/Flutter/GeneratedPluginRegistrant.swift",
    "content": "//\n//  Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\nimport path_provider_foundation\n\nfunc RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {\n  PathProviderPlugin.register(with: registry.registrar(forPlugin: \"PathProviderPlugin\"))\n}\n"
  },
  {
    "path": "examples/flutter_weather/macos/Podfile",
    "content": "platform :osx, '10.15'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \\\"flutter pub get\\\" is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \\\"flutter pub get\\\"\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_macos_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n\n  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_macos_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\nanalyzer:\n  exclude:\n    - lib/**/*.g.dart\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/build.yaml",
    "content": "targets:\n  $default:\n    builders:\n      source_gen|combining_builder:\n        options:\n          ignore_for_file:\n            - implicit_dynamic_parameter\n      json_serializable:\n        options:\n          field_rename: snake\n          create_to_json: false\n          checked: true\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/open_meteo_api.dart",
    "content": "export 'src/models/models.dart';\nexport 'src/open_meteo_api_client.dart';\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\n\npart 'location.g.dart';\n\n@JsonSerializable()\nclass Location {\n  const Location({\n    required this.id,\n    required this.name,\n    required this.latitude,\n    required this.longitude,\n  });\n\n  factory Location.fromJson(Map<String, dynamic> json) =>\n      _$LocationFromJson(json);\n\n  final int id;\n  final String name;\n  final double latitude;\n  final double longitude;\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/models/location.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n// ignore_for_file: implicit_dynamic_parameter\n\npart of 'location.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nLocation _$LocationFromJson(Map<String, dynamic> json) => $checkedCreate(\n  'Location',\n  json,\n  ($checkedConvert) {\n    final val = Location(\n      id: $checkedConvert('id', (v) => v as int),\n      name: $checkedConvert('name', (v) => v as String),\n      latitude: $checkedConvert('latitude', (v) => (v as num).toDouble()),\n      longitude: $checkedConvert('longitude', (v) => (v as num).toDouble()),\n    );\n    return val;\n  },\n);\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/models/models.dart",
    "content": "export 'location.dart';\nexport 'weather.dart';\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\n\npart 'weather.g.dart';\n\n@JsonSerializable()\nclass Weather {\n  const Weather({required this.temperature, required this.weatherCode});\n\n  factory Weather.fromJson(Map<String, dynamic> json) =>\n      _$WeatherFromJson(json);\n\n  final double temperature;\n  @JsonKey(name: 'weathercode')\n  final double weatherCode;\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/models/weather.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n// ignore_for_file: implicit_dynamic_parameter\n\npart of 'weather.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nWeather _$WeatherFromJson(Map<String, dynamic> json) => $checkedCreate(\n  'Weather',\n  json,\n  ($checkedConvert) {\n    final val = Weather(\n      temperature: $checkedConvert('temperature', (v) => (v as num).toDouble()),\n      weatherCode: $checkedConvert('weathercode', (v) => (v as num).toDouble()),\n    );\n    return val;\n  },\n  fieldKeyMap: const {'weatherCode': 'weathercode'},\n);\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/lib/src/open_meteo_api_client.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\n\nimport 'package:http/http.dart' as http;\nimport 'package:open_meteo_api/open_meteo_api.dart';\n\n/// Exception thrown when locationSearch fails.\nclass LocationRequestFailure implements Exception {}\n\n/// Exception thrown when the provided location is not found.\nclass LocationNotFoundFailure implements Exception {}\n\n/// Exception thrown when getWeather fails.\nclass WeatherRequestFailure implements Exception {}\n\n/// Exception thrown when weather for provided location is not found.\nclass WeatherNotFoundFailure implements Exception {}\n\n/// {@template open_meteo_api_client}\n/// Dart API Client which wraps the [Open Meteo API](https://open-meteo.com).\n/// {@endtemplate}\nclass OpenMeteoApiClient {\n  /// {@macro open_meteo_api_client}\n  OpenMeteoApiClient({http.Client? httpClient})\n    : _httpClient = httpClient ?? http.Client();\n\n  static const _baseUrlWeather = 'api.open-meteo.com';\n  static const _baseUrlGeocoding = 'geocoding-api.open-meteo.com';\n\n  final http.Client _httpClient;\n\n  /// Finds a [Location] `/v1/search/?name=(query)`.\n  Future<Location> locationSearch(String query) async {\n    final locationRequest = Uri.https(\n      _baseUrlGeocoding,\n      '/v1/search',\n      {'name': query, 'count': '1'},\n    );\n\n    final locationResponse = await _httpClient.get(locationRequest);\n\n    if (locationResponse.statusCode != 200) {\n      throw LocationRequestFailure();\n    }\n\n    final locationJson = jsonDecode(locationResponse.body) as Map;\n\n    if (!locationJson.containsKey('results')) throw LocationNotFoundFailure();\n\n    final results = locationJson['results'] as List;\n\n    if (results.isEmpty) throw LocationNotFoundFailure();\n\n    return Location.fromJson(results.first as Map<String, dynamic>);\n  }\n\n  /// Fetches [Weather] for a given [latitude] and [longitude].\n  Future<Weather> getWeather({\n    required double latitude,\n    required double longitude,\n  }) async {\n    final weatherRequest = Uri.https(_baseUrlWeather, 'v1/forecast', {\n      'latitude': '$latitude',\n      'longitude': '$longitude',\n      'current_weather': 'true',\n    });\n\n    final weatherResponse = await _httpClient.get(weatherRequest);\n\n    if (weatherResponse.statusCode != 200) {\n      throw WeatherRequestFailure();\n    }\n\n    final bodyJson = jsonDecode(weatherResponse.body) as Map<String, dynamic>;\n\n    if (!bodyJson.containsKey('current_weather')) {\n      throw WeatherNotFoundFailure();\n    }\n\n    final weatherJson = bodyJson['current_weather'] as Map<String, dynamic>;\n\n    return Weather.fromJson(weatherJson);\n  }\n\n  /// Closes the underlying http client.\n  void close() {\n    _httpClient.close();\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/pubspec.yaml",
    "content": "name: open_meteo_api\ndescription: A Dart API Client for the Open-Meteo API.\nversion: 1.0.0+1\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  http: ^1.0.0\n  json_annotation: ^4.6.0\n\ndev_dependencies:\n  build_runner: ^2.0.0\n  json_serializable: ^6.3.1\n  mocktail: ^1.0.0\n  test: ^1.16.4\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/test/location_test.dart",
    "content": "import 'package:open_meteo_api/open_meteo_api.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('Location', () {\n    group('fromJson', () {\n      test('returns correct Location object', () {\n        expect(\n          Location.fromJson(\n            <String, dynamic>{\n              'id': 4887398,\n              'name': 'Chicago',\n              'latitude': 41.85003,\n              'longitude': -87.65005,\n            },\n          ),\n          isA<Location>()\n              .having((w) => w.id, 'id', 4887398)\n              .having((w) => w.name, 'name', 'Chicago')\n              .having((w) => w.latitude, 'latitude', 41.85003)\n              .having((w) => w.longitude, 'longitude', -87.65005),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/test/open_meteo_api_client_test.dart",
    "content": "import 'package:http/http.dart' as http;\nimport 'package:mocktail/mocktail.dart';\nimport 'package:open_meteo_api/open_meteo_api.dart';\nimport 'package:test/test.dart';\n\nclass MockHttpClient extends Mock implements http.Client {}\n\nclass MockResponse extends Mock implements http.Response {}\n\nclass FakeUri extends Fake implements Uri {}\n\nvoid main() {\n  group('OpenMeteoApiClient', () {\n    late http.Client httpClient;\n    late OpenMeteoApiClient apiClient;\n\n    setUpAll(() {\n      registerFallbackValue(FakeUri());\n    });\n\n    setUp(() {\n      httpClient = MockHttpClient();\n      apiClient = OpenMeteoApiClient(httpClient: httpClient);\n    });\n\n    group('constructor', () {\n      test('does not require an httpClient', () {\n        expect(OpenMeteoApiClient(), isNotNull);\n      });\n    });\n\n    group('locationSearch', () {\n      const query = 'mock-query';\n      test('makes correct http request', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn('{}');\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        try {\n          await apiClient.locationSearch(query);\n        } catch (_) {}\n        verify(\n          () => httpClient.get(\n            Uri.https(\n              'geocoding-api.open-meteo.com',\n              '/v1/search',\n              {'name': query, 'count': '1'},\n            ),\n          ),\n        ).called(1);\n      });\n\n      test('throws LocationRequestFailure on non-200 response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(400);\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        expect(\n          () async => apiClient.locationSearch(query),\n          throwsA(isA<LocationRequestFailure>()),\n        );\n      });\n\n      test('throws LocationNotFoundFailure on error response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn('{}');\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        await expectLater(\n          apiClient.locationSearch(query),\n          throwsA(isA<LocationNotFoundFailure>()),\n        );\n      });\n\n      test('throws LocationNotFoundFailure on empty response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn('{\"results\": []}');\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        await expectLater(\n          apiClient.locationSearch(query),\n          throwsA(isA<LocationNotFoundFailure>()),\n        );\n      });\n\n      test('returns Location on valid response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn(\n          '''\n{\n  \"results\": [\n    {\n      \"id\": 4887398,\n      \"name\": \"Chicago\",\n      \"latitude\": 41.85003,\n      \"longitude\": -87.65005\n    }\n  ]\n}''',\n        );\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        final actual = await apiClient.locationSearch(query);\n        expect(\n          actual,\n          isA<Location>()\n              .having((l) => l.name, 'name', 'Chicago')\n              .having((l) => l.id, 'id', 4887398)\n              .having((l) => l.latitude, 'latitude', 41.85003)\n              .having((l) => l.longitude, 'longitude', -87.65005),\n        );\n      });\n    });\n\n    group('getWeather', () {\n      const latitude = 41.85003;\n      const longitude = -87.6500;\n\n      test('makes correct http request', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn('{}');\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        try {\n          await apiClient.getWeather(latitude: latitude, longitude: longitude);\n        } catch (_) {}\n        verify(\n          () => httpClient.get(\n            Uri.https('api.open-meteo.com', 'v1/forecast', {\n              'latitude': '$latitude',\n              'longitude': '$longitude',\n              'current_weather': 'true',\n            }),\n          ),\n        ).called(1);\n      });\n\n      test('throws WeatherRequestFailure on non-200 response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(400);\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        expect(\n          () async => apiClient.getWeather(\n            latitude: latitude,\n            longitude: longitude,\n          ),\n          throwsA(isA<WeatherRequestFailure>()),\n        );\n      });\n\n      test('throws WeatherNotFoundFailure on empty response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn('{}');\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        expect(\n          () async => apiClient.getWeather(\n            latitude: latitude,\n            longitude: longitude,\n          ),\n          throwsA(isA<WeatherNotFoundFailure>()),\n        );\n      });\n\n      test('returns weather on valid response', () async {\n        final response = MockResponse();\n        when(() => response.statusCode).thenReturn(200);\n        when(() => response.body).thenReturn(\n          '''\n{\n\"latitude\": 43,\n\"longitude\": -87.875,\n\"generationtime_ms\": 0.2510547637939453,\n\"utc_offset_seconds\": 0,\n\"timezone\": \"GMT\",\n\"timezone_abbreviation\": \"GMT\",\n\"elevation\": 189,\n\"current_weather\": {\n\"temperature\": 15.3,\n\"windspeed\": 25.8,\n\"winddirection\": 310,\n\"weathercode\": 63,\n\"time\": \"2022-09-12T01:00\"\n}\n}\n        ''',\n        );\n        when(() => httpClient.get(any())).thenAnswer((_) async => response);\n        final actual = await apiClient.getWeather(\n          latitude: latitude,\n          longitude: longitude,\n        );\n        expect(\n          actual,\n          isA<Weather>()\n              .having((w) => w.temperature, 'temperature', 15.3)\n              .having((w) => w.weatherCode, 'weatherCode', 63.0),\n        );\n      });\n    });\n\n    group('close', () {\n      test('closes the underlying http client', () {\n        apiClient.close();\n        verify(httpClient.close).called(1);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/open_meteo_api/test/weather_test.dart",
    "content": "import 'package:open_meteo_api/open_meteo_api.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('Weather', () {\n    group('fromJson', () {\n      test('returns correct Weather object', () {\n        expect(\n          Weather.fromJson(\n            <String, dynamic>{'temperature': 15.3, 'weathercode': 63},\n          ),\n          isA<Weather>()\n              .having((w) => w.temperature, 'temperature', 15.3)\n              .having((w) => w.weatherCode, 'weatherCode', 63),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/analysis_options.yaml",
    "content": "include: ../../../../analysis_options.yaml\nanalyzer:\n  exclude:\n    - lib/**/*.g.dart\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/build.yaml",
    "content": "targets:\n  $default:\n    builders:\n      json_serializable:\n        options:\n          field_rename: snake\n          checked: true\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/lib/src/models/models.dart",
    "content": "export 'weather.dart';\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/lib/src/models/weather.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'weather.g.dart';\n\nenum WeatherCondition {\n  clear,\n  rainy,\n  cloudy,\n  snowy,\n  unknown,\n}\n\n@JsonSerializable()\nclass Weather extends Equatable {\n  const Weather({\n    required this.location,\n    required this.temperature,\n    required this.condition,\n  });\n\n  factory Weather.fromJson(Map<String, dynamic> json) =>\n      _$WeatherFromJson(json);\n\n  Map<String, dynamic> toJson() => _$WeatherToJson(this);\n\n  final String location;\n  final double temperature;\n  final WeatherCondition condition;\n\n  @override\n  List<Object> get props => [location, temperature, condition];\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/lib/src/models/weather.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'weather.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nWeather _$WeatherFromJson(Map<String, dynamic> json) => $checkedCreate(\n  'Weather',\n  json,\n  ($checkedConvert) {\n    final val = Weather(\n      location: $checkedConvert('location', (v) => v as String),\n      temperature: $checkedConvert('temperature', (v) => (v as num).toDouble()),\n      condition: $checkedConvert(\n        'condition',\n        (v) => $enumDecode(_$WeatherConditionEnumMap, v),\n      ),\n    );\n    return val;\n  },\n);\n\nMap<String, dynamic> _$WeatherToJson(Weather instance) => <String, dynamic>{\n  'location': instance.location,\n  'temperature': instance.temperature,\n  'condition': _$WeatherConditionEnumMap[instance.condition]!,\n};\n\nconst _$WeatherConditionEnumMap = {\n  WeatherCondition.clear: 'clear',\n  WeatherCondition.rainy: 'rainy',\n  WeatherCondition.cloudy: 'cloudy',\n  WeatherCondition.snowy: 'snowy',\n  WeatherCondition.unknown: 'unknown',\n};\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/lib/src/weather_repository.dart",
    "content": "import 'dart:async';\n\nimport 'package:open_meteo_api/open_meteo_api.dart' hide Weather;\nimport 'package:weather_repository/weather_repository.dart';\n\nclass WeatherRepository {\n  WeatherRepository({OpenMeteoApiClient? weatherApiClient})\n    : _weatherApiClient = weatherApiClient ?? OpenMeteoApiClient();\n\n  final OpenMeteoApiClient _weatherApiClient;\n\n  Future<Weather> getWeather(String city) async {\n    final location = await _weatherApiClient.locationSearch(city);\n    final weather = await _weatherApiClient.getWeather(\n      latitude: location.latitude,\n      longitude: location.longitude,\n    );\n    return Weather(\n      temperature: weather.temperature,\n      location: location.name,\n      condition: weather.weatherCode.toInt().toCondition,\n    );\n  }\n\n  void dispose() => _weatherApiClient.close();\n}\n\nextension on int {\n  WeatherCondition get toCondition {\n    switch (this) {\n      case 0:\n        return WeatherCondition.clear;\n      case 1:\n      case 2:\n      case 3:\n      case 45:\n      case 48:\n        return WeatherCondition.cloudy;\n      case 51:\n      case 53:\n      case 55:\n      case 56:\n      case 57:\n      case 61:\n      case 63:\n      case 65:\n      case 66:\n      case 67:\n      case 80:\n      case 81:\n      case 82:\n      case 95:\n      case 96:\n      case 99:\n        return WeatherCondition.rainy;\n      case 71:\n      case 73:\n      case 75:\n      case 77:\n      case 85:\n      case 86:\n        return WeatherCondition.snowy;\n      default:\n        return WeatherCondition.unknown;\n    }\n  }\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/lib/weather_repository.dart",
    "content": "export 'src/models/models.dart';\nexport 'src/weather_repository.dart';\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/pubspec.yaml",
    "content": "name: weather_repository\ndescription: A Dart Repository which manages the weather domain.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  equatable: ^2.0.0\n  json_annotation: ^4.6.0\n  open_meteo_api:\n    path: ../open_meteo_api\n\ndev_dependencies:\n  build_runner: ^2.0.0\n  coverage: ^1.0.3\n  json_serializable: ^6.3.1\n  mocktail: ^1.0.0\n  test: ^1.16.4\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/test/src/models/weather_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:test/test.dart';\nimport 'package:weather_repository/weather_repository.dart';\n\nvoid main() {\n  group('Weather', () {\n    test('can be (de)serialized', () {\n      final weather = Weather(\n        condition: WeatherCondition.cloudy,\n        temperature: 10.2,\n        location: 'Chicago',\n      );\n      expect(Weather.fromJson(weather.toJson()), equals(weather));\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/packages/weather_repository/test/weather_repository_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:mocktail/mocktail.dart';\nimport 'package:open_meteo_api/open_meteo_api.dart' as open_meteo_api;\nimport 'package:test/test.dart';\nimport 'package:weather_repository/weather_repository.dart';\n\nclass MockOpenMeteoApiClient extends Mock\n    implements open_meteo_api.OpenMeteoApiClient {}\n\nclass MockLocation extends Mock implements open_meteo_api.Location {}\n\nclass MockWeather extends Mock implements open_meteo_api.Weather {}\n\nvoid main() {\n  group('WeatherRepository', () {\n    late open_meteo_api.OpenMeteoApiClient weatherApiClient;\n    late WeatherRepository weatherRepository;\n\n    setUp(() {\n      weatherApiClient = MockOpenMeteoApiClient();\n      weatherRepository = WeatherRepository(\n        weatherApiClient: weatherApiClient,\n      );\n    });\n\n    group('constructor', () {\n      test('instantiates internal weather api client when not injected', () {\n        expect(WeatherRepository(), isNotNull);\n      });\n    });\n\n    group('getWeather', () {\n      const city = 'chicago';\n      const latitude = 41.85003;\n      const longitude = -87.65005;\n\n      test('calls locationSearch with correct city', () async {\n        try {\n          await weatherRepository.getWeather(city);\n        } catch (_) {}\n        verify(() => weatherApiClient.locationSearch(city)).called(1);\n      });\n\n      test('throws when locationSearch fails', () async {\n        final exception = Exception('oops');\n        when(() => weatherApiClient.locationSearch(any())).thenThrow(exception);\n        expect(\n          () async => weatherRepository.getWeather(city),\n          throwsA(exception),\n        );\n      });\n\n      test('calls getWeather with correct latitude/longitude', () async {\n        final location = MockLocation();\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        try {\n          await weatherRepository.getWeather(city);\n        } catch (_) {}\n        verify(\n          () => weatherApiClient.getWeather(\n            latitude: latitude,\n            longitude: longitude,\n          ),\n        ).called(1);\n      });\n\n      test('throws when getWeather fails', () async {\n        final exception = Exception('oops');\n        final location = MockLocation();\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenThrow(exception);\n        expect(\n          () async => weatherRepository.getWeather(city),\n          throwsA(exception),\n        );\n      });\n\n      test('returns correct weather on success (clear)', () async {\n        final location = MockLocation();\n        final weather = MockWeather();\n        when(() => location.name).thenReturn(city);\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weather.temperature).thenReturn(42.42);\n        when(() => weather.weatherCode).thenReturn(0);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenAnswer((_) async => weather);\n        final actual = await weatherRepository.getWeather(city);\n        expect(\n          actual,\n          Weather(\n            temperature: 42.42,\n            location: city,\n            condition: WeatherCondition.clear,\n          ),\n        );\n      });\n\n      test('returns correct weather on success (cloudy)', () async {\n        final location = MockLocation();\n        final weather = MockWeather();\n        when(() => location.name).thenReturn(city);\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weather.temperature).thenReturn(42.42);\n        when(() => weather.weatherCode).thenReturn(1);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenAnswer((_) async => weather);\n        final actual = await weatherRepository.getWeather(city);\n        expect(\n          actual,\n          Weather(\n            temperature: 42.42,\n            location: city,\n            condition: WeatherCondition.cloudy,\n          ),\n        );\n      });\n\n      test('returns correct weather on success (rainy)', () async {\n        final location = MockLocation();\n        final weather = MockWeather();\n        when(() => location.name).thenReturn(city);\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weather.temperature).thenReturn(42.42);\n        when(() => weather.weatherCode).thenReturn(51);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenAnswer((_) async => weather);\n        final actual = await weatherRepository.getWeather(city);\n        expect(\n          actual,\n          Weather(\n            temperature: 42.42,\n            location: city,\n            condition: WeatherCondition.rainy,\n          ),\n        );\n      });\n\n      test('returns correct weather on success (snowy)', () async {\n        final location = MockLocation();\n        final weather = MockWeather();\n        when(() => location.name).thenReturn(city);\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weather.temperature).thenReturn(42.42);\n        when(() => weather.weatherCode).thenReturn(71);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenAnswer((_) async => weather);\n        final actual = await weatherRepository.getWeather(city);\n        expect(\n          actual,\n          Weather(\n            temperature: 42.42,\n            location: city,\n            condition: WeatherCondition.snowy,\n          ),\n        );\n      });\n\n      test('returns correct weather on success (unknown)', () async {\n        final location = MockLocation();\n        final weather = MockWeather();\n        when(() => location.name).thenReturn(city);\n        when(() => location.latitude).thenReturn(latitude);\n        when(() => location.longitude).thenReturn(longitude);\n        when(() => weather.temperature).thenReturn(42.42);\n        when(() => weather.weatherCode).thenReturn(-1);\n        when(() => weatherApiClient.locationSearch(any())).thenAnswer(\n          (_) async => location,\n        );\n        when(\n          () => weatherApiClient.getWeather(\n            latitude: any(named: 'latitude'),\n            longitude: any(named: 'longitude'),\n          ),\n        ).thenAnswer((_) async => weather);\n        final actual = await weatherRepository.getWeather(city);\n        expect(\n          actual,\n          Weather(\n            temperature: 42.42,\n            location: city,\n            condition: WeatherCondition.unknown,\n          ),\n        );\n      });\n    });\n\n    group('dispose', () {\n      test('closes the weather api client', () {\n        weatherRepository.dispose();\n        verify(weatherApiClient.close).called(1);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/pubspec.yaml",
    "content": "name: flutter_weather\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  google_fonts: ^6.0.0\n  hydrated_bloc: ^10.0.0\n  json_annotation: ^4.8.1\n  path_provider: ^2.0.8\n  weather_repository:\n    path: packages/weather_repository\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  bloc_test: ^10.0.0\n  build_runner: ^2.0.0\n  flutter_test:\n    sdk: flutter\n  json_serializable: ^6.0.0\n  mocktail: ^1.0.0\n\nflutter:\n  uses-material-design: true\n  assets:\n    - assets/\n"
  },
  {
    "path": "examples/flutter_weather/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  bloc_test:\n    path: ../../packages/bloc_test\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n  hydrated_bloc:\n    path: ../../packages/hydrated_bloc\n"
  },
  {
    "path": "examples/flutter_weather/test/app_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/app.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:weather_repository/weather_repository.dart'\n    show WeatherRepository;\n\nimport 'helpers/hydrated_bloc.dart';\n\nclass MockWeatherCubit extends MockCubit<WeatherState>\n    implements WeatherCubit {}\n\nclass MockWeatherRepository extends Mock implements WeatherRepository {}\n\nvoid main() {\n  initHydratedStorage();\n\n  group('WeatherApp', () {\n    testWidgets('renders WeatherAppView', (tester) async {\n      await tester.pumpWidget(const WeatherApp());\n      expect(find.byType(WeatherAppView), findsOneWidget);\n    });\n  });\n\n  group('WeatherAppView', () {\n    late WeatherCubit weatherCubit;\n    late WeatherRepository weatherRepository;\n\n    setUp(() {\n      weatherCubit = MockWeatherCubit();\n      weatherRepository = MockWeatherRepository();\n    });\n\n    testWidgets('renders WeatherPage', (tester) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: weatherRepository,\n          child: BlocProvider.value(\n            value: weatherCubit,\n            child: const WeatherAppView(),\n          ),\n        ),\n      );\n      expect(find.byType(WeatherPage), findsOneWidget);\n    });\n\n    testWidgets('has correct theme color scheme', (tester) async {\n      final state = WeatherState(\n        status: WeatherStatus.success,\n        weather: Weather(\n          condition: WeatherCondition.rainy,\n          lastUpdated: DateTime(2024),\n          location: 'Seattle',\n          temperature: const Temperature(value: 20),\n        ),\n      );\n      when(() => weatherCubit.state).thenReturn(state);\n      await tester.pumpWidget(\n        RepositoryProvider.value(\n          value: weatherRepository,\n          child: BlocProvider.value(\n            value: weatherCubit,\n            child: const WeatherAppView(),\n          ),\n        ),\n      );\n      final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));\n      expect(\n        materialApp.theme?.colorScheme,\n        ColorScheme.fromSeed(seedColor: Colors.indigoAccent),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/helpers/hydrated_bloc.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockStorage extends Mock implements Storage {}\n\nlate Storage hydratedStorage;\n\nvoid initHydratedStorage() {\n  TestWidgetsFlutterBinding.ensureInitialized();\n  hydratedStorage = MockStorage();\n  when(\n    () => hydratedStorage.write(any(), any<dynamic>()),\n  ).thenAnswer((_) async {});\n  HydratedBloc.storage = hydratedStorage;\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/search/view/search_page_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/search/search.dart';\n\nvoid main() {\n  group('SearchPage', () {\n    testWidgets('is routable', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Builder(\n            builder: (context) => Scaffold(\n              floatingActionButton: FloatingActionButton(\n                onPressed: () {\n                  Navigator.of(context).push(SearchPage.route());\n                },\n              ),\n            ),\n          ),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SearchPage), findsOneWidget);\n    });\n\n    testWidgets('returns selected text when popped', (tester) async {\n      String? location;\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Builder(\n            builder: (context) => Scaffold(\n              floatingActionButton: FloatingActionButton(\n                onPressed: () async {\n                  location = await Navigator.of(context).push(\n                    SearchPage.route(),\n                  );\n                },\n              ),\n            ),\n          ),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      await tester.enterText(find.byType(TextField), 'Chicago');\n      await tester.tap(find.byKey(const Key('searchPage_search_iconButton')));\n      await tester.pumpAndSettle();\n      expect(find.byType(SearchPage), findsNothing);\n      expect(location, 'Chicago');\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/settings/view/settings_page_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/settings/settings.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockWeatherCubit extends MockCubit<WeatherState>\n    implements WeatherCubit {}\n\nvoid main() {\n  group('SettingsPage', () {\n    late WeatherCubit weatherCubit;\n\n    setUp(() {\n      weatherCubit = MockWeatherCubit();\n    });\n\n    testWidgets('is routable', (tester) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(\n            home: Builder(\n              builder: (context) => Scaffold(\n                floatingActionButton: FloatingActionButton(\n                  onPressed: () {\n                    Navigator.of(context).push<void>(\n                      SettingsPage.route(),\n                    );\n                  },\n                ),\n              ),\n            ),\n          ),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SettingsPage), findsOneWidget);\n    });\n\n    testWidgets('calls toggleUnits when switch is changed', (tester) async {\n      whenListen(\n        weatherCubit,\n        Stream.fromIterable([\n          WeatherState(),\n          WeatherState(temperatureUnits: TemperatureUnits.fahrenheit),\n        ]),\n      );\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(\n            home: Builder(\n              builder: (context) => Scaffold(\n                floatingActionButton: FloatingActionButton(\n                  onPressed: () {\n                    Navigator.of(context).push<void>(\n                      SettingsPage.route(),\n                    );\n                  },\n                ),\n              ),\n            ),\n          ),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      await tester.tap(find.byType(Switch));\n      verify(() => weatherCubit.toggleUnits()).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/cubit/weather_cubit_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:weather_repository/weather_repository.dart'\n    as weather_repository;\n\nimport '../../helpers/hydrated_bloc.dart';\n\nconst weatherLocation = 'London';\nconst weatherCondition = weather_repository.WeatherCondition.rainy;\nconst weatherTemperature = 9.8;\n\nclass MockWeatherRepository extends Mock\n    implements weather_repository.WeatherRepository {}\n\nclass MockWeather extends Mock implements weather_repository.Weather {}\n\nvoid main() {\n  initHydratedStorage();\n\n  group('WeatherCubit', () {\n    late weather_repository.Weather weather;\n    late weather_repository.WeatherRepository weatherRepository;\n    late WeatherCubit weatherCubit;\n\n    setUp(() async {\n      weather = MockWeather();\n      weatherRepository = MockWeatherRepository();\n      when(() => weather.condition).thenReturn(weatherCondition);\n      when(() => weather.location).thenReturn(weatherLocation);\n      when(() => weather.temperature).thenReturn(weatherTemperature);\n      when(\n        () => weatherRepository.getWeather(any()),\n      ).thenAnswer((_) async => weather);\n      weatherCubit = WeatherCubit(weatherRepository);\n    });\n\n    test('initial state is correct', () {\n      final weatherCubit = WeatherCubit(weatherRepository);\n      expect(weatherCubit.state, WeatherState());\n    });\n\n    group('toJson/fromJson', () {\n      test('work properly', () {\n        final weatherCubit = WeatherCubit(weatherRepository);\n        expect(\n          weatherCubit.fromJson(weatherCubit.toJson(weatherCubit.state)),\n          weatherCubit.state,\n        );\n      });\n    });\n\n    group('fetchWeather', () {\n      blocTest<WeatherCubit, WeatherState>(\n        'emits nothing when city is null',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.fetchWeather(null),\n        expect: () => <WeatherState>[],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits nothing when city is empty',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.fetchWeather(''),\n        expect: () => <WeatherState>[],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'calls getWeather with correct city',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.fetchWeather(weatherLocation),\n        verify: (_) {\n          verify(() => weatherRepository.getWeather(weatherLocation)).called(1);\n        },\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits [loading, failure] when getWeather throws',\n        setUp: () {\n          when(\n            () => weatherRepository.getWeather(any()),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => weatherCubit,\n        act: (cubit) => cubit.fetchWeather(weatherLocation),\n        expect: () => <WeatherState>[\n          WeatherState(status: WeatherStatus.loading),\n          WeatherState(status: WeatherStatus.failure),\n        ],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits [loading, success] when getWeather returns (celsius)',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.fetchWeather(weatherLocation),\n        expect: () => <dynamic>[\n          WeatherState(status: WeatherStatus.loading),\n          isA<WeatherState>()\n              .having((w) => w.status, 'status', WeatherStatus.success)\n              .having(\n                (w) => w.weather,\n                'weather',\n                isA<Weather>()\n                    .having((w) => w.lastUpdated, 'lastUpdated', isNotNull)\n                    .having((w) => w.condition, 'condition', weatherCondition)\n                    .having(\n                      (w) => w.temperature,\n                      'temperature',\n                      Temperature(value: weatherTemperature),\n                    )\n                    .having((w) => w.location, 'location', weatherLocation),\n              ),\n        ],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits [loading, success] when getWeather returns (fahrenheit)',\n        build: () => weatherCubit,\n        seed: () => WeatherState(temperatureUnits: TemperatureUnits.fahrenheit),\n        act: (cubit) => cubit.fetchWeather(weatherLocation),\n        expect: () => <dynamic>[\n          WeatherState(\n            status: WeatherStatus.loading,\n            temperatureUnits: TemperatureUnits.fahrenheit,\n          ),\n          isA<WeatherState>()\n              .having((w) => w.status, 'status', WeatherStatus.success)\n              .having(\n                (w) => w.weather,\n                'weather',\n                isA<Weather>()\n                    .having((w) => w.lastUpdated, 'lastUpdated', isNotNull)\n                    .having((w) => w.condition, 'condition', weatherCondition)\n                    .having(\n                      (w) => w.temperature,\n                      'temperature',\n                      Temperature(value: weatherTemperature.toFahrenheit()),\n                    )\n                    .having((w) => w.location, 'location', weatherLocation),\n              ),\n        ],\n      );\n    });\n\n    group('refreshWeather', () {\n      blocTest<WeatherCubit, WeatherState>(\n        'emits nothing when status is not success',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.refreshWeather(),\n        expect: () => <WeatherState>[],\n        verify: (_) {\n          verifyNever(() => weatherRepository.getWeather(any()));\n        },\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits nothing when location is null',\n        build: () => weatherCubit,\n        seed: () => WeatherState(status: WeatherStatus.success),\n        act: (cubit) => cubit.refreshWeather(),\n        expect: () => <WeatherState>[],\n        verify: (_) {\n          verifyNever(() => weatherRepository.getWeather(any()));\n        },\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'invokes getWeather with correct location',\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          status: WeatherStatus.success,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: weatherTemperature),\n            lastUpdated: DateTime(2020),\n            condition: weatherCondition,\n          ),\n        ),\n        act: (cubit) => cubit.refreshWeather(),\n        verify: (_) {\n          verify(() => weatherRepository.getWeather(weatherLocation)).called(1);\n        },\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits nothing when exception is thrown',\n        setUp: () {\n          when(\n            () => weatherRepository.getWeather(any()),\n          ).thenThrow(Exception('oops'));\n        },\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          status: WeatherStatus.success,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: weatherTemperature),\n            lastUpdated: DateTime(2020),\n            condition: weatherCondition,\n          ),\n        ),\n        act: (cubit) => cubit.refreshWeather(),\n        expect: () => <WeatherState>[],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits updated weather (celsius)',\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          status: WeatherStatus.success,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: 0),\n            lastUpdated: DateTime(2020),\n            condition: weatherCondition,\n          ),\n        ),\n        act: (cubit) => cubit.refreshWeather(),\n        expect: () => <Matcher>[\n          isA<WeatherState>()\n              .having((w) => w.status, 'status', WeatherStatus.success)\n              .having(\n                (w) => w.weather,\n                'weather',\n                isA<Weather>()\n                    .having((w) => w.lastUpdated, 'lastUpdated', isNotNull)\n                    .having((w) => w.condition, 'condition', weatherCondition)\n                    .having(\n                      (w) => w.temperature,\n                      'temperature',\n                      Temperature(value: weatherTemperature),\n                    )\n                    .having((w) => w.location, 'location', weatherLocation),\n              ),\n        ],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits updated weather (fahrenheit)',\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          temperatureUnits: TemperatureUnits.fahrenheit,\n          status: WeatherStatus.success,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: 0),\n            lastUpdated: DateTime(2020),\n            condition: weatherCondition,\n          ),\n        ),\n        act: (cubit) => cubit.refreshWeather(),\n        expect: () => <Matcher>[\n          isA<WeatherState>()\n              .having((w) => w.status, 'status', WeatherStatus.success)\n              .having(\n                (w) => w.weather,\n                'weather',\n                isA<Weather>()\n                    .having((w) => w.lastUpdated, 'lastUpdated', isNotNull)\n                    .having((w) => w.condition, 'condition', weatherCondition)\n                    .having(\n                      (w) => w.temperature,\n                      'temperature',\n                      Temperature(value: weatherTemperature.toFahrenheit()),\n                    )\n                    .having((w) => w.location, 'location', weatherLocation),\n              ),\n        ],\n      );\n    });\n\n    group('toggleUnits', () {\n      blocTest<WeatherCubit, WeatherState>(\n        'emits updated units when status is not success',\n        build: () => weatherCubit,\n        act: (cubit) => cubit.toggleUnits(),\n        expect: () => <WeatherState>[\n          WeatherState(temperatureUnits: TemperatureUnits.fahrenheit),\n        ],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits updated units and temperature '\n        'when status is success (celsius)',\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          status: WeatherStatus.success,\n          temperatureUnits: TemperatureUnits.fahrenheit,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: weatherTemperature),\n            lastUpdated: DateTime(2020),\n            condition: WeatherCondition.rainy,\n          ),\n        ),\n        act: (cubit) => cubit.toggleUnits(),\n        expect: () => <WeatherState>[\n          WeatherState(\n            status: WeatherStatus.success,\n            weather: Weather(\n              location: weatherLocation,\n              temperature: Temperature(value: weatherTemperature.toCelsius()),\n              lastUpdated: DateTime(2020),\n              condition: WeatherCondition.rainy,\n            ),\n          ),\n        ],\n      );\n\n      blocTest<WeatherCubit, WeatherState>(\n        'emits updated units and temperature '\n        'when status is success (fahrenheit)',\n        build: () => weatherCubit,\n        seed: () => WeatherState(\n          status: WeatherStatus.success,\n          weather: Weather(\n            location: weatherLocation,\n            temperature: Temperature(value: weatherTemperature),\n            lastUpdated: DateTime(2020),\n            condition: WeatherCondition.rainy,\n          ),\n        ),\n        act: (cubit) => cubit.toggleUnits(),\n        expect: () => <WeatherState>[\n          WeatherState(\n            status: WeatherStatus.success,\n            temperatureUnits: TemperatureUnits.fahrenheit,\n            weather: Weather(\n              location: weatherLocation,\n              temperature: Temperature(\n                value: weatherTemperature.toFahrenheit(),\n              ),\n              lastUpdated: DateTime(2020),\n              condition: WeatherCondition.rainy,\n            ),\n          ),\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/cubit/weather_state_test.dart",
    "content": "import 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nvoid main() {\n  group('WeatherStatusX', () {\n    test('returns correct values for WeatherStatus.initial', () {\n      const status = WeatherStatus.initial;\n      expect(status.isInitial, isTrue);\n      expect(status.isLoading, isFalse);\n      expect(status.isSuccess, isFalse);\n      expect(status.isFailure, isFalse);\n    });\n\n    test('returns correct values for WeatherStatus.loading', () {\n      const status = WeatherStatus.loading;\n      expect(status.isInitial, isFalse);\n      expect(status.isLoading, isTrue);\n      expect(status.isSuccess, isFalse);\n      expect(status.isFailure, isFalse);\n    });\n\n    test('returns correct values for WeatherStatus.success', () {\n      const status = WeatherStatus.success;\n      expect(status.isInitial, isFalse);\n      expect(status.isLoading, isFalse);\n      expect(status.isSuccess, isTrue);\n      expect(status.isFailure, isFalse);\n    });\n\n    test('returns correct values for WeatherStatus.failure', () {\n      const status = WeatherStatus.failure;\n      expect(status.isInitial, isFalse);\n      expect(status.isLoading, isFalse);\n      expect(status.isSuccess, isFalse);\n      expect(status.isFailure, isTrue);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/view/weather_page_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/search/search.dart';\nimport 'package:flutter_weather/settings/settings.dart';\nimport 'package:flutter_weather/weather/weather.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:weather_repository/weather_repository.dart' hide Weather;\n\nimport '../../helpers/hydrated_bloc.dart';\n\nclass MockWeatherRepository extends Mock implements WeatherRepository {}\n\nclass MockWeatherCubit extends MockCubit<WeatherState>\n    implements WeatherCubit {}\n\nvoid main() {\n  initHydratedStorage();\n\n  group('WeatherPage', () {\n    final weather = Weather(\n      temperature: Temperature(value: 4.2),\n      condition: WeatherCondition.cloudy,\n      lastUpdated: DateTime(2020),\n      location: 'London',\n    );\n    late WeatherCubit weatherCubit;\n\n    setUp(() {\n      weatherCubit = MockWeatherCubit();\n    });\n\n    testWidgets('renders WeatherEmpty for WeatherStatus.initial', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      expect(find.byType(WeatherEmpty), findsOneWidget);\n    });\n\n    testWidgets('renders WeatherLoading for WeatherStatus.loading', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(\n        WeatherState(\n          status: WeatherStatus.loading,\n        ),\n      );\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      expect(find.byType(WeatherLoading), findsOneWidget);\n    });\n\n    testWidgets('renders WeatherPopulated for WeatherStatus.success', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(\n        WeatherState(\n          status: WeatherStatus.success,\n          weather: weather,\n        ),\n      );\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      expect(find.byType(WeatherPopulated), findsOneWidget);\n    });\n\n    testWidgets('renders WeatherError for WeatherStatus.failure', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(\n        WeatherState(\n          status: WeatherStatus.failure,\n        ),\n      );\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      expect(find.byType(WeatherError), findsOneWidget);\n    });\n\n    testWidgets('state is cached', (tester) async {\n      when<dynamic>(() => hydratedStorage.read('$WeatherCubit')).thenReturn(\n        WeatherState(\n          status: WeatherStatus.success,\n          weather: weather,\n          temperatureUnits: TemperatureUnits.fahrenheit,\n        ).toJson(),\n      );\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: WeatherCubit(MockWeatherRepository()),\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      expect(find.byType(WeatherPopulated), findsOneWidget);\n    });\n\n    testWidgets('navigates to SettingsPage when settings icon is tapped', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      await tester.tap(find.byType(IconButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SettingsPage), findsOneWidget);\n    });\n\n    testWidgets('navigates to SearchPage when search button is tapped', (\n      tester,\n    ) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      expect(find.byType(SearchPage), findsOneWidget);\n    });\n\n    testWidgets('triggers refreshWeather on pull to refresh', (tester) async {\n      when(() => weatherCubit.state).thenReturn(\n        WeatherState(\n          status: WeatherStatus.success,\n          weather: weather,\n        ),\n      );\n      when(() => weatherCubit.refreshWeather()).thenAnswer((_) async {});\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      await tester.fling(\n        find.text('London'),\n        const Offset(0, 500),\n        1000,\n      );\n      await tester.pumpAndSettle();\n      verify(() => weatherCubit.refreshWeather()).called(1);\n    });\n\n    testWidgets('triggers fetch on search pop', (tester) async {\n      when(() => weatherCubit.state).thenReturn(WeatherState());\n      when(() => weatherCubit.fetchWeather(any())).thenAnswer((_) async {});\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: weatherCubit,\n          child: MaterialApp(home: WeatherPage()),\n        ),\n      );\n      await tester.tap(find.byType(FloatingActionButton));\n      await tester.pumpAndSettle();\n      await tester.enterText(find.byType(TextField), 'Chicago');\n      await tester.tap(find.byKey(const Key('searchPage_search_iconButton')));\n      await tester.pumpAndSettle();\n      verify(() => weatherCubit.fetchWeather('Chicago')).called(1);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/widgets/weather_empty_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nvoid main() {\n  group('WeatherEmpty', () {\n    testWidgets('renders correct text and icon', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherEmpty(),\n          ),\n        ),\n      );\n      expect(find.text('Please Select a City!'), findsOneWidget);\n      expect(find.text('🏙️'), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/widgets/weather_error_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nvoid main() {\n  group('WeatherError', () {\n    testWidgets('renders correct text and icon', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherError(),\n          ),\n        ),\n      );\n      expect(find.text('Something went wrong!'), findsOneWidget);\n      expect(find.text('🙈'), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/widgets/weather_loading_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nvoid main() {\n  group('WeatherLoading', () {\n    testWidgets('renders correct text and icon', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherLoading(),\n          ),\n        ),\n      );\n      expect(find.text('Loading Weather'), findsOneWidget);\n      expect(find.byType(CircularProgressIndicator), findsOneWidget);\n      expect(find.text('⛅'), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/test/weather/widgets/weather_populated_test.dart",
    "content": "// ignore_for_file: prefer_const_constructors\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:flutter_weather/weather/weather.dart';\n\nvoid main() {\n  group('WeatherPopulated', () {\n    final weather = Weather(\n      condition: WeatherCondition.clear,\n      temperature: Temperature(value: 42),\n      location: 'Chicago',\n      lastUpdated: DateTime(2020),\n    );\n\n    testWidgets('renders correct emoji (clear)', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherPopulated(\n              weather: weather,\n              units: TemperatureUnits.fahrenheit,\n              onRefresh: () async {},\n            ),\n          ),\n        ),\n      );\n      expect(find.text('☀️'), findsOneWidget);\n    });\n\n    testWidgets('renders correct emoji (rainy)', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherPopulated(\n              weather: weather.copyWith(condition: WeatherCondition.rainy),\n              units: TemperatureUnits.fahrenheit,\n              onRefresh: () async {},\n            ),\n          ),\n        ),\n      );\n      expect(find.text('🌧️'), findsOneWidget);\n    });\n\n    testWidgets('renders correct emoji (cloudy)', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherPopulated(\n              weather: weather.copyWith(condition: WeatherCondition.cloudy),\n              units: TemperatureUnits.fahrenheit,\n              onRefresh: () async {},\n            ),\n          ),\n        ),\n      );\n      expect(find.text('☁️'), findsOneWidget);\n    });\n\n    testWidgets('renders correct emoji (snowy)', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherPopulated(\n              weather: weather.copyWith(condition: WeatherCondition.snowy),\n              units: TemperatureUnits.fahrenheit,\n              onRefresh: () async {},\n            ),\n          ),\n        ),\n      );\n      expect(find.text('🌨️'), findsOneWidget);\n    });\n\n    testWidgets('renders correct emoji (unknown)', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: WeatherPopulated(\n              weather: weather.copyWith(condition: WeatherCondition.unknown),\n              units: TemperatureUnits.fahrenheit,\n              onRefresh: () async {},\n            ),\n          ),\n        ),\n      );\n      expect(find.text('❓'), findsOneWidget);\n    });\n  });\n}\n"
  },
  {
    "path": "examples/flutter_weather/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_weather\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_weather</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_weather/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_weather\",\n    \"short_name\": \"flutter_weather\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/flutter_wizard/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/flutter_wizard/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/flutter_wizard/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_wizard\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/flutter_wizard/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/flutter_wizard/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/flutter_wizard/lib/bloc/profile_wizard_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'profile_wizard_event.dart';\npart 'profile_wizard_state.dart';\n\nclass ProfileWizardBloc extends Bloc<ProfileWizardEvent, ProfileWizardState> {\n  ProfileWizardBloc() : super(ProfileWizardState.initial()) {\n    on<ProfileWizardNameSubmitted>((event, emit) {\n      emit(state.copyWith(profile: state.profile.copyWith(name: event.name)));\n    });\n\n    on<ProfileWizardAgeSubmitted>((event, emit) {\n      emit(state.copyWith(profile: state.profile.copyWith(age: event.age)));\n    });\n  }\n}\n"
  },
  {
    "path": "examples/flutter_wizard/lib/bloc/profile_wizard_event.dart",
    "content": "part of 'profile_wizard_bloc.dart';\n\nsealed class ProfileWizardEvent extends Equatable {\n  const ProfileWizardEvent();\n\n  @override\n  List<Object?> get props => [];\n}\n\nfinal class ProfileWizardNameSubmitted extends ProfileWizardEvent {\n  const ProfileWizardNameSubmitted(this.name);\n\n  final String name;\n\n  @override\n  List<Object> get props => [name];\n}\n\nfinal class ProfileWizardAgeSubmitted extends ProfileWizardEvent {\n  const ProfileWizardAgeSubmitted(this.age);\n\n  final int? age;\n\n  @override\n  List<Object?> get props => [age];\n}\n"
  },
  {
    "path": "examples/flutter_wizard/lib/bloc/profile_wizard_state.dart",
    "content": "part of 'profile_wizard_bloc.dart';\n\nfinal class Profile extends Equatable {\n  const Profile({required this.name, required this.age});\n\n  final String? name;\n  final int? age;\n\n  Profile copyWith({String? name, int? age}) {\n    return Profile(\n      name: name ?? this.name,\n      age: age ?? this.age,\n    );\n  }\n\n  @override\n  List<Object?> get props => [name, age];\n}\n\nfinal class ProfileWizardState extends Equatable {\n  ProfileWizardState({required this.profile}) : lastUpdated = DateTime.now();\n\n  ProfileWizardState.initial()\n    : this(profile: const Profile(name: null, age: null));\n\n  final Profile profile;\n  final DateTime lastUpdated;\n\n  ProfileWizardState copyWith({Profile? profile}) {\n    return ProfileWizardState(\n      profile: profile ?? this.profile,\n    );\n  }\n\n  @override\n  List<Object> get props => [profile, lastUpdated];\n}\n"
  },
  {
    "path": "examples/flutter_wizard/lib/main.dart",
    "content": "import 'package:flow_builder/flow_builder.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_wizard/bloc/profile_wizard_bloc.dart';\n\nvoid main() => runApp(const MyApp());\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) => const MaterialApp(home: Home());\n}\n\nclass Home extends StatefulWidget {\n  const Home({super.key});\n\n  @override\n  State<Home> createState() => _HomeState();\n}\n\nclass _HomeState extends State<Home> {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Home')),\n      body: Center(\n        child: Builder(\n          builder: (context) {\n            return ElevatedButton(\n              onPressed: () async {\n                final profile = await Navigator.of(context).push(\n                  ProfileWizard.route(),\n                );\n                if (!context.mounted) return;\n                ScaffoldMessenger.of(context)\n                  ..hideCurrentSnackBar()\n                  ..showSnackBar(SnackBar(content: Text('$profile')));\n              },\n              child: const Text('Start Profile Wizard'),\n            );\n          },\n        ),\n      ),\n    );\n  }\n}\n\nclass ProfileWizard extends StatelessWidget {\n  const ProfileWizard({super.key});\n\n  static Route<Profile> route() {\n    return MaterialPageRoute(builder: (_) => const ProfileWizard());\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => ProfileWizardBloc(),\n      child: ProfileWizardFlow(\n        onComplete: (profile) => Navigator.of(context).pop(profile),\n      ),\n    );\n  }\n}\n\nclass ProfileWizardFlow extends StatelessWidget {\n  const ProfileWizardFlow({required this.onComplete, super.key});\n\n  final ValueSetter<Profile> onComplete;\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<ProfileWizardBloc, ProfileWizardState>(\n      listenWhen: (_, state) => state.profile.isComplete,\n      listener: (context, state) => onComplete(state.profile),\n      child: FlowBuilder<ProfileWizardState>(\n        state: context.watch<ProfileWizardBloc>().state,\n        onGeneratePages: (state, pages) {\n          return [\n            ProfileNameForm.page(),\n            if (state.profile.name != null) ProfileAgeForm.page(),\n          ];\n        },\n      ),\n    );\n  }\n}\n\nclass ProfileNameForm extends StatefulWidget {\n  const ProfileNameForm({super.key});\n\n  static Page<void> page() {\n    return const MaterialPage<void>(child: ProfileNameForm());\n  }\n\n  @override\n  State<ProfileNameForm> createState() => _ProfileNameFormState();\n}\n\nclass _ProfileNameFormState extends State<ProfileNameForm> {\n  var _name = '';\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Name')),\n      body: Center(\n        child: Padding(\n          padding: const EdgeInsets.all(8),\n          child: Column(\n            children: <Widget>[\n              TextField(\n                onChanged: (value) => setState(() => _name = value),\n                decoration: const InputDecoration(\n                  labelText: 'Name',\n                  hintText: 'John Doe',\n                ),\n              ),\n              ElevatedButton(\n                onPressed: _name.isNotEmpty\n                    ? () => context.read<ProfileWizardBloc>().add(\n                        ProfileWizardNameSubmitted(_name),\n                      )\n                    : null,\n                child: const Text('Continue'),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nclass ProfileAgeForm extends StatefulWidget {\n  const ProfileAgeForm({super.key});\n\n  static Page<void> page() => const MaterialPage<void>(child: ProfileAgeForm());\n\n  @override\n  State<ProfileAgeForm> createState() => _ProfileAgeFormState();\n}\n\nclass _ProfileAgeFormState extends State<ProfileAgeForm> {\n  int? _age;\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Age')),\n      body: Center(\n        child: Padding(\n          padding: const EdgeInsets.all(8),\n          child: Column(\n            children: <Widget>[\n              TextField(\n                onChanged: (value) => setState(() => _age = int.parse(value)),\n                decoration: const InputDecoration(\n                  labelText: 'Age',\n                  hintText: '42',\n                ),\n                keyboardType: TextInputType.number,\n              ),\n              ElevatedButton(\n                onPressed: _age != null\n                    ? () => context.read<ProfileWizardBloc>().add(\n                        ProfileWizardAgeSubmitted(_age),\n                      )\n                    : null,\n                child: const Text('Continue'),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nextension on Profile {\n  bool get isComplete => name != null && age != null;\n}\n"
  },
  {
    "path": "examples/flutter_wizard/pubspec.yaml",
    "content": "name: flutter_wizard\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  flow_builder: ^0.1.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\nflutter:\n  uses-material-design: true\ndev_dependencies:\n  bloc_lint: ^0.3.0\n"
  },
  {
    "path": "examples/flutter_wizard/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../packages/bloc\n  bloc_lint:\n    path: ../../packages/bloc_lint\n  flutter_bloc:\n    path: ../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/flutter_wizard/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_wizard\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_wizard</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/flutter_wizard/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_wizard\",\n    \"short_name\": \"flutter_wizard\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/github_search/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# Github Search\n\nSample project which illustrates how to setup a Flutter and AngularDart project with code sharing.\n\n## Quick Start\n\n_Make sure you have the [Dart SDK](https://dart.dev/tools/sdk) and [Flutter SDK](https://flutter.dev/docs/get-started/install) installed before proceeding._\n\nOpen this project in your editor of choice (VSCode is recommended).\n\n### Setup\n\n1. Install dependencies for `common_github_search`:\n\n   ```bash\n   # change directories into common_github_search\n   cd common_github_search\n\n   # install dependencies\n   dart pub get\n\n   # change directories back out to the root directory\n   cd ../\n   ```\n\n2. Install dependencies for `flutter_github_search`\n\n   ```bash\n   # change directories into flutter_github_search\n   cd flutter_github_search\n\n   # install dependencies\n   flutter pub get\n\n   # change directories back out to the root directory\n   cd ../\n   ```\n\n3. Install dependencies for `angular_github_search`\n\n   ```bash\n   # change directories into flutter_github_search\n   cd angular_github_search\n\n   # install dependencies\n   dart pub get\n\n   # change directories into flutter_github_search\n   cd ../\n   ```\n\n### Run Flutter\n\n```bash\n# change directories into flutter_github_search\ncd flutter_github_search\n\n# run the flutter project\nflutter run\n```\n\n### Run AngularDart\n\n```bash\n\n# change directories into angular_github_search\ncd angular_github_search\n\n# run the angular project\nwebdev serve\n```\n"
  },
  {
    "path": "examples/github_search/angular_github_search/.gitignore",
    "content": "# Files and directories created by pub\n.dart_tool/\n.packages\n# Remove the following pattern if you wish to check in your lock file\npubspec.lock\n\n# Conventional directory for build outputs\nbuild/\n\n# Directory created by dartdoc\ndoc/api/\n"
  },
  {
    "path": "examples/github_search/angular_github_search/CHANGELOG.md",
    "content": "## 1.0.0\n\n- Initial version, created by Stagehand\n"
  },
  {
    "path": "examples/github_search/angular_github_search/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# angular_github_search\n\nA web app that uses [AngularDart](https://angulardart.dev/) and\n[AngularDart Components](https://angulardart.dev/components).\n\nCreated from templates made available by Stagehand under a BSD-style\n[license](https://github.com/dart-lang/stagehand/blob/master/LICENSE).\n"
  },
  {
    "path": "examples/github_search/angular_github_search/analysis_options.yaml",
    "content": "include: ../../../analysis_options.yaml\n\nanalyzer:\n  exclude: [build/**]\n  errors:\n    uri_has_not_been_generated: ignore\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/app_component.dart",
    "content": "import 'package:angular_github_search/src/github_search.dart';\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'my-app',\n  template: '<search-form [githubRepository]=\"githubRepository\"></search-form>',\n  directives: [SearchFormComponent],\n)\nclass AppComponent {\n  final githubRepository = GithubRepository();\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/github_search.dart",
    "content": "export './search_form/search_bar/search_bar_component.dart';\nexport './search_form/search_body/search_body_component.dart';\nexport './search_form/search_body/search_results/search_result_item/search_result_item_component.dart';\nexport './search_form/search_body/search_results/search_results_component.dart';\nexport './search_form/search_form_component.dart';\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'search-bar',\n  templateUrl: 'search_bar_component.html',\n)\nclass SearchBarComponent {\n  @Input()\n  late GithubSearchBloc githubSearchBloc;\n\n  void onTextChanged(String text) {\n    githubSearchBloc.add(TextChanged(text: text));\n  }\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_bar/search_bar_component.html",
    "content": "<label for=\"term\" class=\"clip\">Enter a search term</label>\n<input\n  id=\"term\"\n  placeholder=\"Enter a search term\"\n  class=\"input-reset outline-transparent glow o-50 bg-near-black near-white w-100 pv2 border-box b--white-50 br-0 bl-0 bt-0 bb-ridge mb3\"\n  autofocus\n  (keyup)=\"onTextChanged($event.target.value)\"\n/>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.dart",
    "content": "import 'package:angular_github_search/src/github_search.dart';\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'search-body',\n  templateUrl: 'search_body_component.html',\n  directives: [\n    coreDirectives,\n    SearchResultsComponent,\n  ],\n)\nclass SearchBodyComponent {\n  @Input()\n  late GithubSearchState state;\n\n  bool get isEmpty => state is SearchStateEmpty;\n  bool get isLoading => state is SearchStateLoading;\n  bool get isSuccess => state is SearchStateSuccess;\n  bool get isError => state is SearchStateError;\n\n  List<SearchResultItem> get items =>\n      isSuccess ? (state as SearchStateSuccess).items : [];\n\n  String get error => isError ? (state as SearchStateError).error : '';\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_body_component.html",
    "content": "<div *ngIf=\"state != null\" class=\"mw10\">\n  <div *ngIf=\"isEmpty\" class=\"tc\">\n    <span>🔍</span>\n    <p>Please enter a term to begin</p>\n  </div>\n  <div *ngIf=\"isLoading\">\n    <div class=\"sk-chase center\">\n      <div class=\"sk-chase-dot\"></div>\n      <div class=\"sk-chase-dot\"></div>\n      <div class=\"sk-chase-dot\"></div>\n      <div class=\"sk-chase-dot\"></div>\n      <div class=\"sk-chase-dot\"></div>\n      <div class=\"sk-chase-dot\"></div>\n    </div>\n  </div>\n  <div *ngIf=\"isError\" class=\"tc\">\n    <span>‼️</span>\n    <p>{{ error }}</p>\n  </div>\n  <div *ngIf=\"isSuccess\">\n    <div *ngIf=\"items.length == 0\" class=\"tc\">\n      <span>⚠️</span>\n      <p>No Results</p>\n    </div>\n    <search-results [items]=\"items\"></search-results>\n  </div>\n</div>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'search-result-item',\n  templateUrl: 'search_result_item_component.html',\n)\nclass SearchResultItemComponent {\n  @Input()\n  late SearchResultItem item;\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_result_item/search_result_item_component.html",
    "content": "<div class=\"fl w-10 h-auto\">\n  <img class=\"br-100\" src=\"{{ item.owner.avatarUrl }}\" />\n</div>\n<div class=\"fl w-90 ph3\">\n  <h1 class=\"f5 ma0\">{{ item.fullName }}</h1>\n  <p>\n    <a href=\"{{ item.htmlUrl }}\" class=\"light-blue\" target=\"_blank\">{{\n      item.htmlUrl\n    }}</a>\n  </p>\n</div>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart",
    "content": "import 'package:angular_github_search/src/github_search.dart';\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'search-results',\n  templateUrl: 'search_results_component.html',\n  directives: [coreDirectives, SearchResultItemComponent],\n)\nclass SearchResultsComponent {\n  @Input()\n  late List<SearchResultItem> items;\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.html",
    "content": "<ul class=\"list pa0 ma0\">\n  <li *ngFor=\"let item of items\" class=\"pa2 cf\">\n    <search-result-item [item]=\"item\"></search-result-item>\n  </li>\n</ul>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_form_component.dart",
    "content": "import 'package:angular_bloc/angular_bloc.dart';\nimport 'package:angular_github_search/src/github_search.dart';\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'search-form',\n  templateUrl: 'search_form_component.html',\n  directives: [\n    SearchBarComponent,\n    SearchBodyComponent,\n  ],\n  pipes: [BlocPipe],\n)\nclass SearchFormComponent implements OnInit, OnDestroy {\n  @Input()\n  late GithubRepository githubRepository;\n\n  late GithubSearchBloc githubSearchBloc;\n\n  @override\n  void ngOnInit() {\n    githubSearchBloc = GithubSearchBloc(\n      githubRepository: githubRepository,\n    );\n  }\n\n  @override\n  void ngOnDestroy() {\n    githubSearchBloc.close();\n  }\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/search_form/search_form_component.html",
    "content": "<div>\n  <h1>GitHub Search</h1>\n  <search-bar [githubSearchBloc]=\"githubSearchBloc\"></search-bar>\n  <search-body [state]=\"$pipe.bloc(githubSearchBloc)\"></search-body>\n</div>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/lib/src/src.dart",
    "content": "export 'github_search.dart';\n"
  },
  {
    "path": "examples/github_search/angular_github_search/pubspec.yaml",
    "content": "name: angular_github_search\ndescription: A web app that uses AngularDart Components\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  angular_bloc: ^10.0.0-dev.5\n  bloc: ^9.0.0\n  common_github_search:\n    path: ../common_github_search\n  ngdart: ^8.0.0-dev.4\n\ndev_dependencies:\n  build_daemon: ^4.0.0\n  build_runner: ^2.0.0\n  build_web_compilers: ^4.0.0\n"
  },
  {
    "path": "examples/github_search/angular_github_search/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  angular_bloc:\n    path: ../../../packages/angular_bloc\n  bloc:\n    path: ../../../packages/bloc\n  build_modules: ^5.0.0\n  build_web_compilers: ^4.0.0\n"
  },
  {
    "path": "examples/github_search/angular_github_search/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>angular_github_search</title>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"stylesheet\" href=\"styles.css\">\n    <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\">\n    <script defer src=\"main.dart.js\"></script>\n  </head>\n  <body>\n    <my-app>Loading...</my-app>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/github_search/angular_github_search/web/main.dart",
    "content": "import 'package:angular_github_search/app_component.template.dart' as ng;\nimport 'package:ngdart/angular.dart';\n\nvoid main() {\n  runApp(ng.AppComponentNgFactory);\n}\n"
  },
  {
    "path": "examples/github_search/angular_github_search/web/styles.css",
    "content": "@import url(https://fonts.googleapis.com/css?family=Material+Icons);\n\n.sk-chase {\n  width: 40px;\n  height: 40px;\n  position: relative;\n  animation: sk-chase 2.5s infinite linear both;\n}\n\n.sk-chase-dot {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  left: 0;\n  top: 0; \n  animation: sk-chase-dot 2.0s infinite ease-in-out both; \n}\n\n.sk-chase-dot:before {\n  content: '';\n  display: block;\n  width: 25%;\n  height: 25%;\n  background-color: #2196F3;\n  border-radius: 100%;\n  animation: sk-chase-dot-before 2.0s infinite ease-in-out both; \n}\n\n.sk-chase-dot:nth-child(1) { animation-delay: -1.1s; }\n.sk-chase-dot:nth-child(2) { animation-delay: -1.0s; }\n.sk-chase-dot:nth-child(3) { animation-delay: -0.9s; }\n.sk-chase-dot:nth-child(4) { animation-delay: -0.8s; }\n.sk-chase-dot:nth-child(5) { animation-delay: -0.7s; }\n.sk-chase-dot:nth-child(6) { animation-delay: -0.6s; }\n.sk-chase-dot:nth-child(1):before { animation-delay: -1.1s; }\n.sk-chase-dot:nth-child(2):before { animation-delay: -1.0s; }\n.sk-chase-dot:nth-child(3):before { animation-delay: -0.9s; }\n.sk-chase-dot:nth-child(4):before { animation-delay: -0.8s; }\n.sk-chase-dot:nth-child(5):before { animation-delay: -0.7s; }\n.sk-chase-dot:nth-child(6):before { animation-delay: -0.6s; }\n\n@keyframes sk-chase {\n  100% { transform: rotate(360deg); } \n}\n\n@keyframes sk-chase-dot {\n  80%, 100% { transform: rotate(360deg); } \n}\n\n@keyframes sk-chase-dot-before {\n  50% {\n    transform: scale(0.4); \n  } 100%, 0% {\n    transform: scale(1.0); \n  } \n}\n\n.bb-ridge {\n  border-bottom-style: ridge;\n}\n\n/*! TACHYONS v4.9.1 | http://tachyons.io */\n/*\n *\n *      ________            ______\n *      ___  __/_____ _________  /______  ______________________\n *      __  /  _  __ `/  ___/_  __ \\_  / / /  __ \\_  __ \\_  ___/\n *      _  /   / /_/ // /__ _  / / /  /_/ // /_/ /  / / /(__  )\n *      /_/    \\__,_/ \\___/ /_/ /_/_\\__, / \\____//_/ /_//____/\n *                                 /____/\n *\n *    TABLE OF CONTENTS\n *\n *    1. External Library Includes\n *       - Normalize.css | http://normalize.css.github.io\n *    2. Tachyons Modules\n *    3. Variables\n *       - Media Queries\n *       - Colors\n *    4. Debugging\n *       - Debug all\n *       - Debug children\n *\n */\n/* External Library Includes */\n/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */\n/* Document\n   ========================================================================== */\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\nhtml {\n  line-height: 1.15; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n/* Sections\n   ========================================================================== */\n/**\n * Remove the margin in all browsers.\n */\nbody {\n  margin: 0;\n}\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n/* Grouping content\n   ========================================================================== */\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\nhr {\n  box-sizing: content-box; /* 1 */\n  height: 0; /* 1 */\n  overflow: visible; /* 2 */\n}\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\npre {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n/* Text-level semantics\n   ========================================================================== */\n/**\n * Remove the gray background on active links in IE 10.\n */\na {\n  background-color: transparent;\n}\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\nabbr[title] {\n  border-bottom: none; /* 1 */\n  text-decoration: underline; /* 2 */\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted; /* 2 */\n}\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\nb,\nstrong {\n  font-weight: bolder;\n}\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n/**\n * Add the correct font size in all browsers.\n */\nsmall {\n  font-size: 80%;\n}\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\nsub {\n  bottom: -0.25em;\n}\nsup {\n  top: -0.5em;\n}\n/* Embedded content\n   ========================================================================== */\n/**\n * Remove the border on images inside links in IE 10.\n */\nimg {\n  border-style: none;\n}\n/* Forms\n   ========================================================================== */\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  line-height: 1.15; /* 1 */\n  margin: 0; /* 2 */\n}\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\nbutton,\ninput {\n  /* 1 */\n  overflow: visible;\n}\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\nbutton,\nselect {\n  /* 1 */\n  text-transform: none;\n}\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n  -webkit-appearance: button;\n}\n/**\n * Remove the inner border and padding in Firefox.\n */\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n/**\n * Restore the focus styles unset by the previous rule.\n */\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n/**\n * Correct the padding in Firefox.\n */\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n *    `fieldset` elements in all browsers.\n */\nlegend {\n  box-sizing: border-box; /* 1 */\n  color: inherit; /* 2 */\n  display: table; /* 1 */\n  max-width: 100%; /* 1 */\n  padding: 0; /* 3 */\n  white-space: normal; /* 1 */\n}\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\nprogress {\n  vertical-align: baseline;\n}\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\ntextarea {\n  overflow: auto;\n}\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n[type=\"checkbox\"],\n[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n/* Interactive\n   ========================================================================== */\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\ndetails {\n  display: block;\n}\n/*\n * Add the correct display in all browsers.\n */\nsummary {\n  display: list-item;\n}\n/* Misc\n   ========================================================================== */\n/**\n * Add the correct display in IE 10+.\n */\ntemplate {\n  display: none;\n}\n/**\n * Add the correct display in IE 10.\n */\n[hidden] {\n  display: none;\n}\n/* Modules */\n/*\n\n  BOX SIZING\n\n*/\nhtml,\nbody,\ndiv,\narticle,\naside,\nsection,\nmain,\nnav,\nfooter,\nheader,\nform,\nfieldset,\nlegend,\npre,\ncode,\na,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nul,\nol,\nli,\ndl,\ndt,\ndd,\nblockquote,\nfigcaption,\nfigure,\ntextarea,\ntable,\ntd,\nth,\ntr,\ninput[type=\"email\"],\ninput[type=\"number\"],\ninput[type=\"password\"],\ninput[type=\"tel\"],\ninput[type=\"text\"],\ninput[type=\"url\"],\n.border-box {\n  box-sizing: border-box;\n}\n/*\n\n   ASPECT RATIOS\n\n*/\n/* This is for fluid media that is embedded from third party sites like youtube, vimeo etc.\n * Wrap the outer element in aspect-ratio and then extend it with the desired ratio i.e\n * Make sure there are no height and width attributes on the embedded media.\n * Adapted from: https://github.com/suitcss/components-flex-embed\n *\n * Example:\n *\n * <div class=\"aspect-ratio aspect-ratio--16x9\">\n *  <iframe class=\"aspect-ratio--object\"></iframe>\n * </div>\n *\n * */\n.aspect-ratio {\n  height: 0;\n  position: relative;\n}\n.aspect-ratio--16x9 {\n  padding-bottom: 56.25%;\n}\n.aspect-ratio--9x16 {\n  padding-bottom: 177.77%;\n}\n.aspect-ratio--4x3 {\n  padding-bottom: 75%;\n}\n.aspect-ratio--3x4 {\n  padding-bottom: 133.33%;\n}\n.aspect-ratio--6x4 {\n  padding-bottom: 66.6%;\n}\n.aspect-ratio--4x6 {\n  padding-bottom: 150%;\n}\n.aspect-ratio--8x5 {\n  padding-bottom: 62.5%;\n}\n.aspect-ratio--5x8 {\n  padding-bottom: 160%;\n}\n.aspect-ratio--7x5 {\n  padding-bottom: 71.42%;\n}\n.aspect-ratio--5x7 {\n  padding-bottom: 140%;\n}\n.aspect-ratio--1x1 {\n  padding-bottom: 100%;\n}\n.aspect-ratio--object {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 100;\n}\n/*\n\n   IMAGES\n   Docs: http://tachyons.io/docs/elements/images/\n\n*/\n/* Responsive images! */\nimg {\n  max-width: 100%;\n}\n/*\n\n   BACKGROUND SIZE\n   Docs: http://tachyons.io/docs/themes/background-size/\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/*\n  Often used in combination with background image set as an inline style\n  on an html element.\n*/\n.cover {\n  background-size: cover !important;\n}\n.contain {\n  background-size: contain !important;\n}\n/*\n\n    BACKGROUND POSITION\n\n    Base:\n    bg = background\n\n    Modifiers:\n    -center = center center\n    -top = top center\n    -right = center right\n    -bottom = bottom center\n    -left = center left\n\n    Media Query Extensions:\n      -ns = not-small\n      -m  = medium\n      -l  = large\n\n */\n.bg-center {\n  background-repeat: no-repeat;\n  background-position: center center;\n}\n.bg-top {\n  background-repeat: no-repeat;\n  background-position: top center;\n}\n.bg-right {\n  background-repeat: no-repeat;\n  background-position: center right;\n}\n.bg-bottom {\n  background-repeat: no-repeat;\n  background-position: bottom center;\n}\n.bg-left {\n  background-repeat: no-repeat;\n  background-position: center left;\n}\n/*\n\n   OUTLINES\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.outline {\n  outline: 1px solid;\n}\n.outline-transparent {\n  outline: 1px solid transparent;\n}\n.outline-0 {\n  outline: 0;\n}\n/*\n\n    BORDERS\n    Docs: http://tachyons.io/docs/themes/borders/\n\n    Base:\n      b = border\n\n    Modifiers:\n      a = all\n      t = top\n      r = right\n      b = bottom\n      l = left\n      n = none\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.ba {\n  border-style: solid;\n  border-width: 1px;\n}\n.bt {\n  border-top-style: solid;\n  border-top-width: 1px;\n}\n.br {\n  border-right-style: solid;\n  border-right-width: 1px;\n}\n.bb {\n  border-bottom-style: solid;\n  border-bottom-width: 1px;\n}\n.bl {\n  border-left-style: solid;\n  border-left-width: 1px;\n}\n.bn {\n  border-style: none;\n  border-width: 0;\n}\n/*\n\n   BORDER COLORS\n   Docs: http://tachyons.io/docs/themes/borders/\n\n   Border colors can be used to extend the base\n   border classes ba,bt,bb,br,bl found in the _borders.css file.\n\n   The base border class by default will set the color of the border\n   to that of the current text color. These classes are for the cases\n   where you desire for the text and border colors to be different.\n\n   Base:\n     b = border\n\n   Modifiers:\n   --color-name = each color variable name is also a border color name\n\n*/\n.b--black {\n  border-color: #000;\n}\n.b--near-black {\n  border-color: #111;\n}\n.b--dark-gray {\n  border-color: #333;\n}\n.b--mid-gray {\n  border-color: #555;\n}\n.b--gray {\n  border-color: #777;\n}\n.b--silver {\n  border-color: #999;\n}\n.b--light-silver {\n  border-color: #aaa;\n}\n.b--moon-gray {\n  border-color: #ccc;\n}\n.b--light-gray {\n  border-color: #eee;\n}\n.b--near-white {\n  border-color: #f4f4f4;\n}\n.b--white {\n  border-color: #fff;\n}\n.b--white-90 {\n  border-color: rgba(255, 255, 255, 0.9);\n}\n.b--white-80 {\n  border-color: rgba(255, 255, 255, 0.8);\n}\n.b--white-70 {\n  border-color: rgba(255, 255, 255, 0.7);\n}\n.b--white-60 {\n  border-color: rgba(255, 255, 255, 0.6);\n}\n.b--white-50 {\n  border-color: rgba(255, 255, 255, 0.5);\n}\n.b--white-40 {\n  border-color: rgba(255, 255, 255, 0.4);\n}\n.b--white-30 {\n  border-color: rgba(255, 255, 255, 0.3);\n}\n.b--white-20 {\n  border-color: rgba(255, 255, 255, 0.2);\n}\n.b--white-10 {\n  border-color: rgba(255, 255, 255, 0.1);\n}\n.b--white-05 {\n  border-color: rgba(255, 255, 255, 0.05);\n}\n.b--white-025 {\n  border-color: rgba(255, 255, 255, 0.025);\n}\n.b--white-0125 {\n  border-color: rgba(255, 255, 255, 0.0125);\n}\n.b--black-90 {\n  border-color: rgba(0, 0, 0, 0.9);\n}\n.b--black-80 {\n  border-color: rgba(0, 0, 0, 0.8);\n}\n.b--black-70 {\n  border-color: rgba(0, 0, 0, 0.7);\n}\n.b--black-60 {\n  border-color: rgba(0, 0, 0, 0.6);\n}\n.b--black-50 {\n  border-color: rgba(0, 0, 0, 0.5);\n}\n.b--black-40 {\n  border-color: rgba(0, 0, 0, 0.4);\n}\n.b--black-30 {\n  border-color: rgba(0, 0, 0, 0.3);\n}\n.b--black-20 {\n  border-color: rgba(0, 0, 0, 0.2);\n}\n.b--black-10 {\n  border-color: rgba(0, 0, 0, 0.1);\n}\n.b--black-05 {\n  border-color: rgba(0, 0, 0, 0.05);\n}\n.b--black-025 {\n  border-color: rgba(0, 0, 0, 0.025);\n}\n.b--black-0125 {\n  border-color: rgba(0, 0, 0, 0.0125);\n}\n.b--dark-red {\n  border-color: #e7040f;\n}\n.b--red {\n  border-color: #ff4136;\n}\n.b--light-red {\n  border-color: #ff725c;\n}\n.b--orange {\n  border-color: #ff6300;\n}\n.b--gold {\n  border-color: #ffb700;\n}\n.b--yellow {\n  border-color: #ffd700;\n}\n.b--light-yellow {\n  border-color: #fbf1a9;\n}\n.b--purple {\n  border-color: #5e2ca5;\n}\n.b--light-purple {\n  border-color: #a463f2;\n}\n.b--dark-pink {\n  border-color: #d5008f;\n}\n.b--hot-pink {\n  border-color: #ff41b4;\n}\n.b--pink {\n  border-color: #ff80cc;\n}\n.b--light-pink {\n  border-color: #ffa3d7;\n}\n.b--dark-green {\n  border-color: #137752;\n}\n.b--green {\n  border-color: #19a974;\n}\n.b--light-green {\n  border-color: #9eebcf;\n}\n.b--navy {\n  border-color: #001b44;\n}\n.b--dark-blue {\n  border-color: #00449e;\n}\n.b--blue {\n  border-color: #357edd;\n}\n.b--light-blue {\n  border-color: #96ccff;\n}\n.b--lightest-blue {\n  border-color: #cdecff;\n}\n.b--washed-blue {\n  border-color: #f6fffe;\n}\n.b--washed-green {\n  border-color: #e8fdf5;\n}\n.b--washed-yellow {\n  border-color: #fffceb;\n}\n.b--washed-red {\n  border-color: #ffdfdf;\n}\n.b--transparent {\n  border-color: transparent;\n}\n.b--inherit {\n  border-color: inherit;\n}\n/*\n\n   BORDER RADIUS\n   Docs: http://tachyons.io/docs/themes/border-radius/\n\n   Base:\n     br   = border-radius\n\n   Modifiers:\n     0    = 0/none\n     1    = 1st step in scale\n     2    = 2nd step in scale\n     3    = 3rd step in scale\n     4    = 4th step in scale\n\n   Literal values:\n     -100 = 100%\n     -pill = 9999px\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.br0 {\n  border-radius: 0;\n}\n.br1 {\n  border-radius: 0.125rem;\n}\n.br2 {\n  border-radius: 0.25rem;\n}\n.br3 {\n  border-radius: 0.5rem;\n}\n.br4 {\n  border-radius: 1rem;\n}\n.br-100 {\n  border-radius: 100%;\n}\n.br-pill {\n  border-radius: 9999px;\n}\n.br--bottom {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.br--top {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.br--right {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.br--left {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n/*\n\n   BORDER STYLES\n   Docs: http://tachyons.io/docs/themes/borders/\n\n   Depends on base border module in _borders.css\n\n   Base:\n     b = border-style\n\n   Modifiers:\n     --none   = none\n     --dotted = dotted\n     --dashed = dashed\n     --solid  = solid\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n */\n.b--dotted {\n  border-style: dotted;\n}\n.b--dashed {\n  border-style: dashed;\n}\n.b--solid {\n  border-style: solid;\n}\n.b--none {\n  border-style: none;\n}\n/*\n\n   BORDER WIDTHS\n   Docs: http://tachyons.io/docs/themes/borders/\n\n   Base:\n     bw = border-width\n\n   Modifiers:\n     0 = 0 width border\n     1 = 1st step in border-width scale\n     2 = 2nd step in border-width scale\n     3 = 3rd step in border-width scale\n     4 = 4th step in border-width scale\n     5 = 5th step in border-width scale\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.bw0 {\n  border-width: 0;\n}\n.bw1 {\n  border-width: 0.125rem;\n}\n.bw2 {\n  border-width: 0.25rem;\n}\n.bw3 {\n  border-width: 0.5rem;\n}\n.bw4 {\n  border-width: 1rem;\n}\n.bw5 {\n  border-width: 2rem;\n}\n/* Resets */\n.bt-0 {\n  border-top-width: 0;\n}\n.br-0 {\n  border-right-width: 0;\n}\n.bb-0 {\n  border-bottom-width: 0;\n}\n.bl-0 {\n  border-left-width: 0;\n}\n/*\n\n  BOX-SHADOW\n  Docs: http://tachyons.io/docs/themes/box-shadow/\n\n  Media Query Extensions:\n   -ns = not-small\n   -m  = medium\n   -l  = large\n\n */\n.shadow-1 {\n  box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2);\n}\n.shadow-2 {\n  box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.2);\n}\n.shadow-3 {\n  box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, 0.2);\n}\n.shadow-4 {\n  box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, 0.2);\n}\n.shadow-5 {\n  box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.2);\n}\n/*\n\n   CODE\n\n*/\n.pre {\n  overflow-x: auto;\n  overflow-y: hidden;\n  overflow: scroll;\n}\n/*\n\n   COORDINATES\n   Docs: http://tachyons.io/docs/layout/position/\n\n   Use in combination with the position module.\n\n   Base:\n     top\n     bottom\n     right\n     left\n\n   Modifiers:\n     -0  = literal value 0\n     -1  = literal value 1\n     -2  = literal value 2\n     --1 = literal value -1\n     --2 = literal value -2\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.top-0 {\n  top: 0;\n}\n.right-0 {\n  right: 0;\n}\n.bottom-0 {\n  bottom: 0;\n}\n.left-0 {\n  left: 0;\n}\n.top-1 {\n  top: 1rem;\n}\n.right-1 {\n  right: 1rem;\n}\n.bottom-1 {\n  bottom: 1rem;\n}\n.left-1 {\n  left: 1rem;\n}\n.top-2 {\n  top: 2rem;\n}\n.right-2 {\n  right: 2rem;\n}\n.bottom-2 {\n  bottom: 2rem;\n}\n.left-2 {\n  left: 2rem;\n}\n.top--1 {\n  top: -1rem;\n}\n.right--1 {\n  right: -1rem;\n}\n.bottom--1 {\n  bottom: -1rem;\n}\n.left--1 {\n  left: -1rem;\n}\n.top--2 {\n  top: -2rem;\n}\n.right--2 {\n  right: -2rem;\n}\n.bottom--2 {\n  bottom: -2rem;\n}\n.left--2 {\n  left: -2rem;\n}\n.absolute--fill {\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n}\n/*\n\n   CLEARFIX\n   http://tachyons.io/docs/layout/clearfix/\n\n*/\n/* Nicolas Gallaghers Clearfix solution\n   Ref: http://nicolasgallagher.com/micro-clearfix-hack/ */\n.cf:before,\n.cf:after {\n  content: \" \";\n  display: table;\n}\n.cf:after {\n  clear: both;\n}\n.cf {\n  *zoom: 1;\n}\n.cl {\n  clear: left;\n}\n.cr {\n  clear: right;\n}\n.cb {\n  clear: both;\n}\n.cn {\n  clear: none;\n}\n/*\n\n   DISPLAY\n   Docs: http://tachyons.io/docs/layout/display\n\n   Base:\n    d = display\n\n   Modifiers:\n    n     = none\n    b     = block\n    ib    = inline-block\n    it    = inline-table\n    t     = table\n    tc    = table-cell\n    t-row          = table-row\n    t-columm       = table-column\n    t-column-group = table-column-group\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.dn {\n  display: none;\n}\n.di {\n  display: inline;\n}\n.db {\n  display: block;\n}\n.dib {\n  display: inline-block;\n}\n.dit {\n  display: inline-table;\n}\n.dt {\n  display: table;\n}\n.dtc {\n  display: table-cell;\n}\n.dt-row {\n  display: table-row;\n}\n.dt-row-group {\n  display: table-row-group;\n}\n.dt-column {\n  display: table-column;\n}\n.dt-column-group {\n  display: table-column-group;\n}\n/*\n  This will set table to full width and then\n  all cells will be equal width\n*/\n.dt--fixed {\n  table-layout: fixed;\n  width: 100%;\n}\n/*\n\n  FLEXBOX\n\n  Media Query Extensions:\n   -ns = not-small\n   -m  = medium\n   -l  = large\n\n*/\n.flex {\n  display: flex;\n}\n.inline-flex {\n  display: inline-flex;\n}\n/* 1. Fix for Chrome 44 bug.\n * https://code.google.com/p/chromium/issues/detail?id=506893 */\n.flex-auto {\n  flex: 1 1 auto;\n  min-width: 0; /* 1 */\n  min-height: 0; /* 1 */\n}\n.flex-none {\n  flex: none;\n}\n.flex-column {\n  flex-direction: column;\n}\n.flex-row {\n  flex-direction: row;\n}\n.flex-wrap {\n  flex-wrap: wrap;\n}\n.flex-nowrap {\n  flex-wrap: nowrap;\n}\n.flex-wrap-reverse {\n  flex-wrap: wrap-reverse;\n}\n.flex-column-reverse {\n  flex-direction: column-reverse;\n}\n.flex-row-reverse {\n  flex-direction: row-reverse;\n}\n.items-start {\n  align-items: flex-start;\n}\n.items-end {\n  align-items: flex-end;\n}\n.items-center {\n  align-items: center;\n}\n.items-baseline {\n  align-items: baseline;\n}\n.items-stretch {\n  align-items: stretch;\n}\n.self-start {\n  align-self: flex-start;\n}\n.self-end {\n  align-self: flex-end;\n}\n.self-center {\n  align-self: center;\n}\n.self-baseline {\n  align-self: baseline;\n}\n.self-stretch {\n  align-self: stretch;\n}\n.justify-start {\n  justify-content: flex-start;\n}\n.justify-end {\n  justify-content: flex-end;\n}\n.justify-center {\n  justify-content: center;\n}\n.justify-between {\n  justify-content: space-between;\n}\n.justify-around {\n  justify-content: space-around;\n}\n.content-start {\n  align-content: flex-start;\n}\n.content-end {\n  align-content: flex-end;\n}\n.content-center {\n  align-content: center;\n}\n.content-between {\n  align-content: space-between;\n}\n.content-around {\n  align-content: space-around;\n}\n.content-stretch {\n  align-content: stretch;\n}\n.order-0 {\n  order: 0;\n}\n.order-1 {\n  order: 1;\n}\n.order-2 {\n  order: 2;\n}\n.order-3 {\n  order: 3;\n}\n.order-4 {\n  order: 4;\n}\n.order-5 {\n  order: 5;\n}\n.order-6 {\n  order: 6;\n}\n.order-7 {\n  order: 7;\n}\n.order-8 {\n  order: 8;\n}\n.order-last {\n  order: 99999;\n}\n.flex-grow-0 {\n  flex-grow: 0;\n}\n.flex-grow-1 {\n  flex-grow: 1;\n}\n.flex-shrink-0 {\n  flex-shrink: 0;\n}\n.flex-shrink-1 {\n  flex-shrink: 1;\n}\n/*\n\n   FLOATS\n   http://tachyons.io/docs/layout/floats/\n\n   1. Floated elements are automatically rendered as block level elements.\n      Setting floats to display inline will fix the double margin bug in\n      ie6. You know... just in case.\n\n   2. Don't forget to clearfix your floats with .cf\n\n   Base:\n     f = float\n\n   Modifiers:\n     l = left\n     r = right\n     n = none\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.fl {\n  float: left;\n  _display: inline;\n}\n.fr {\n  float: right;\n  _display: inline;\n}\n.fn {\n  float: none;\n}\n/*\n\n   FONT FAMILY GROUPS\n   Docs: http://tachyons.io/docs/typography/font-family/\n\n*/\n.sans-serif {\n  font-family: -apple-system, BlinkMacSystemFont, \"avenir next\", avenir,\n    \"helvetica neue\", helvetica, ubuntu, roboto, noto, \"segoe ui\", arial,\n    sans-serif;\n}\n.serif {\n  font-family: georgia, times, serif;\n}\n.system-sans-serif {\n  font-family: sans-serif;\n}\n.system-serif {\n  font-family: serif;\n}\n/* Monospaced Typefaces (for code) */\n/* From http://cssfontstack.com */\ncode,\n.code {\n  font-family: Consolas, monaco, monospace;\n}\n.courier {\n  font-family: \"Courier Next\", courier, monospace;\n}\n/* Sans-Serif Typefaces */\n.helvetica {\n  font-family: \"helvetica neue\", helvetica, sans-serif;\n}\n.avenir {\n  font-family: \"avenir next\", avenir, sans-serif;\n}\n/* Serif Typefaces */\n.athelas {\n  font-family: athelas, georgia, serif;\n}\n.georgia {\n  font-family: georgia, serif;\n}\n.times {\n  font-family: times, serif;\n}\n.bodoni {\n  font-family: \"Bodoni MT\", serif;\n}\n.calisto {\n  font-family: \"Calisto MT\", serif;\n}\n.garamond {\n  font-family: garamond, serif;\n}\n.baskerville {\n  font-family: baskerville, serif;\n}\n/*\n\n   FONT STYLE\n   Docs: http://tachyons.io/docs/typography/font-style/\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.i {\n  font-style: italic;\n}\n.fs-normal {\n  font-style: normal;\n}\n/*\n\n   FONT WEIGHT\n   Docs: http://tachyons.io/docs/typography/font-weight/\n\n   Base\n     fw = font-weight\n\n   Modifiers:\n     1 = literal value 100\n     2 = literal value 200\n     3 = literal value 300\n     4 = literal value 400\n     5 = literal value 500\n     6 = literal value 600\n     7 = literal value 700\n     8 = literal value 800\n     9 = literal value 900\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.normal {\n  font-weight: normal;\n}\n.b {\n  font-weight: bold;\n}\n.fw1 {\n  font-weight: 100;\n}\n.fw2 {\n  font-weight: 200;\n}\n.fw3 {\n  font-weight: 300;\n}\n.fw4 {\n  font-weight: 400;\n}\n.fw5 {\n  font-weight: 500;\n}\n.fw6 {\n  font-weight: 600;\n}\n.fw7 {\n  font-weight: 700;\n}\n.fw8 {\n  font-weight: 800;\n}\n.fw9 {\n  font-weight: 900;\n}\n/*\n\n   FORMS\n\n*/\n.input-reset {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\n.button-reset::-moz-focus-inner,\n.input-reset::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\n/*\n\n   HEIGHTS\n   Docs: http://tachyons.io/docs/layout/heights/\n\n   Base:\n     h = height\n     min-h = min-height\n     min-vh = min-height vertical screen height\n     vh = vertical screen height\n\n   Modifiers\n     1 = 1st step in height scale\n     2 = 2nd step in height scale\n     3 = 3rd step in height scale\n     4 = 4th step in height scale\n     5 = 5th step in height scale\n\n     -25   = literal value 25%\n     -50   = literal value 50%\n     -75   = literal value 75%\n     -100  = literal value 100%\n\n     -auto = string value of auto\n     -inherit = string value of inherit\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/* Height Scale */\n.h1 {\n  height: 1rem;\n}\n.h2 {\n  height: 2rem;\n}\n.h3 {\n  height: 4rem;\n}\n.h4 {\n  height: 8rem;\n}\n.h5 {\n  height: 16rem;\n}\n/* Height Percentages - Based off of height of parent */\n.h-25 {\n  height: 25%;\n}\n.h-50 {\n  height: 50%;\n}\n.h-75 {\n  height: 75%;\n}\n.h-100 {\n  height: 100%;\n}\n.min-h-100 {\n  min-height: 100%;\n}\n/* Screen Height Percentage */\n.vh-25 {\n  height: 25vh;\n}\n.vh-50 {\n  height: 50vh;\n}\n.vh-75 {\n  height: 75vh;\n}\n.vh-100 {\n  height: 100vh;\n}\n.min-vh-100 {\n  min-height: 100vh;\n}\n/* String Properties */\n.h-auto {\n  height: auto;\n}\n.h-inherit {\n  height: inherit;\n}\n/*\n\n   LETTER SPACING\n   Docs: http://tachyons.io/docs/typography/tracking/\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.tracked {\n  letter-spacing: 0.1em;\n}\n.tracked-tight {\n  letter-spacing: -0.05em;\n}\n.tracked-mega {\n  letter-spacing: 0.25em;\n}\n/*\n\n   LINE HEIGHT / LEADING\n   Docs: http://tachyons.io/docs/typography/line-height\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.lh-solid {\n  line-height: 1;\n}\n.lh-title {\n  line-height: 1.25;\n}\n.lh-copy {\n  line-height: 1.5;\n}\n/*\n\n   LINKS\n   Docs: http://tachyons.io/docs/elements/links/\n\n*/\n.link {\n  text-decoration: none;\n  transition: color 0.15s ease-in;\n}\n.link:link,\n.link:visited {\n  transition: color 0.15s ease-in;\n}\n.link:hover {\n  transition: color 0.15s ease-in;\n}\n.link:active {\n  transition: color 0.15s ease-in;\n}\n.link:focus {\n  transition: color 0.15s ease-in;\n  outline: 1px dotted currentColor;\n}\n/*\n\n   LISTS\n   http://tachyons.io/docs/elements/lists/\n\n*/\n.list {\n  list-style-type: none;\n}\n/*\n\n   MAX WIDTHS\n   Docs: http://tachyons.io/docs/layout/max-widths/\n\n   Base:\n     mw = max-width\n\n   Modifiers\n     1 = 1st step in width scale\n     2 = 2nd step in width scale\n     3 = 3rd step in width scale\n     4 = 4th step in width scale\n     5 = 5th step in width scale\n     6 = 6st step in width scale\n     7 = 7nd step in width scale\n     8 = 8rd step in width scale\n     9 = 9th step in width scale\n\n     -100 = literal value 100%\n\n     -none  = string value none\n\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/* Max Width Percentages */\n.mw-100 {\n  max-width: 100%;\n}\n/* Max Width Scale */\n.mw1 {\n  max-width: 1rem;\n}\n.mw2 {\n  max-width: 2rem;\n}\n.mw3 {\n  max-width: 4rem;\n}\n.mw4 {\n  max-width: 8rem;\n}\n.mw5 {\n  max-width: 16rem;\n}\n.mw6 {\n  max-width: 32rem;\n}\n.mw7 {\n  max-width: 48rem;\n}\n.mw8 {\n  max-width: 64rem;\n}\n.mw9 {\n  max-width: 96rem;\n}\n/* Max Width String Properties */\n.mw-none {\n  max-width: none;\n}\n/*\n\n   WIDTHS\n   Docs: http://tachyons.io/docs/layout/widths/\n\n   Base:\n     w = width\n\n   Modifiers\n     1 = 1st step in width scale\n     2 = 2nd step in width scale\n     3 = 3rd step in width scale\n     4 = 4th step in width scale\n     5 = 5th step in width scale\n\n     -10  = literal value 10%\n     -20  = literal value 20%\n     -25  = literal value 25%\n     -30  = literal value 30%\n     -33  = literal value 33%\n     -34  = literal value 34%\n     -40  = literal value 40%\n     -50  = literal value 50%\n     -60  = literal value 60%\n     -70  = literal value 70%\n     -75  = literal value 75%\n     -80  = literal value 80%\n     -90  = literal value 90%\n     -100 = literal value 100%\n\n     -third      = 100% / 3 (Not supported in opera mini or IE8)\n     -two-thirds = 100% / 1.5 (Not supported in opera mini or IE8)\n     -auto       = string value auto\n\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/* Width Scale */\n.w1 {\n  width: 1rem;\n}\n.w2 {\n  width: 2rem;\n}\n.w3 {\n  width: 4rem;\n}\n.w4 {\n  width: 8rem;\n}\n.w5 {\n  width: 16rem;\n}\n.w-10 {\n  width: 10%;\n}\n.w-20 {\n  width: 20%;\n}\n.w-25 {\n  width: 25%;\n}\n.w-30 {\n  width: 30%;\n}\n.w-33 {\n  width: 33%;\n}\n.w-34 {\n  width: 34%;\n}\n.w-40 {\n  width: 40%;\n}\n.w-50 {\n  width: 50%;\n}\n.w-60 {\n  width: 60%;\n}\n.w-70 {\n  width: 70%;\n}\n.w-75 {\n  width: 75%;\n}\n.w-80 {\n  width: 80%;\n}\n.w-90 {\n  width: 90%;\n}\n.w-100 {\n  width: 100%;\n}\n.w-third {\n  width: calc(100% / 3);\n}\n.w-two-thirds {\n  width: calc(100% / 1.5);\n}\n.w-auto {\n  width: auto;\n}\n/*\n\n    OVERFLOW\n\n    Media Query Extensions:\n      -ns = not-small\n      -m  = medium\n      -l  = large\n\n */\n.overflow-visible {\n  overflow: visible;\n}\n.overflow-hidden {\n  overflow: hidden;\n}\n.overflow-scroll {\n  overflow: scroll;\n}\n.overflow-auto {\n  overflow: auto;\n}\n.overflow-x-visible {\n  overflow-x: visible;\n}\n.overflow-x-hidden {\n  overflow-x: hidden;\n}\n.overflow-x-scroll {\n  overflow-x: scroll;\n}\n.overflow-x-auto {\n  overflow-x: auto;\n}\n.overflow-y-visible {\n  overflow-y: visible;\n}\n.overflow-y-hidden {\n  overflow-y: hidden;\n}\n.overflow-y-scroll {\n  overflow-y: scroll;\n}\n.overflow-y-auto {\n  overflow-y: auto;\n}\n/*\n\n   POSITIONING\n   Docs: http://tachyons.io/docs/layout/position/\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.static {\n  position: static;\n}\n.relative {\n  position: relative;\n}\n.absolute {\n  position: absolute;\n}\n.fixed {\n  position: fixed;\n}\n/*\n\n    OPACITY\n    Docs: http://tachyons.io/docs/themes/opacity/\n\n*/\n.o-100 {\n  opacity: 1;\n}\n.o-90 {\n  opacity: 0.9;\n}\n.o-80 {\n  opacity: 0.8;\n}\n.o-70 {\n  opacity: 0.7;\n}\n.o-60 {\n  opacity: 0.6;\n}\n.o-50 {\n  opacity: 0.5;\n}\n.o-40 {\n  opacity: 0.4;\n}\n.o-30 {\n  opacity: 0.3;\n}\n.o-20 {\n  opacity: 0.2;\n}\n.o-10 {\n  opacity: 0.1;\n}\n.o-05 {\n  opacity: 0.05;\n}\n.o-025 {\n  opacity: 0.025;\n}\n.o-0 {\n  opacity: 0;\n}\n/*\n\n   ROTATIONS\n\n*/\n.rotate-45 {\n  -webkit-transform: rotate(45deg);\n  transform: rotate(45deg);\n}\n.rotate-90 {\n  -webkit-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n.rotate-135 {\n  -webkit-transform: rotate(135deg);\n  transform: rotate(135deg);\n}\n.rotate-180 {\n  -webkit-transform: rotate(180deg);\n  transform: rotate(180deg);\n}\n.rotate-225 {\n  -webkit-transform: rotate(225deg);\n  transform: rotate(225deg);\n}\n.rotate-270 {\n  -webkit-transform: rotate(270deg);\n  transform: rotate(270deg);\n}\n.rotate-315 {\n  -webkit-transform: rotate(315deg);\n  transform: rotate(315deg);\n}\n/*\n\n   SKINS\n   Docs: http://tachyons.io/docs/themes/skins/\n\n   Classes for setting foreground and background colors on elements.\n   If you haven't declared a border color, but set border on an element, it will\n   be set to the current text color.\n\n*/\n/* Text colors */\n.black-90 {\n  color: rgba(0, 0, 0, 0.9);\n}\n.black-80 {\n  color: rgba(0, 0, 0, 0.8);\n}\n.black-70 {\n  color: rgba(0, 0, 0, 0.7);\n}\n.black-60 {\n  color: rgba(0, 0, 0, 0.6);\n}\n.black-50 {\n  color: rgba(0, 0, 0, 0.5);\n}\n.black-40 {\n  color: rgba(0, 0, 0, 0.4);\n}\n.black-30 {\n  color: rgba(0, 0, 0, 0.3);\n}\n.black-20 {\n  color: rgba(0, 0, 0, 0.2);\n}\n.black-10 {\n  color: rgba(0, 0, 0, 0.1);\n}\n.black-05 {\n  color: rgba(0, 0, 0, 0.05);\n}\n.white-90 {\n  color: rgba(255, 255, 255, 0.9);\n}\n.white-80 {\n  color: rgba(255, 255, 255, 0.8);\n}\n.white-70 {\n  color: rgba(255, 255, 255, 0.7);\n}\n.white-60 {\n  color: rgba(255, 255, 255, 0.6);\n}\n.white-50 {\n  color: rgba(255, 255, 255, 0.5);\n}\n.white-40 {\n  color: rgba(255, 255, 255, 0.4);\n}\n.white-30 {\n  color: rgba(255, 255, 255, 0.3);\n}\n.white-20 {\n  color: rgba(255, 255, 255, 0.2);\n}\n.white-10 {\n  color: rgba(255, 255, 255, 0.1);\n}\n.black {\n  color: #000;\n}\n.near-black {\n  color: #111;\n}\n.dark-gray {\n  color: #333;\n}\n.mid-gray {\n  color: #555;\n}\n.gray {\n  color: #777;\n}\n.silver {\n  color: #999;\n}\n.light-silver {\n  color: #aaa;\n}\n.moon-gray {\n  color: #ccc;\n}\n.light-gray {\n  color: #eee;\n}\n.near-white {\n  color: #f4f4f4;\n}\n.white {\n  color: #fff;\n}\n.dark-red {\n  color: #e7040f;\n}\n.red {\n  color: #ff4136;\n}\n.light-red {\n  color: #ff725c;\n}\n.orange {\n  color: #ff6300;\n}\n.gold {\n  color: #ffb700;\n}\n.yellow {\n  color: #ffd700;\n}\n.light-yellow {\n  color: #fbf1a9;\n}\n.purple {\n  color: #5e2ca5;\n}\n.light-purple {\n  color: #a463f2;\n}\n.dark-pink {\n  color: #d5008f;\n}\n.hot-pink {\n  color: #ff41b4;\n}\n.pink {\n  color: #ff80cc;\n}\n.light-pink {\n  color: #ffa3d7;\n}\n.dark-green {\n  color: #137752;\n}\n.green {\n  color: #19a974;\n}\n.light-green {\n  color: #9eebcf;\n}\n.navy {\n  color: #001b44;\n}\n.dark-blue {\n  color: #00449e;\n}\n.blue {\n  color: #357edd;\n}\n.light-blue {\n  color: #96ccff;\n}\n.lightest-blue {\n  color: #cdecff;\n}\n.washed-blue {\n  color: #f6fffe;\n}\n.washed-green {\n  color: #e8fdf5;\n}\n.washed-yellow {\n  color: #fffceb;\n}\n.washed-red {\n  color: #ffdfdf;\n}\n.color-inherit {\n  color: inherit;\n}\n.bg-black-90 {\n  background-color: rgba(0, 0, 0, 0.9);\n}\n.bg-black-80 {\n  background-color: rgba(0, 0, 0, 0.8);\n}\n.bg-black-70 {\n  background-color: rgba(0, 0, 0, 0.7);\n}\n.bg-black-60 {\n  background-color: rgba(0, 0, 0, 0.6);\n}\n.bg-black-50 {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n.bg-black-40 {\n  background-color: rgba(0, 0, 0, 0.4);\n}\n.bg-black-30 {\n  background-color: rgba(0, 0, 0, 0.3);\n}\n.bg-black-20 {\n  background-color: rgba(0, 0, 0, 0.2);\n}\n.bg-black-10 {\n  background-color: rgba(0, 0, 0, 0.1);\n}\n.bg-black-05 {\n  background-color: rgba(0, 0, 0, 0.05);\n}\n.bg-white-90 {\n  background-color: rgba(255, 255, 255, 0.9);\n}\n.bg-white-80 {\n  background-color: rgba(255, 255, 255, 0.8);\n}\n.bg-white-70 {\n  background-color: rgba(255, 255, 255, 0.7);\n}\n.bg-white-60 {\n  background-color: rgba(255, 255, 255, 0.6);\n}\n.bg-white-50 {\n  background-color: rgba(255, 255, 255, 0.5);\n}\n.bg-white-40 {\n  background-color: rgba(255, 255, 255, 0.4);\n}\n.bg-white-30 {\n  background-color: rgba(255, 255, 255, 0.3);\n}\n.bg-white-20 {\n  background-color: rgba(255, 255, 255, 0.2);\n}\n.bg-white-10 {\n  background-color: rgba(255, 255, 255, 0.1);\n}\n/* Background colors */\n.bg-black {\n  background-color: #000;\n}\n.bg-near-black {\n  background-color: #111;\n}\n.bg-dark-gray {\n  background-color: #333;\n}\n.bg-mid-gray {\n  background-color: #555;\n}\n.bg-gray {\n  background-color: #777;\n}\n.bg-silver {\n  background-color: #999;\n}\n.bg-light-silver {\n  background-color: #aaa;\n}\n.bg-moon-gray {\n  background-color: #ccc;\n}\n.bg-light-gray {\n  background-color: #eee;\n}\n.bg-near-white {\n  background-color: #f4f4f4;\n}\n.bg-white {\n  background-color: #fff;\n}\n.bg-transparent {\n  background-color: transparent;\n}\n.bg-dark-red {\n  background-color: #e7040f;\n}\n.bg-red {\n  background-color: #ff4136;\n}\n.bg-light-red {\n  background-color: #ff725c;\n}\n.bg-orange {\n  background-color: #ff6300;\n}\n.bg-gold {\n  background-color: #ffb700;\n}\n.bg-yellow {\n  background-color: #ffd700;\n}\n.bg-light-yellow {\n  background-color: #fbf1a9;\n}\n.bg-purple {\n  background-color: #5e2ca5;\n}\n.bg-light-purple {\n  background-color: #a463f2;\n}\n.bg-dark-pink {\n  background-color: #d5008f;\n}\n.bg-hot-pink {\n  background-color: #ff41b4;\n}\n.bg-pink {\n  background-color: #ff80cc;\n}\n.bg-light-pink {\n  background-color: #ffa3d7;\n}\n.bg-dark-green {\n  background-color: #137752;\n}\n.bg-green {\n  background-color: #19a974;\n}\n.bg-light-green {\n  background-color: #9eebcf;\n}\n.bg-navy {\n  background-color: #001b44;\n}\n.bg-dark-blue {\n  background-color: #00449e;\n}\n.bg-blue {\n  background-color: #357edd;\n}\n.bg-light-blue {\n  background-color: #96ccff;\n}\n.bg-lightest-blue {\n  background-color: #cdecff;\n}\n.bg-washed-blue {\n  background-color: #f6fffe;\n}\n.bg-washed-green {\n  background-color: #e8fdf5;\n}\n.bg-washed-yellow {\n  background-color: #fffceb;\n}\n.bg-washed-red {\n  background-color: #ffdfdf;\n}\n.bg-inherit {\n  background-color: inherit;\n}\n/*\n\n   SKINS:PSEUDO\n\n   Customize the color of an element when\n   it is focused or hovered over.\n\n */\n.hover-black:hover {\n  color: #000;\n}\n.hover-black:focus {\n  color: #000;\n}\n.hover-near-black:hover {\n  color: #111;\n}\n.hover-near-black:focus {\n  color: #111;\n}\n.hover-dark-gray:hover {\n  color: #333;\n}\n.hover-dark-gray:focus {\n  color: #333;\n}\n.hover-mid-gray:hover {\n  color: #555;\n}\n.hover-mid-gray:focus {\n  color: #555;\n}\n.hover-gray:hover {\n  color: #777;\n}\n.hover-gray:focus {\n  color: #777;\n}\n.hover-silver:hover {\n  color: #999;\n}\n.hover-silver:focus {\n  color: #999;\n}\n.hover-light-silver:hover {\n  color: #aaa;\n}\n.hover-light-silver:focus {\n  color: #aaa;\n}\n.hover-moon-gray:hover {\n  color: #ccc;\n}\n.hover-moon-gray:focus {\n  color: #ccc;\n}\n.hover-light-gray:hover {\n  color: #eee;\n}\n.hover-light-gray:focus {\n  color: #eee;\n}\n.hover-near-white:hover {\n  color: #f4f4f4;\n}\n.hover-near-white:focus {\n  color: #f4f4f4;\n}\n.hover-white:hover {\n  color: #fff;\n}\n.hover-white:focus {\n  color: #fff;\n}\n.hover-black-90:hover {\n  color: rgba(0, 0, 0, 0.9);\n}\n.hover-black-90:focus {\n  color: rgba(0, 0, 0, 0.9);\n}\n.hover-black-80:hover {\n  color: rgba(0, 0, 0, 0.8);\n}\n.hover-black-80:focus {\n  color: rgba(0, 0, 0, 0.8);\n}\n.hover-black-70:hover {\n  color: rgba(0, 0, 0, 0.7);\n}\n.hover-black-70:focus {\n  color: rgba(0, 0, 0, 0.7);\n}\n.hover-black-60:hover {\n  color: rgba(0, 0, 0, 0.6);\n}\n.hover-black-60:focus {\n  color: rgba(0, 0, 0, 0.6);\n}\n.hover-black-50:hover {\n  color: rgba(0, 0, 0, 0.5);\n}\n.hover-black-50:focus {\n  color: rgba(0, 0, 0, 0.5);\n}\n.hover-black-40:hover {\n  color: rgba(0, 0, 0, 0.4);\n}\n.hover-black-40:focus {\n  color: rgba(0, 0, 0, 0.4);\n}\n.hover-black-30:hover {\n  color: rgba(0, 0, 0, 0.3);\n}\n.hover-black-30:focus {\n  color: rgba(0, 0, 0, 0.3);\n}\n.hover-black-20:hover {\n  color: rgba(0, 0, 0, 0.2);\n}\n.hover-black-20:focus {\n  color: rgba(0, 0, 0, 0.2);\n}\n.hover-black-10:hover {\n  color: rgba(0, 0, 0, 0.1);\n}\n.hover-black-10:focus {\n  color: rgba(0, 0, 0, 0.1);\n}\n.hover-white-90:hover {\n  color: rgba(255, 255, 255, 0.9);\n}\n.hover-white-90:focus {\n  color: rgba(255, 255, 255, 0.9);\n}\n.hover-white-80:hover {\n  color: rgba(255, 255, 255, 0.8);\n}\n.hover-white-80:focus {\n  color: rgba(255, 255, 255, 0.8);\n}\n.hover-white-70:hover {\n  color: rgba(255, 255, 255, 0.7);\n}\n.hover-white-70:focus {\n  color: rgba(255, 255, 255, 0.7);\n}\n.hover-white-60:hover {\n  color: rgba(255, 255, 255, 0.6);\n}\n.hover-white-60:focus {\n  color: rgba(255, 255, 255, 0.6);\n}\n.hover-white-50:hover {\n  color: rgba(255, 255, 255, 0.5);\n}\n.hover-white-50:focus {\n  color: rgba(255, 255, 255, 0.5);\n}\n.hover-white-40:hover {\n  color: rgba(255, 255, 255, 0.4);\n}\n.hover-white-40:focus {\n  color: rgba(255, 255, 255, 0.4);\n}\n.hover-white-30:hover {\n  color: rgba(255, 255, 255, 0.3);\n}\n.hover-white-30:focus {\n  color: rgba(255, 255, 255, 0.3);\n}\n.hover-white-20:hover {\n  color: rgba(255, 255, 255, 0.2);\n}\n.hover-white-20:focus {\n  color: rgba(255, 255, 255, 0.2);\n}\n.hover-white-10:hover {\n  color: rgba(255, 255, 255, 0.1);\n}\n.hover-white-10:focus {\n  color: rgba(255, 255, 255, 0.1);\n}\n.hover-inherit:hover,\n.hover-inherit:focus {\n  color: inherit;\n}\n.hover-bg-black:hover {\n  background-color: #000;\n}\n.hover-bg-black:focus {\n  background-color: #000;\n}\n.hover-bg-near-black:hover {\n  background-color: #111;\n}\n.hover-bg-near-black:focus {\n  background-color: #111;\n}\n.hover-bg-dark-gray:hover {\n  background-color: #333;\n}\n.hover-bg-dark-gray:focus {\n  background-color: #333;\n}\n.hover-bg-mid-gray:hover {\n  background-color: #555;\n}\n.hover-bg-mid-gray:focus {\n  background-color: #555;\n}\n.hover-bg-gray:hover {\n  background-color: #777;\n}\n.hover-bg-gray:focus {\n  background-color: #777;\n}\n.hover-bg-silver:hover {\n  background-color: #999;\n}\n.hover-bg-silver:focus {\n  background-color: #999;\n}\n.hover-bg-light-silver:hover {\n  background-color: #aaa;\n}\n.hover-bg-light-silver:focus {\n  background-color: #aaa;\n}\n.hover-bg-moon-gray:hover {\n  background-color: #ccc;\n}\n.hover-bg-moon-gray:focus {\n  background-color: #ccc;\n}\n.hover-bg-light-gray:hover {\n  background-color: #eee;\n}\n.hover-bg-light-gray:focus {\n  background-color: #eee;\n}\n.hover-bg-near-white:hover {\n  background-color: #f4f4f4;\n}\n.hover-bg-near-white:focus {\n  background-color: #f4f4f4;\n}\n.hover-bg-white:hover {\n  background-color: #fff;\n}\n.hover-bg-white:focus {\n  background-color: #fff;\n}\n.hover-bg-transparent:hover {\n  background-color: transparent;\n}\n.hover-bg-transparent:focus {\n  background-color: transparent;\n}\n.hover-bg-black-90:hover {\n  background-color: rgba(0, 0, 0, 0.9);\n}\n.hover-bg-black-90:focus {\n  background-color: rgba(0, 0, 0, 0.9);\n}\n.hover-bg-black-80:hover {\n  background-color: rgba(0, 0, 0, 0.8);\n}\n.hover-bg-black-80:focus {\n  background-color: rgba(0, 0, 0, 0.8);\n}\n.hover-bg-black-70:hover {\n  background-color: rgba(0, 0, 0, 0.7);\n}\n.hover-bg-black-70:focus {\n  background-color: rgba(0, 0, 0, 0.7);\n}\n.hover-bg-black-60:hover {\n  background-color: rgba(0, 0, 0, 0.6);\n}\n.hover-bg-black-60:focus {\n  background-color: rgba(0, 0, 0, 0.6);\n}\n.hover-bg-black-50:hover {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n.hover-bg-black-50:focus {\n  background-color: rgba(0, 0, 0, 0.5);\n}\n.hover-bg-black-40:hover {\n  background-color: rgba(0, 0, 0, 0.4);\n}\n.hover-bg-black-40:focus {\n  background-color: rgba(0, 0, 0, 0.4);\n}\n.hover-bg-black-30:hover {\n  background-color: rgba(0, 0, 0, 0.3);\n}\n.hover-bg-black-30:focus {\n  background-color: rgba(0, 0, 0, 0.3);\n}\n.hover-bg-black-20:hover {\n  background-color: rgba(0, 0, 0, 0.2);\n}\n.hover-bg-black-20:focus {\n  background-color: rgba(0, 0, 0, 0.2);\n}\n.hover-bg-black-10:hover {\n  background-color: rgba(0, 0, 0, 0.1);\n}\n.hover-bg-black-10:focus {\n  background-color: rgba(0, 0, 0, 0.1);\n}\n.hover-bg-white-90:hover {\n  background-color: rgba(255, 255, 255, 0.9);\n}\n.hover-bg-white-90:focus {\n  background-color: rgba(255, 255, 255, 0.9);\n}\n.hover-bg-white-80:hover {\n  background-color: rgba(255, 255, 255, 0.8);\n}\n.hover-bg-white-80:focus {\n  background-color: rgba(255, 255, 255, 0.8);\n}\n.hover-bg-white-70:hover {\n  background-color: rgba(255, 255, 255, 0.7);\n}\n.hover-bg-white-70:focus {\n  background-color: rgba(255, 255, 255, 0.7);\n}\n.hover-bg-white-60:hover {\n  background-color: rgba(255, 255, 255, 0.6);\n}\n.hover-bg-white-60:focus {\n  background-color: rgba(255, 255, 255, 0.6);\n}\n.hover-bg-white-50:hover {\n  background-color: rgba(255, 255, 255, 0.5);\n}\n.hover-bg-white-50:focus {\n  background-color: rgba(255, 255, 255, 0.5);\n}\n.hover-bg-white-40:hover {\n  background-color: rgba(255, 255, 255, 0.4);\n}\n.hover-bg-white-40:focus {\n  background-color: rgba(255, 255, 255, 0.4);\n}\n.hover-bg-white-30:hover {\n  background-color: rgba(255, 255, 255, 0.3);\n}\n.hover-bg-white-30:focus {\n  background-color: rgba(255, 255, 255, 0.3);\n}\n.hover-bg-white-20:hover {\n  background-color: rgba(255, 255, 255, 0.2);\n}\n.hover-bg-white-20:focus {\n  background-color: rgba(255, 255, 255, 0.2);\n}\n.hover-bg-white-10:hover {\n  background-color: rgba(255, 255, 255, 0.1);\n}\n.hover-bg-white-10:focus {\n  background-color: rgba(255, 255, 255, 0.1);\n}\n.hover-dark-red:hover {\n  color: #e7040f;\n}\n.hover-dark-red:focus {\n  color: #e7040f;\n}\n.hover-red:hover {\n  color: #ff4136;\n}\n.hover-red:focus {\n  color: #ff4136;\n}\n.hover-light-red:hover {\n  color: #ff725c;\n}\n.hover-light-red:focus {\n  color: #ff725c;\n}\n.hover-orange:hover {\n  color: #ff6300;\n}\n.hover-orange:focus {\n  color: #ff6300;\n}\n.hover-gold:hover {\n  color: #ffb700;\n}\n.hover-gold:focus {\n  color: #ffb700;\n}\n.hover-yellow:hover {\n  color: #ffd700;\n}\n.hover-yellow:focus {\n  color: #ffd700;\n}\n.hover-light-yellow:hover {\n  color: #fbf1a9;\n}\n.hover-light-yellow:focus {\n  color: #fbf1a9;\n}\n.hover-purple:hover {\n  color: #5e2ca5;\n}\n.hover-purple:focus {\n  color: #5e2ca5;\n}\n.hover-light-purple:hover {\n  color: #a463f2;\n}\n.hover-light-purple:focus {\n  color: #a463f2;\n}\n.hover-dark-pink:hover {\n  color: #d5008f;\n}\n.hover-dark-pink:focus {\n  color: #d5008f;\n}\n.hover-hot-pink:hover {\n  color: #ff41b4;\n}\n.hover-hot-pink:focus {\n  color: #ff41b4;\n}\n.hover-pink:hover {\n  color: #ff80cc;\n}\n.hover-pink:focus {\n  color: #ff80cc;\n}\n.hover-light-pink:hover {\n  color: #ffa3d7;\n}\n.hover-light-pink:focus {\n  color: #ffa3d7;\n}\n.hover-dark-green:hover {\n  color: #137752;\n}\n.hover-dark-green:focus {\n  color: #137752;\n}\n.hover-green:hover {\n  color: #19a974;\n}\n.hover-green:focus {\n  color: #19a974;\n}\n.hover-light-green:hover {\n  color: #9eebcf;\n}\n.hover-light-green:focus {\n  color: #9eebcf;\n}\n.hover-navy:hover {\n  color: #001b44;\n}\n.hover-navy:focus {\n  color: #001b44;\n}\n.hover-dark-blue:hover {\n  color: #00449e;\n}\n.hover-dark-blue:focus {\n  color: #00449e;\n}\n.hover-blue:hover {\n  color: #357edd;\n}\n.hover-blue:focus {\n  color: #357edd;\n}\n.hover-light-blue:hover {\n  color: #96ccff;\n}\n.hover-light-blue:focus {\n  color: #96ccff;\n}\n.hover-lightest-blue:hover {\n  color: #cdecff;\n}\n.hover-lightest-blue:focus {\n  color: #cdecff;\n}\n.hover-washed-blue:hover {\n  color: #f6fffe;\n}\n.hover-washed-blue:focus {\n  color: #f6fffe;\n}\n.hover-washed-green:hover {\n  color: #e8fdf5;\n}\n.hover-washed-green:focus {\n  color: #e8fdf5;\n}\n.hover-washed-yellow:hover {\n  color: #fffceb;\n}\n.hover-washed-yellow:focus {\n  color: #fffceb;\n}\n.hover-washed-red:hover {\n  color: #ffdfdf;\n}\n.hover-washed-red:focus {\n  color: #ffdfdf;\n}\n.hover-bg-dark-red:hover {\n  background-color: #e7040f;\n}\n.hover-bg-dark-red:focus {\n  background-color: #e7040f;\n}\n.hover-bg-red:hover {\n  background-color: #ff4136;\n}\n.hover-bg-red:focus {\n  background-color: #ff4136;\n}\n.hover-bg-light-red:hover {\n  background-color: #ff725c;\n}\n.hover-bg-light-red:focus {\n  background-color: #ff725c;\n}\n.hover-bg-orange:hover {\n  background-color: #ff6300;\n}\n.hover-bg-orange:focus {\n  background-color: #ff6300;\n}\n.hover-bg-gold:hover {\n  background-color: #ffb700;\n}\n.hover-bg-gold:focus {\n  background-color: #ffb700;\n}\n.hover-bg-yellow:hover {\n  background-color: #ffd700;\n}\n.hover-bg-yellow:focus {\n  background-color: #ffd700;\n}\n.hover-bg-light-yellow:hover {\n  background-color: #fbf1a9;\n}\n.hover-bg-light-yellow:focus {\n  background-color: #fbf1a9;\n}\n.hover-bg-purple:hover {\n  background-color: #5e2ca5;\n}\n.hover-bg-purple:focus {\n  background-color: #5e2ca5;\n}\n.hover-bg-light-purple:hover {\n  background-color: #a463f2;\n}\n.hover-bg-light-purple:focus {\n  background-color: #a463f2;\n}\n.hover-bg-dark-pink:hover {\n  background-color: #d5008f;\n}\n.hover-bg-dark-pink:focus {\n  background-color: #d5008f;\n}\n.hover-bg-hot-pink:hover {\n  background-color: #ff41b4;\n}\n.hover-bg-hot-pink:focus {\n  background-color: #ff41b4;\n}\n.hover-bg-pink:hover {\n  background-color: #ff80cc;\n}\n.hover-bg-pink:focus {\n  background-color: #ff80cc;\n}\n.hover-bg-light-pink:hover {\n  background-color: #ffa3d7;\n}\n.hover-bg-light-pink:focus {\n  background-color: #ffa3d7;\n}\n.hover-bg-dark-green:hover {\n  background-color: #137752;\n}\n.hover-bg-dark-green:focus {\n  background-color: #137752;\n}\n.hover-bg-green:hover {\n  background-color: #19a974;\n}\n.hover-bg-green:focus {\n  background-color: #19a974;\n}\n.hover-bg-light-green:hover {\n  background-color: #9eebcf;\n}\n.hover-bg-light-green:focus {\n  background-color: #9eebcf;\n}\n.hover-bg-navy:hover {\n  background-color: #001b44;\n}\n.hover-bg-navy:focus {\n  background-color: #001b44;\n}\n.hover-bg-dark-blue:hover {\n  background-color: #00449e;\n}\n.hover-bg-dark-blue:focus {\n  background-color: #00449e;\n}\n.hover-bg-blue:hover {\n  background-color: #357edd;\n}\n.hover-bg-blue:focus {\n  background-color: #357edd;\n}\n.hover-bg-light-blue:hover {\n  background-color: #96ccff;\n}\n.hover-bg-light-blue:focus {\n  background-color: #96ccff;\n}\n.hover-bg-lightest-blue:hover {\n  background-color: #cdecff;\n}\n.hover-bg-lightest-blue:focus {\n  background-color: #cdecff;\n}\n.hover-bg-washed-blue:hover {\n  background-color: #f6fffe;\n}\n.hover-bg-washed-blue:focus {\n  background-color: #f6fffe;\n}\n.hover-bg-washed-green:hover {\n  background-color: #e8fdf5;\n}\n.hover-bg-washed-green:focus {\n  background-color: #e8fdf5;\n}\n.hover-bg-washed-yellow:hover {\n  background-color: #fffceb;\n}\n.hover-bg-washed-yellow:focus {\n  background-color: #fffceb;\n}\n.hover-bg-washed-red:hover {\n  background-color: #ffdfdf;\n}\n.hover-bg-washed-red:focus {\n  background-color: #ffdfdf;\n}\n.hover-bg-inherit:hover,\n.hover-bg-inherit:focus {\n  background-color: inherit;\n}\n/* Variables */\n/*\n   SPACING\n   Docs: http://tachyons.io/docs/layout/spacing/\n\n   An eight step powers of two scale ranging from 0 to 16rem.\n\n   Base:\n     p = padding\n     m = margin\n\n   Modifiers:\n     a = all\n     h = horizontal\n     v = vertical\n     t = top\n     r = right\n     b = bottom\n     l = left\n\n     0 = none\n     1 = 1st step in spacing scale\n     2 = 2nd step in spacing scale\n     3 = 3rd step in spacing scale\n     4 = 4th step in spacing scale\n     5 = 5th step in spacing scale\n     6 = 6th step in spacing scale\n     7 = 7th step in spacing scale\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.pa0 {\n  padding: 0;\n}\n.pa1 {\n  padding: 0.25rem;\n}\n.pa2 {\n  padding: 0.5rem;\n}\n.pa3 {\n  padding: 1rem;\n}\n.pa4 {\n  padding: 2rem;\n}\n.pa5 {\n  padding: 4rem;\n}\n.pa6 {\n  padding: 8rem;\n}\n.pa7 {\n  padding: 16rem;\n}\n.pl0 {\n  padding-left: 0;\n}\n.pl1 {\n  padding-left: 0.25rem;\n}\n.pl2 {\n  padding-left: 0.5rem;\n}\n.pl3 {\n  padding-left: 1rem;\n}\n.pl4 {\n  padding-left: 2rem;\n}\n.pl5 {\n  padding-left: 4rem;\n}\n.pl6 {\n  padding-left: 8rem;\n}\n.pl7 {\n  padding-left: 16rem;\n}\n.pr0 {\n  padding-right: 0;\n}\n.pr1 {\n  padding-right: 0.25rem;\n}\n.pr2 {\n  padding-right: 0.5rem;\n}\n.pr3 {\n  padding-right: 1rem;\n}\n.pr4 {\n  padding-right: 2rem;\n}\n.pr5 {\n  padding-right: 4rem;\n}\n.pr6 {\n  padding-right: 8rem;\n}\n.pr7 {\n  padding-right: 16rem;\n}\n.pb0 {\n  padding-bottom: 0;\n}\n.pb1 {\n  padding-bottom: 0.25rem;\n}\n.pb2 {\n  padding-bottom: 0.5rem;\n}\n.pb3 {\n  padding-bottom: 1rem;\n}\n.pb4 {\n  padding-bottom: 2rem;\n}\n.pb5 {\n  padding-bottom: 4rem;\n}\n.pb6 {\n  padding-bottom: 8rem;\n}\n.pb7 {\n  padding-bottom: 16rem;\n}\n.pt0 {\n  padding-top: 0;\n}\n.pt1 {\n  padding-top: 0.25rem;\n}\n.pt2 {\n  padding-top: 0.5rem;\n}\n.pt3 {\n  padding-top: 1rem;\n}\n.pt4 {\n  padding-top: 2rem;\n}\n.pt5 {\n  padding-top: 4rem;\n}\n.pt6 {\n  padding-top: 8rem;\n}\n.pt7 {\n  padding-top: 16rem;\n}\n.pv0 {\n  padding-top: 0;\n  padding-bottom: 0;\n}\n.pv1 {\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n}\n.pv2 {\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n}\n.pv3 {\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n}\n.pv4 {\n  padding-top: 2rem;\n  padding-bottom: 2rem;\n}\n.pv5 {\n  padding-top: 4rem;\n  padding-bottom: 4rem;\n}\n.pv6 {\n  padding-top: 8rem;\n  padding-bottom: 8rem;\n}\n.pv7 {\n  padding-top: 16rem;\n  padding-bottom: 16rem;\n}\n.ph0 {\n  padding-left: 0;\n  padding-right: 0;\n}\n.ph1 {\n  padding-left: 0.25rem;\n  padding-right: 0.25rem;\n}\n.ph2 {\n  padding-left: 0.5rem;\n  padding-right: 0.5rem;\n}\n.ph3 {\n  padding-left: 1rem;\n  padding-right: 1rem;\n}\n.ph4 {\n  padding-left: 2rem;\n  padding-right: 2rem;\n}\n.ph5 {\n  padding-left: 4rem;\n  padding-right: 4rem;\n}\n.ph6 {\n  padding-left: 8rem;\n  padding-right: 8rem;\n}\n.ph7 {\n  padding-left: 16rem;\n  padding-right: 16rem;\n}\n.ma0 {\n  margin: 0;\n}\n.ma1 {\n  margin: 0.25rem;\n}\n.ma2 {\n  margin: 0.5rem;\n}\n.ma3 {\n  margin: 1rem;\n}\n.ma4 {\n  margin: 2rem;\n}\n.ma5 {\n  margin: 4rem;\n}\n.ma6 {\n  margin: 8rem;\n}\n.ma7 {\n  margin: 16rem;\n}\n.ml0 {\n  margin-left: 0;\n}\n.ml1 {\n  margin-left: 0.25rem;\n}\n.ml2 {\n  margin-left: 0.5rem;\n}\n.ml3 {\n  margin-left: 1rem;\n}\n.ml4 {\n  margin-left: 2rem;\n}\n.ml5 {\n  margin-left: 4rem;\n}\n.ml6 {\n  margin-left: 8rem;\n}\n.ml7 {\n  margin-left: 16rem;\n}\n.mr0 {\n  margin-right: 0;\n}\n.mr1 {\n  margin-right: 0.25rem;\n}\n.mr2 {\n  margin-right: 0.5rem;\n}\n.mr3 {\n  margin-right: 1rem;\n}\n.mr4 {\n  margin-right: 2rem;\n}\n.mr5 {\n  margin-right: 4rem;\n}\n.mr6 {\n  margin-right: 8rem;\n}\n.mr7 {\n  margin-right: 16rem;\n}\n.mb0 {\n  margin-bottom: 0;\n}\n.mb1 {\n  margin-bottom: 0.25rem;\n}\n.mb2 {\n  margin-bottom: 0.5rem;\n}\n.mb3 {\n  margin-bottom: 1rem;\n}\n.mb4 {\n  margin-bottom: 2rem;\n}\n.mb5 {\n  margin-bottom: 4rem;\n}\n.mb6 {\n  margin-bottom: 8rem;\n}\n.mb7 {\n  margin-bottom: 16rem;\n}\n.mt0 {\n  margin-top: 0;\n}\n.mt1 {\n  margin-top: 0.25rem;\n}\n.mt2 {\n  margin-top: 0.5rem;\n}\n.mt3 {\n  margin-top: 1rem;\n}\n.mt4 {\n  margin-top: 2rem;\n}\n.mt5 {\n  margin-top: 4rem;\n}\n.mt6 {\n  margin-top: 8rem;\n}\n.mt7 {\n  margin-top: 16rem;\n}\n.mv0 {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.mv1 {\n  margin-top: 0.25rem;\n  margin-bottom: 0.25rem;\n}\n.mv2 {\n  margin-top: 0.5rem;\n  margin-bottom: 0.5rem;\n}\n.mv3 {\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n}\n.mv4 {\n  margin-top: 2rem;\n  margin-bottom: 2rem;\n}\n.mv5 {\n  margin-top: 4rem;\n  margin-bottom: 4rem;\n}\n.mv6 {\n  margin-top: 8rem;\n  margin-bottom: 8rem;\n}\n.mv7 {\n  margin-top: 16rem;\n  margin-bottom: 16rem;\n}\n.mh0 {\n  margin-left: 0;\n  margin-right: 0;\n}\n.mh1 {\n  margin-left: 0.25rem;\n  margin-right: 0.25rem;\n}\n.mh2 {\n  margin-left: 0.5rem;\n  margin-right: 0.5rem;\n}\n.mh3 {\n  margin-left: 1rem;\n  margin-right: 1rem;\n}\n.mh4 {\n  margin-left: 2rem;\n  margin-right: 2rem;\n}\n.mh5 {\n  margin-left: 4rem;\n  margin-right: 4rem;\n}\n.mh6 {\n  margin-left: 8rem;\n  margin-right: 8rem;\n}\n.mh7 {\n  margin-left: 16rem;\n  margin-right: 16rem;\n}\n/*\n   NEGATIVE MARGINS\n\n   Base:\n     n = negative\n\n   Modifiers:\n     a = all\n     t = top\n     r = right\n     b = bottom\n     l = left\n\n     1 = 1st step in spacing scale\n     2 = 2nd step in spacing scale\n     3 = 3rd step in spacing scale\n     4 = 4th step in spacing scale\n     5 = 5th step in spacing scale\n     6 = 6th step in spacing scale\n     7 = 7th step in spacing scale\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.na1 {\n  margin: -0.25rem;\n}\n.na2 {\n  margin: -0.5rem;\n}\n.na3 {\n  margin: -1rem;\n}\n.na4 {\n  margin: -2rem;\n}\n.na5 {\n  margin: -4rem;\n}\n.na6 {\n  margin: -8rem;\n}\n.na7 {\n  margin: -16rem;\n}\n.nl1 {\n  margin-left: -0.25rem;\n}\n.nl2 {\n  margin-left: -0.5rem;\n}\n.nl3 {\n  margin-left: -1rem;\n}\n.nl4 {\n  margin-left: -2rem;\n}\n.nl5 {\n  margin-left: -4rem;\n}\n.nl6 {\n  margin-left: -8rem;\n}\n.nl7 {\n  margin-left: -16rem;\n}\n.nr1 {\n  margin-right: -0.25rem;\n}\n.nr2 {\n  margin-right: -0.5rem;\n}\n.nr3 {\n  margin-right: -1rem;\n}\n.nr4 {\n  margin-right: -2rem;\n}\n.nr5 {\n  margin-right: -4rem;\n}\n.nr6 {\n  margin-right: -8rem;\n}\n.nr7 {\n  margin-right: -16rem;\n}\n.nb1 {\n  margin-bottom: -0.25rem;\n}\n.nb2 {\n  margin-bottom: -0.5rem;\n}\n.nb3 {\n  margin-bottom: -1rem;\n}\n.nb4 {\n  margin-bottom: -2rem;\n}\n.nb5 {\n  margin-bottom: -4rem;\n}\n.nb6 {\n  margin-bottom: -8rem;\n}\n.nb7 {\n  margin-bottom: -16rem;\n}\n.nt1 {\n  margin-top: -0.25rem;\n}\n.nt2 {\n  margin-top: -0.5rem;\n}\n.nt3 {\n  margin-top: -1rem;\n}\n.nt4 {\n  margin-top: -2rem;\n}\n.nt5 {\n  margin-top: -4rem;\n}\n.nt6 {\n  margin-top: -8rem;\n}\n.nt7 {\n  margin-top: -16rem;\n}\n/*\n\n  TABLES\n  Docs: http://tachyons.io/docs/elements/tables/\n\n*/\n.collapse {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n.striped--light-silver:nth-child(odd) {\n  background-color: #aaa;\n}\n.striped--moon-gray:nth-child(odd) {\n  background-color: #ccc;\n}\n.striped--light-gray:nth-child(odd) {\n  background-color: #eee;\n}\n.striped--near-white:nth-child(odd) {\n  background-color: #f4f4f4;\n}\n.stripe-light:nth-child(odd) {\n  background-color: rgba(255, 255, 255, 0.1);\n}\n.stripe-dark:nth-child(odd) {\n  background-color: rgba(0, 0, 0, 0.1);\n}\n/*\n\n   TEXT DECORATION\n   Docs: http://tachyons.io/docs/typography/text-decoration/\n\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.strike {\n  text-decoration: line-through;\n}\n.underline {\n  text-decoration: underline;\n}\n.no-underline {\n  text-decoration: none;\n}\n/*\n\n  TEXT ALIGN\n  Docs: http://tachyons.io/docs/typography/text-align/\n\n  Base\n    t = text-align\n\n  Modifiers\n    l = left\n    r = right\n    c = center\n    j = justify\n\n  Media Query Extensions:\n    -ns = not-small\n    -m  = medium\n    -l  = large\n\n*/\n.tl {\n  text-align: left;\n}\n.tr {\n  text-align: right;\n}\n.tc {\n  text-align: center;\n}\n.tj {\n  text-align: justify;\n}\n/*\n\n   TEXT TRANSFORM\n   Docs: http://tachyons.io/docs/typography/text-transform/\n\n   Base:\n     tt = text-transform\n\n   Modifiers\n     c = capitalize\n     l = lowercase\n     u = uppercase\n     n = none\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.ttc {\n  text-transform: capitalize;\n}\n.ttl {\n  text-transform: lowercase;\n}\n.ttu {\n  text-transform: uppercase;\n}\n.ttn {\n  text-transform: none;\n}\n/*\n\n   TYPE SCALE\n   Docs: http://tachyons.io/docs/typography/scale/\n\n   Base:\n    f = font-size\n\n   Modifiers\n     1 = 1st step in size scale\n     2 = 2nd step in size scale\n     3 = 3rd step in size scale\n     4 = 4th step in size scale\n     5 = 5th step in size scale\n     6 = 6th step in size scale\n     7 = 7th step in size scale\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n*/\n/*\n * For Hero/Marketing Titles\n *\n * These generally are too large for mobile\n * so be careful using them on smaller screens.\n * */\n.f-6,\n.f-headline {\n  font-size: 6rem;\n}\n.f-5,\n.f-subheadline {\n  font-size: 5rem;\n}\n/* Type Scale */\n.f1 {\n  font-size: 3rem;\n}\n.f2 {\n  font-size: 2.25rem;\n}\n.f3 {\n  font-size: 1.5rem;\n}\n.f4 {\n  font-size: 1.25rem;\n}\n.f5 {\n  font-size: 1rem;\n}\n.f6 {\n  font-size: 0.875rem;\n}\n.f7 {\n  font-size: 0.75rem;\n}\n/* Small and hard to read for many people so use with extreme caution */\n/*\n\n   TYPOGRAPHY\n   http://tachyons.io/docs/typography/measure/\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/* Measure is limited to ~66 characters */\n.measure {\n  max-width: 30em;\n}\n/* Measure is limited to ~80 characters */\n.measure-wide {\n  max-width: 34em;\n}\n/* Measure is limited to ~45 characters */\n.measure-narrow {\n  max-width: 20em;\n}\n/* Book paragraph style - paragraphs are indented with no vertical spacing. */\n.indent {\n  text-indent: 1em;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.small-caps {\n  font-variant: small-caps;\n}\n/* Combine this class with a width to truncate text (or just leave as is to truncate at width of containing element. */\n.truncate {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n/*\n\n   UTILITIES\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/* Equivalent to .overflow-y-scroll */\n.overflow-container {\n  overflow-y: scroll;\n}\n.center {\n  margin-right: auto;\n  margin-left: auto;\n}\n.mr-auto {\n  margin-right: auto;\n}\n.ml-auto {\n  margin-left: auto;\n}\n/*\n\n   VISIBILITY\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n/*\n    Text that is hidden but accessible\n    Ref: http://snook.ca/archives/html_and_css/hiding-content-for-accessibility\n*/\n.clip {\n  position: fixed !important;\n  _position: absolute !important;\n  clip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n  clip: rect(1px, 1px, 1px, 1px);\n}\n/*\n\n   WHITE SPACE\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.ws-normal {\n  white-space: normal;\n}\n.nowrap {\n  white-space: nowrap;\n}\n.pre {\n  white-space: pre;\n}\n/*\n\n   VERTICAL ALIGN\n\n   Media Query Extensions:\n     -ns = not-small\n     -m  = medium\n     -l  = large\n\n*/\n.v-base {\n  vertical-align: baseline;\n}\n.v-mid {\n  vertical-align: middle;\n}\n.v-top {\n  vertical-align: top;\n}\n.v-btm {\n  vertical-align: bottom;\n}\n/*\n\n  HOVER EFFECTS\n  Docs: http://tachyons.io/docs/themes/hovers/\n\n    - Dim\n    - Glow\n    - Hide Child\n    - Underline text\n    - Grow\n    - Pointer\n    - Shadow\n\n*/\n/*\n\n  Dim element on hover by adding the dim class.\n\n*/\n.dim {\n  opacity: 1;\n  transition: opacity 0.15s ease-in;\n}\n.dim:hover,\n.dim:focus {\n  opacity: 0.5;\n  transition: opacity 0.15s ease-in;\n}\n.dim:active {\n  opacity: 0.8;\n  transition: opacity 0.15s ease-out;\n}\n/*\n\n  Animate opacity to 100% on hover by adding the glow class.\n\n*/\n.glow {\n  transition: opacity 0.15s ease-in;\n}\n.glow:hover,\n.glow:focus {\n  opacity: 1;\n  transition: opacity 0.15s ease-in;\n}\n/*\n\n  Hide child & reveal on hover:\n\n  Put the hide-child class on a parent element and any nested element with the\n  child class will be hidden and displayed on hover or focus.\n\n  <div class=\"hide-child\">\n    <div class=\"child\"> Hidden until hover or focus </div>\n    <div class=\"child\"> Hidden until hover or focus </div>\n    <div class=\"child\"> Hidden until hover or focus </div>\n    <div class=\"child\"> Hidden until hover or focus </div>\n  </div>\n*/\n.hide-child .child {\n  opacity: 0;\n  transition: opacity 0.15s ease-in;\n}\n.hide-child:hover .child,\n.hide-child:focus .child,\n.hide-child:active .child {\n  opacity: 1;\n  transition: opacity 0.15s ease-in;\n}\n.underline-hover:hover,\n.underline-hover:focus {\n  text-decoration: underline;\n}\n/* Can combine this with overflow-hidden to make background images grow on hover\n * even if you are using background-size: cover */\n.grow {\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-transform: translateZ(0);\n  transform: translateZ(0);\n  transition: -webkit-transform 0.25s ease-out;\n  transition: transform 0.25s ease-out;\n  transition: transform 0.25s ease-out, -webkit-transform 0.25s ease-out;\n}\n.grow:hover,\n.grow:focus {\n  -webkit-transform: scale(1.05);\n  transform: scale(1.05);\n}\n.grow:active {\n  -webkit-transform: scale(0.9);\n  transform: scale(0.9);\n}\n.grow-large {\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n  -webkit-transform: translateZ(0);\n  transform: translateZ(0);\n  transition: -webkit-transform 0.25s ease-in-out;\n  transition: transform 0.25s ease-in-out;\n  transition: transform 0.25s ease-in-out, -webkit-transform 0.25s ease-in-out;\n}\n.grow-large:hover,\n.grow-large:focus {\n  -webkit-transform: scale(1.2);\n  transform: scale(1.2);\n}\n.grow-large:active {\n  -webkit-transform: scale(0.95);\n  transform: scale(0.95);\n}\n/* Add pointer on hover */\n.pointer:hover {\n  cursor: pointer;\n}\n/*\n   Add shadow on hover.\n\n   Performant box-shadow animation pattern from\n   http://tobiasahlin.com/blog/how-to-animate-box-shadow/\n*/\n.shadow-hover {\n  cursor: pointer;\n  position: relative;\n  transition: all 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);\n}\n.shadow-hover::after {\n  content: \"\";\n  box-shadow: 0 0 16px 2px rgba(0, 0, 0, 0.2);\n  border-radius: inherit;\n  opacity: 0;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: -1;\n  transition: opacity 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);\n}\n.shadow-hover:hover::after,\n.shadow-hover:focus::after {\n  opacity: 1;\n}\n/* Combine with classes in skins and skins-pseudo for\n * many different transition possibilities. */\n.bg-animate,\n.bg-animate:hover,\n.bg-animate:focus {\n  transition: background-color 0.15s ease-in-out;\n}\n/*\n\n  Z-INDEX\n\n  Base\n    z = z-index\n\n  Modifiers\n    -0 = literal value 0\n    -1 = literal value 1\n    -2 = literal value 2\n    -3 = literal value 3\n    -4 = literal value 4\n    -5 = literal value 5\n    -999 = literal value 999\n    -9999 = literal value 9999\n\n    -max = largest accepted z-index value as integer\n\n    -inherit = string value inherit\n    -initial = string value initial\n    -unset = string value unset\n\n  MDN: https://developer.mozilla.org/en/docs/Web/CSS/z-index\n  Spec: http://www.w3.org/TR/CSS2/zindex.html\n  Articles:\n    https://philipwalton.com/articles/what-no-one-told-you-about-z-index/\n\n  Tips on extending:\n  There might be a time worth using negative z-index values.\n  Or if you are using tachyons with another project, you might need to\n  adjust these values to suit your needs.\n\n*/\n.z-0 {\n  z-index: 0;\n}\n.z-1 {\n  z-index: 1;\n}\n.z-2 {\n  z-index: 2;\n}\n.z-3 {\n  z-index: 3;\n}\n.z-4 {\n  z-index: 4;\n}\n.z-5 {\n  z-index: 5;\n}\n.z-999 {\n  z-index: 999;\n}\n.z-9999 {\n  z-index: 9999;\n}\n.z-max {\n  z-index: 2147483647;\n}\n.z-inherit {\n  z-index: inherit;\n}\n.z-initial {\n  z-index: initial;\n}\n.z-unset {\n  z-index: unset;\n}\n/*\n\n    NESTED\n    Tachyons module for styling nested elements\n    that are generated by a cms.\n\n*/\n.nested-copy-line-height p,\n.nested-copy-line-height ul,\n.nested-copy-line-height ol {\n  line-height: 1.5;\n}\n.nested-headline-line-height h1,\n.nested-headline-line-height h2,\n.nested-headline-line-height h3,\n.nested-headline-line-height h4,\n.nested-headline-line-height h5,\n.nested-headline-line-height h6 {\n  line-height: 1.25;\n}\n.nested-list-reset ul,\n.nested-list-reset ol {\n  padding-left: 0;\n  margin-left: 0;\n  list-style-type: none;\n}\n.nested-copy-indent p + p {\n  text-indent: 1em;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.nested-copy-separator p + p {\n  margin-top: 1.5em;\n}\n.nested-img img {\n  width: 100%;\n  max-width: 100%;\n  display: block;\n}\n.nested-links a {\n  color: #357edd;\n  transition: color 0.15s ease-in;\n}\n.nested-links a:hover {\n  color: #96ccff;\n  transition: color 0.15s ease-in;\n}\n.nested-links a:focus {\n  color: #96ccff;\n  transition: color 0.15s ease-in;\n}\n/*\n\n  STYLES\n\n  Add custom styles here.\n\n*/\n/* Variables */\n/* Importing here will allow you to override any variables in the modules */\n/*\n\n   Tachyons\n   COLOR VARIABLES\n\n   Grayscale\n   - Solids\n   - Transparencies\n   Colors\n\n*/\n/*\n\n  CUSTOM MEDIA QUERIES\n\n  Media query values can be changed to fit your own content.\n  There are no magic bullets when it comes to media query width values.\n  They should be declared in em units - and they should be set to meet\n  the needs of your content. You can also add additional media queries,\n  or remove some of the existing ones.\n\n  These media queries can be referenced like so:\n\n  @media (--breakpoint-not-small) {\n    .medium-and-larger-specific-style {\n      background-color: red;\n    }\n  }\n\n  @media (--breakpoint-medium) {\n    .medium-screen-specific-style {\n      background-color: red;\n    }\n  }\n\n  @media (--breakpoint-large) {\n    .large-and-larger-screen-specific-style {\n      background-color: red;\n    }\n  }\n\n*/\n/* Media Queries */\n/* Debugging */\n/*\n\n  DEBUG CHILDREN\n  Docs: http://tachyons.io/docs/debug/\n\n  Just add the debug class to any element to see outlines on its\n  children.\n\n*/\n.debug * {\n  outline: 1px solid gold;\n}\n.debug-white * {\n  outline: 1px solid white;\n}\n.debug-black * {\n  outline: 1px solid black;\n}\n/*\n\n   DEBUG GRID\n   http://tachyons.io/docs/debug-grid/\n\n   Can be useful for debugging layout issues\n   or helping to make sure things line up perfectly.\n   Just tack one of these classes onto a parent element.\n\n*/\n.debug-grid {\n  background: transparent\n    url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII= )\n    repeat top left;\n}\n.debug-grid-16 {\n  background: transparent\n    url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII= )\n    repeat top left;\n}\n.debug-grid-8-solid {\n  background: white\n    url( data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw== )\n    repeat top left;\n}\n.debug-grid-16-solid {\n  background: white\n    url( data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7 )\n    repeat top left;\n}\n/* Uncomment out the line below to help debug layout issues */\n/* @import './_debug'; */\n@media screen and (min-width: 30em) {\n  .aspect-ratio-ns {\n    height: 0;\n    position: relative;\n  }\n  .aspect-ratio--16x9-ns {\n    padding-bottom: 56.25%;\n  }\n  .aspect-ratio--9x16-ns {\n    padding-bottom: 177.77%;\n  }\n  .aspect-ratio--4x3-ns {\n    padding-bottom: 75%;\n  }\n  .aspect-ratio--3x4-ns {\n    padding-bottom: 133.33%;\n  }\n  .aspect-ratio--6x4-ns {\n    padding-bottom: 66.6%;\n  }\n  .aspect-ratio--4x6-ns {\n    padding-bottom: 150%;\n  }\n  .aspect-ratio--8x5-ns {\n    padding-bottom: 62.5%;\n  }\n  .aspect-ratio--5x8-ns {\n    padding-bottom: 160%;\n  }\n  .aspect-ratio--7x5-ns {\n    padding-bottom: 71.42%;\n  }\n  .aspect-ratio--5x7-ns {\n    padding-bottom: 140%;\n  }\n  .aspect-ratio--1x1-ns {\n    padding-bottom: 100%;\n  }\n  .aspect-ratio--object-ns {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    z-index: 100;\n  }\n  .cover-ns {\n    background-size: cover !important;\n  }\n  .contain-ns {\n    background-size: contain !important;\n  }\n  .bg-center-ns {\n    background-repeat: no-repeat;\n    background-position: center center;\n  }\n  .bg-top-ns {\n    background-repeat: no-repeat;\n    background-position: top center;\n  }\n  .bg-right-ns {\n    background-repeat: no-repeat;\n    background-position: center right;\n  }\n  .bg-bottom-ns {\n    background-repeat: no-repeat;\n    background-position: bottom center;\n  }\n  .bg-left-ns {\n    background-repeat: no-repeat;\n    background-position: center left;\n  }\n  .outline-ns {\n    outline: 1px solid;\n  }\n  .outline-transparent-ns {\n    outline: 1px solid transparent;\n  }\n  .outline-0-ns {\n    outline: 0;\n  }\n  .ba-ns {\n    border-style: solid;\n    border-width: 1px;\n  }\n  .bt-ns {\n    border-top-style: solid;\n    border-top-width: 1px;\n  }\n  .br-ns {\n    border-right-style: solid;\n    border-right-width: 1px;\n  }\n  .bb-ns {\n    border-bottom-style: solid;\n    border-bottom-width: 1px;\n  }\n  .bl-ns {\n    border-left-style: solid;\n    border-left-width: 1px;\n  }\n  .bn-ns {\n    border-style: none;\n    border-width: 0;\n  }\n  .br0-ns {\n    border-radius: 0;\n  }\n  .br1-ns {\n    border-radius: 0.125rem;\n  }\n  .br2-ns {\n    border-radius: 0.25rem;\n  }\n  .br3-ns {\n    border-radius: 0.5rem;\n  }\n  .br4-ns {\n    border-radius: 1rem;\n  }\n  .br-100-ns {\n    border-radius: 100%;\n  }\n  .br-pill-ns {\n    border-radius: 9999px;\n  }\n  .br--bottom-ns {\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n  }\n  .br--top-ns {\n    border-bottom-left-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .br--right-ns {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n  .br--left-ns {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .b--dotted-ns {\n    border-style: dotted;\n  }\n  .b--dashed-ns {\n    border-style: dashed;\n  }\n  .b--solid-ns {\n    border-style: solid;\n  }\n  .b--none-ns {\n    border-style: none;\n  }\n  .bw0-ns {\n    border-width: 0;\n  }\n  .bw1-ns {\n    border-width: 0.125rem;\n  }\n  .bw2-ns {\n    border-width: 0.25rem;\n  }\n  .bw3-ns {\n    border-width: 0.5rem;\n  }\n  .bw4-ns {\n    border-width: 1rem;\n  }\n  .bw5-ns {\n    border-width: 2rem;\n  }\n  .bt-0-ns {\n    border-top-width: 0;\n  }\n  .br-0-ns {\n    border-right-width: 0;\n  }\n  .bb-0-ns {\n    border-bottom-width: 0;\n  }\n  .bl-0-ns {\n    border-left-width: 0;\n  }\n  .shadow-1-ns {\n    box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-2-ns {\n    box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-3-ns {\n    box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-4-ns {\n    box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .shadow-5-ns {\n    box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .top-0-ns {\n    top: 0;\n  }\n  .left-0-ns {\n    left: 0;\n  }\n  .right-0-ns {\n    right: 0;\n  }\n  .bottom-0-ns {\n    bottom: 0;\n  }\n  .top-1-ns {\n    top: 1rem;\n  }\n  .left-1-ns {\n    left: 1rem;\n  }\n  .right-1-ns {\n    right: 1rem;\n  }\n  .bottom-1-ns {\n    bottom: 1rem;\n  }\n  .top-2-ns {\n    top: 2rem;\n  }\n  .left-2-ns {\n    left: 2rem;\n  }\n  .right-2-ns {\n    right: 2rem;\n  }\n  .bottom-2-ns {\n    bottom: 2rem;\n  }\n  .top--1-ns {\n    top: -1rem;\n  }\n  .right--1-ns {\n    right: -1rem;\n  }\n  .bottom--1-ns {\n    bottom: -1rem;\n  }\n  .left--1-ns {\n    left: -1rem;\n  }\n  .top--2-ns {\n    top: -2rem;\n  }\n  .right--2-ns {\n    right: -2rem;\n  }\n  .bottom--2-ns {\n    bottom: -2rem;\n  }\n  .left--2-ns {\n    left: -2rem;\n  }\n  .absolute--fill-ns {\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n  }\n  .cl-ns {\n    clear: left;\n  }\n  .cr-ns {\n    clear: right;\n  }\n  .cb-ns {\n    clear: both;\n  }\n  .cn-ns {\n    clear: none;\n  }\n  .dn-ns {\n    display: none;\n  }\n  .di-ns {\n    display: inline;\n  }\n  .db-ns {\n    display: block;\n  }\n  .dib-ns {\n    display: inline-block;\n  }\n  .dit-ns {\n    display: inline-table;\n  }\n  .dt-ns {\n    display: table;\n  }\n  .dtc-ns {\n    display: table-cell;\n  }\n  .dt-row-ns {\n    display: table-row;\n  }\n  .dt-row-group-ns {\n    display: table-row-group;\n  }\n  .dt-column-ns {\n    display: table-column;\n  }\n  .dt-column-group-ns {\n    display: table-column-group;\n  }\n  .dt--fixed-ns {\n    table-layout: fixed;\n    width: 100%;\n  }\n  .flex-ns {\n    display: flex;\n  }\n  .inline-flex-ns {\n    display: inline-flex;\n  }\n  .flex-auto-ns {\n    flex: 1 1 auto;\n    min-width: 0; /* 1 */\n    min-height: 0; /* 1 */\n  }\n  .flex-none-ns {\n    flex: none;\n  }\n  .flex-column-ns {\n    flex-direction: column;\n  }\n  .flex-row-ns {\n    flex-direction: row;\n  }\n  .flex-wrap-ns {\n    flex-wrap: wrap;\n  }\n  .flex-nowrap-ns {\n    flex-wrap: nowrap;\n  }\n  .flex-wrap-reverse-ns {\n    flex-wrap: wrap-reverse;\n  }\n  .flex-column-reverse-ns {\n    flex-direction: column-reverse;\n  }\n  .flex-row-reverse-ns {\n    flex-direction: row-reverse;\n  }\n  .items-start-ns {\n    align-items: flex-start;\n  }\n  .items-end-ns {\n    align-items: flex-end;\n  }\n  .items-center-ns {\n    align-items: center;\n  }\n  .items-baseline-ns {\n    align-items: baseline;\n  }\n  .items-stretch-ns {\n    align-items: stretch;\n  }\n  .self-start-ns {\n    align-self: flex-start;\n  }\n  .self-end-ns {\n    align-self: flex-end;\n  }\n  .self-center-ns {\n    align-self: center;\n  }\n  .self-baseline-ns {\n    align-self: baseline;\n  }\n  .self-stretch-ns {\n    align-self: stretch;\n  }\n  .justify-start-ns {\n    justify-content: flex-start;\n  }\n  .justify-end-ns {\n    justify-content: flex-end;\n  }\n  .justify-center-ns {\n    justify-content: center;\n  }\n  .justify-between-ns {\n    justify-content: space-between;\n  }\n  .justify-around-ns {\n    justify-content: space-around;\n  }\n  .content-start-ns {\n    align-content: flex-start;\n  }\n  .content-end-ns {\n    align-content: flex-end;\n  }\n  .content-center-ns {\n    align-content: center;\n  }\n  .content-between-ns {\n    align-content: space-between;\n  }\n  .content-around-ns {\n    align-content: space-around;\n  }\n  .content-stretch-ns {\n    align-content: stretch;\n  }\n  .order-0-ns {\n    order: 0;\n  }\n  .order-1-ns {\n    order: 1;\n  }\n  .order-2-ns {\n    order: 2;\n  }\n  .order-3-ns {\n    order: 3;\n  }\n  .order-4-ns {\n    order: 4;\n  }\n  .order-5-ns {\n    order: 5;\n  }\n  .order-6-ns {\n    order: 6;\n  }\n  .order-7-ns {\n    order: 7;\n  }\n  .order-8-ns {\n    order: 8;\n  }\n  .order-last-ns {\n    order: 99999;\n  }\n  .flex-grow-0-ns {\n    flex-grow: 0;\n  }\n  .flex-grow-1-ns {\n    flex-grow: 1;\n  }\n  .flex-shrink-0-ns {\n    flex-shrink: 0;\n  }\n  .flex-shrink-1-ns {\n    flex-shrink: 1;\n  }\n  .fl-ns {\n    float: left;\n    _display: inline;\n  }\n  .fr-ns {\n    float: right;\n    _display: inline;\n  }\n  .fn-ns {\n    float: none;\n  }\n  .i-ns {\n    font-style: italic;\n  }\n  .fs-normal-ns {\n    font-style: normal;\n  }\n  .normal-ns {\n    font-weight: normal;\n  }\n  .b-ns {\n    font-weight: bold;\n  }\n  .fw1-ns {\n    font-weight: 100;\n  }\n  .fw2-ns {\n    font-weight: 200;\n  }\n  .fw3-ns {\n    font-weight: 300;\n  }\n  .fw4-ns {\n    font-weight: 400;\n  }\n  .fw5-ns {\n    font-weight: 500;\n  }\n  .fw6-ns {\n    font-weight: 600;\n  }\n  .fw7-ns {\n    font-weight: 700;\n  }\n  .fw8-ns {\n    font-weight: 800;\n  }\n  .fw9-ns {\n    font-weight: 900;\n  }\n  .h1-ns {\n    height: 1rem;\n  }\n  .h2-ns {\n    height: 2rem;\n  }\n  .h3-ns {\n    height: 4rem;\n  }\n  .h4-ns {\n    height: 8rem;\n  }\n  .h5-ns {\n    height: 16rem;\n  }\n  .h-25-ns {\n    height: 25%;\n  }\n  .h-50-ns {\n    height: 50%;\n  }\n  .h-75-ns {\n    height: 75%;\n  }\n  .h-100-ns {\n    height: 100%;\n  }\n  .min-h-100-ns {\n    min-height: 100%;\n  }\n  .vh-25-ns {\n    height: 25vh;\n  }\n  .vh-50-ns {\n    height: 50vh;\n  }\n  .vh-75-ns {\n    height: 75vh;\n  }\n  .vh-100-ns {\n    height: 100vh;\n  }\n  .min-vh-100-ns {\n    min-height: 100vh;\n  }\n  .h-auto-ns {\n    height: auto;\n  }\n  .h-inherit-ns {\n    height: inherit;\n  }\n  .tracked-ns {\n    letter-spacing: 0.1em;\n  }\n  .tracked-tight-ns {\n    letter-spacing: -0.05em;\n  }\n  .tracked-mega-ns {\n    letter-spacing: 0.25em;\n  }\n  .lh-solid-ns {\n    line-height: 1;\n  }\n  .lh-title-ns {\n    line-height: 1.25;\n  }\n  .lh-copy-ns {\n    line-height: 1.5;\n  }\n  .mw-100-ns {\n    max-width: 100%;\n  }\n  .mw1-ns {\n    max-width: 1rem;\n  }\n  .mw2-ns {\n    max-width: 2rem;\n  }\n  .mw3-ns {\n    max-width: 4rem;\n  }\n  .mw4-ns {\n    max-width: 8rem;\n  }\n  .mw5-ns {\n    max-width: 16rem;\n  }\n  .mw6-ns {\n    max-width: 32rem;\n  }\n  .mw7-ns {\n    max-width: 48rem;\n  }\n  .mw8-ns {\n    max-width: 64rem;\n  }\n  .mw9-ns {\n    max-width: 96rem;\n  }\n  .mw-none-ns {\n    max-width: none;\n  }\n  .w1-ns {\n    width: 1rem;\n  }\n  .w2-ns {\n    width: 2rem;\n  }\n  .w3-ns {\n    width: 4rem;\n  }\n  .w4-ns {\n    width: 8rem;\n  }\n  .w5-ns {\n    width: 16rem;\n  }\n  .w-10-ns {\n    width: 10%;\n  }\n  .w-20-ns {\n    width: 20%;\n  }\n  .w-25-ns {\n    width: 25%;\n  }\n  .w-30-ns {\n    width: 30%;\n  }\n  .w-33-ns {\n    width: 33%;\n  }\n  .w-34-ns {\n    width: 34%;\n  }\n  .w-40-ns {\n    width: 40%;\n  }\n  .w-50-ns {\n    width: 50%;\n  }\n  .w-60-ns {\n    width: 60%;\n  }\n  .w-70-ns {\n    width: 70%;\n  }\n  .w-75-ns {\n    width: 75%;\n  }\n  .w-80-ns {\n    width: 80%;\n  }\n  .w-90-ns {\n    width: 90%;\n  }\n  .w-100-ns {\n    width: 100%;\n  }\n  .w-third-ns {\n    width: calc(100% / 3);\n  }\n  .w-two-thirds-ns {\n    width: calc(100% / 1.5);\n  }\n  .w-auto-ns {\n    width: auto;\n  }\n  .overflow-visible-ns {\n    overflow: visible;\n  }\n  .overflow-hidden-ns {\n    overflow: hidden;\n  }\n  .overflow-scroll-ns {\n    overflow: scroll;\n  }\n  .overflow-auto-ns {\n    overflow: auto;\n  }\n  .overflow-x-visible-ns {\n    overflow-x: visible;\n  }\n  .overflow-x-hidden-ns {\n    overflow-x: hidden;\n  }\n  .overflow-x-scroll-ns {\n    overflow-x: scroll;\n  }\n  .overflow-x-auto-ns {\n    overflow-x: auto;\n  }\n  .overflow-y-visible-ns {\n    overflow-y: visible;\n  }\n  .overflow-y-hidden-ns {\n    overflow-y: hidden;\n  }\n  .overflow-y-scroll-ns {\n    overflow-y: scroll;\n  }\n  .overflow-y-auto-ns {\n    overflow-y: auto;\n  }\n  .static-ns {\n    position: static;\n  }\n  .relative-ns {\n    position: relative;\n  }\n  .absolute-ns {\n    position: absolute;\n  }\n  .fixed-ns {\n    position: fixed;\n  }\n  .rotate-45-ns {\n    -webkit-transform: rotate(45deg);\n    transform: rotate(45deg);\n  }\n  .rotate-90-ns {\n    -webkit-transform: rotate(90deg);\n    transform: rotate(90deg);\n  }\n  .rotate-135-ns {\n    -webkit-transform: rotate(135deg);\n    transform: rotate(135deg);\n  }\n  .rotate-180-ns {\n    -webkit-transform: rotate(180deg);\n    transform: rotate(180deg);\n  }\n  .rotate-225-ns {\n    -webkit-transform: rotate(225deg);\n    transform: rotate(225deg);\n  }\n  .rotate-270-ns {\n    -webkit-transform: rotate(270deg);\n    transform: rotate(270deg);\n  }\n  .rotate-315-ns {\n    -webkit-transform: rotate(315deg);\n    transform: rotate(315deg);\n  }\n  .pa0-ns {\n    padding: 0;\n  }\n  .pa1-ns {\n    padding: 0.25rem;\n  }\n  .pa2-ns {\n    padding: 0.5rem;\n  }\n  .pa3-ns {\n    padding: 1rem;\n  }\n  .pa4-ns {\n    padding: 2rem;\n  }\n  .pa5-ns {\n    padding: 4rem;\n  }\n  .pa6-ns {\n    padding: 8rem;\n  }\n  .pa7-ns {\n    padding: 16rem;\n  }\n  .pl0-ns {\n    padding-left: 0;\n  }\n  .pl1-ns {\n    padding-left: 0.25rem;\n  }\n  .pl2-ns {\n    padding-left: 0.5rem;\n  }\n  .pl3-ns {\n    padding-left: 1rem;\n  }\n  .pl4-ns {\n    padding-left: 2rem;\n  }\n  .pl5-ns {\n    padding-left: 4rem;\n  }\n  .pl6-ns {\n    padding-left: 8rem;\n  }\n  .pl7-ns {\n    padding-left: 16rem;\n  }\n  .pr0-ns {\n    padding-right: 0;\n  }\n  .pr1-ns {\n    padding-right: 0.25rem;\n  }\n  .pr2-ns {\n    padding-right: 0.5rem;\n  }\n  .pr3-ns {\n    padding-right: 1rem;\n  }\n  .pr4-ns {\n    padding-right: 2rem;\n  }\n  .pr5-ns {\n    padding-right: 4rem;\n  }\n  .pr6-ns {\n    padding-right: 8rem;\n  }\n  .pr7-ns {\n    padding-right: 16rem;\n  }\n  .pb0-ns {\n    padding-bottom: 0;\n  }\n  .pb1-ns {\n    padding-bottom: 0.25rem;\n  }\n  .pb2-ns {\n    padding-bottom: 0.5rem;\n  }\n  .pb3-ns {\n    padding-bottom: 1rem;\n  }\n  .pb4-ns {\n    padding-bottom: 2rem;\n  }\n  .pb5-ns {\n    padding-bottom: 4rem;\n  }\n  .pb6-ns {\n    padding-bottom: 8rem;\n  }\n  .pb7-ns {\n    padding-bottom: 16rem;\n  }\n  .pt0-ns {\n    padding-top: 0;\n  }\n  .pt1-ns {\n    padding-top: 0.25rem;\n  }\n  .pt2-ns {\n    padding-top: 0.5rem;\n  }\n  .pt3-ns {\n    padding-top: 1rem;\n  }\n  .pt4-ns {\n    padding-top: 2rem;\n  }\n  .pt5-ns {\n    padding-top: 4rem;\n  }\n  .pt6-ns {\n    padding-top: 8rem;\n  }\n  .pt7-ns {\n    padding-top: 16rem;\n  }\n  .pv0-ns {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n  .pv1-ns {\n    padding-top: 0.25rem;\n    padding-bottom: 0.25rem;\n  }\n  .pv2-ns {\n    padding-top: 0.5rem;\n    padding-bottom: 0.5rem;\n  }\n  .pv3-ns {\n    padding-top: 1rem;\n    padding-bottom: 1rem;\n  }\n  .pv4-ns {\n    padding-top: 2rem;\n    padding-bottom: 2rem;\n  }\n  .pv5-ns {\n    padding-top: 4rem;\n    padding-bottom: 4rem;\n  }\n  .pv6-ns {\n    padding-top: 8rem;\n    padding-bottom: 8rem;\n  }\n  .pv7-ns {\n    padding-top: 16rem;\n    padding-bottom: 16rem;\n  }\n  .ph0-ns {\n    padding-left: 0;\n    padding-right: 0;\n  }\n  .ph1-ns {\n    padding-left: 0.25rem;\n    padding-right: 0.25rem;\n  }\n  .ph2-ns {\n    padding-left: 0.5rem;\n    padding-right: 0.5rem;\n  }\n  .ph3-ns {\n    padding-left: 1rem;\n    padding-right: 1rem;\n  }\n  .ph4-ns {\n    padding-left: 2rem;\n    padding-right: 2rem;\n  }\n  .ph5-ns {\n    padding-left: 4rem;\n    padding-right: 4rem;\n  }\n  .ph6-ns {\n    padding-left: 8rem;\n    padding-right: 8rem;\n  }\n  .ph7-ns {\n    padding-left: 16rem;\n    padding-right: 16rem;\n  }\n  .ma0-ns {\n    margin: 0;\n  }\n  .ma1-ns {\n    margin: 0.25rem;\n  }\n  .ma2-ns {\n    margin: 0.5rem;\n  }\n  .ma3-ns {\n    margin: 1rem;\n  }\n  .ma4-ns {\n    margin: 2rem;\n  }\n  .ma5-ns {\n    margin: 4rem;\n  }\n  .ma6-ns {\n    margin: 8rem;\n  }\n  .ma7-ns {\n    margin: 16rem;\n  }\n  .ml0-ns {\n    margin-left: 0;\n  }\n  .ml1-ns {\n    margin-left: 0.25rem;\n  }\n  .ml2-ns {\n    margin-left: 0.5rem;\n  }\n  .ml3-ns {\n    margin-left: 1rem;\n  }\n  .ml4-ns {\n    margin-left: 2rem;\n  }\n  .ml5-ns {\n    margin-left: 4rem;\n  }\n  .ml6-ns {\n    margin-left: 8rem;\n  }\n  .ml7-ns {\n    margin-left: 16rem;\n  }\n  .mr0-ns {\n    margin-right: 0;\n  }\n  .mr1-ns {\n    margin-right: 0.25rem;\n  }\n  .mr2-ns {\n    margin-right: 0.5rem;\n  }\n  .mr3-ns {\n    margin-right: 1rem;\n  }\n  .mr4-ns {\n    margin-right: 2rem;\n  }\n  .mr5-ns {\n    margin-right: 4rem;\n  }\n  .mr6-ns {\n    margin-right: 8rem;\n  }\n  .mr7-ns {\n    margin-right: 16rem;\n  }\n  .mb0-ns {\n    margin-bottom: 0;\n  }\n  .mb1-ns {\n    margin-bottom: 0.25rem;\n  }\n  .mb2-ns {\n    margin-bottom: 0.5rem;\n  }\n  .mb3-ns {\n    margin-bottom: 1rem;\n  }\n  .mb4-ns {\n    margin-bottom: 2rem;\n  }\n  .mb5-ns {\n    margin-bottom: 4rem;\n  }\n  .mb6-ns {\n    margin-bottom: 8rem;\n  }\n  .mb7-ns {\n    margin-bottom: 16rem;\n  }\n  .mt0-ns {\n    margin-top: 0;\n  }\n  .mt1-ns {\n    margin-top: 0.25rem;\n  }\n  .mt2-ns {\n    margin-top: 0.5rem;\n  }\n  .mt3-ns {\n    margin-top: 1rem;\n  }\n  .mt4-ns {\n    margin-top: 2rem;\n  }\n  .mt5-ns {\n    margin-top: 4rem;\n  }\n  .mt6-ns {\n    margin-top: 8rem;\n  }\n  .mt7-ns {\n    margin-top: 16rem;\n  }\n  .mv0-ns {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .mv1-ns {\n    margin-top: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n  .mv2-ns {\n    margin-top: 0.5rem;\n    margin-bottom: 0.5rem;\n  }\n  .mv3-ns {\n    margin-top: 1rem;\n    margin-bottom: 1rem;\n  }\n  .mv4-ns {\n    margin-top: 2rem;\n    margin-bottom: 2rem;\n  }\n  .mv5-ns {\n    margin-top: 4rem;\n    margin-bottom: 4rem;\n  }\n  .mv6-ns {\n    margin-top: 8rem;\n    margin-bottom: 8rem;\n  }\n  .mv7-ns {\n    margin-top: 16rem;\n    margin-bottom: 16rem;\n  }\n  .mh0-ns {\n    margin-left: 0;\n    margin-right: 0;\n  }\n  .mh1-ns {\n    margin-left: 0.25rem;\n    margin-right: 0.25rem;\n  }\n  .mh2-ns {\n    margin-left: 0.5rem;\n    margin-right: 0.5rem;\n  }\n  .mh3-ns {\n    margin-left: 1rem;\n    margin-right: 1rem;\n  }\n  .mh4-ns {\n    margin-left: 2rem;\n    margin-right: 2rem;\n  }\n  .mh5-ns {\n    margin-left: 4rem;\n    margin-right: 4rem;\n  }\n  .mh6-ns {\n    margin-left: 8rem;\n    margin-right: 8rem;\n  }\n  .mh7-ns {\n    margin-left: 16rem;\n    margin-right: 16rem;\n  }\n  .na1-ns {\n    margin: -0.25rem;\n  }\n  .na2-ns {\n    margin: -0.5rem;\n  }\n  .na3-ns {\n    margin: -1rem;\n  }\n  .na4-ns {\n    margin: -2rem;\n  }\n  .na5-ns {\n    margin: -4rem;\n  }\n  .na6-ns {\n    margin: -8rem;\n  }\n  .na7-ns {\n    margin: -16rem;\n  }\n  .nl1-ns {\n    margin-left: -0.25rem;\n  }\n  .nl2-ns {\n    margin-left: -0.5rem;\n  }\n  .nl3-ns {\n    margin-left: -1rem;\n  }\n  .nl4-ns {\n    margin-left: -2rem;\n  }\n  .nl5-ns {\n    margin-left: -4rem;\n  }\n  .nl6-ns {\n    margin-left: -8rem;\n  }\n  .nl7-ns {\n    margin-left: -16rem;\n  }\n  .nr1-ns {\n    margin-right: -0.25rem;\n  }\n  .nr2-ns {\n    margin-right: -0.5rem;\n  }\n  .nr3-ns {\n    margin-right: -1rem;\n  }\n  .nr4-ns {\n    margin-right: -2rem;\n  }\n  .nr5-ns {\n    margin-right: -4rem;\n  }\n  .nr6-ns {\n    margin-right: -8rem;\n  }\n  .nr7-ns {\n    margin-right: -16rem;\n  }\n  .nb1-ns {\n    margin-bottom: -0.25rem;\n  }\n  .nb2-ns {\n    margin-bottom: -0.5rem;\n  }\n  .nb3-ns {\n    margin-bottom: -1rem;\n  }\n  .nb4-ns {\n    margin-bottom: -2rem;\n  }\n  .nb5-ns {\n    margin-bottom: -4rem;\n  }\n  .nb6-ns {\n    margin-bottom: -8rem;\n  }\n  .nb7-ns {\n    margin-bottom: -16rem;\n  }\n  .nt1-ns {\n    margin-top: -0.25rem;\n  }\n  .nt2-ns {\n    margin-top: -0.5rem;\n  }\n  .nt3-ns {\n    margin-top: -1rem;\n  }\n  .nt4-ns {\n    margin-top: -2rem;\n  }\n  .nt5-ns {\n    margin-top: -4rem;\n  }\n  .nt6-ns {\n    margin-top: -8rem;\n  }\n  .nt7-ns {\n    margin-top: -16rem;\n  }\n  .strike-ns {\n    text-decoration: line-through;\n  }\n  .underline-ns {\n    text-decoration: underline;\n  }\n  .no-underline-ns {\n    text-decoration: none;\n  }\n  .tl-ns {\n    text-align: left;\n  }\n  .tr-ns {\n    text-align: right;\n  }\n  .tc-ns {\n    text-align: center;\n  }\n  .tj-ns {\n    text-align: justify;\n  }\n  .ttc-ns {\n    text-transform: capitalize;\n  }\n  .ttl-ns {\n    text-transform: lowercase;\n  }\n  .ttu-ns {\n    text-transform: uppercase;\n  }\n  .ttn-ns {\n    text-transform: none;\n  }\n  .f-6-ns,\n  .f-headline-ns {\n    font-size: 6rem;\n  }\n  .f-5-ns,\n  .f-subheadline-ns {\n    font-size: 5rem;\n  }\n  .f1-ns {\n    font-size: 3rem;\n  }\n  .f2-ns {\n    font-size: 2.25rem;\n  }\n  .f3-ns {\n    font-size: 1.5rem;\n  }\n  .f4-ns {\n    font-size: 1.25rem;\n  }\n  .f5-ns {\n    font-size: 1rem;\n  }\n  .f6-ns {\n    font-size: 0.875rem;\n  }\n  .f7-ns {\n    font-size: 0.75rem;\n  }\n  .measure-ns {\n    max-width: 30em;\n  }\n  .measure-wide-ns {\n    max-width: 34em;\n  }\n  .measure-narrow-ns {\n    max-width: 20em;\n  }\n  .indent-ns {\n    text-indent: 1em;\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .small-caps-ns {\n    font-variant: small-caps;\n  }\n  .truncate-ns {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .center-ns {\n    margin-right: auto;\n    margin-left: auto;\n  }\n  .mr-auto-ns {\n    margin-right: auto;\n  }\n  .ml-auto-ns {\n    margin-left: auto;\n  }\n  .clip-ns {\n    position: fixed !important;\n    _position: absolute !important;\n    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n    clip: rect(1px, 1px, 1px, 1px);\n  }\n  .ws-normal-ns {\n    white-space: normal;\n  }\n  .nowrap-ns {\n    white-space: nowrap;\n  }\n  .pre-ns {\n    white-space: pre;\n  }\n  .v-base-ns {\n    vertical-align: baseline;\n  }\n  .v-mid-ns {\n    vertical-align: middle;\n  }\n  .v-top-ns {\n    vertical-align: top;\n  }\n  .v-btm-ns {\n    vertical-align: bottom;\n  }\n}\n@media screen and (min-width: 30em) and (max-width: 60em) {\n  .aspect-ratio-m {\n    height: 0;\n    position: relative;\n  }\n  .aspect-ratio--16x9-m {\n    padding-bottom: 56.25%;\n  }\n  .aspect-ratio--9x16-m {\n    padding-bottom: 177.77%;\n  }\n  .aspect-ratio--4x3-m {\n    padding-bottom: 75%;\n  }\n  .aspect-ratio--3x4-m {\n    padding-bottom: 133.33%;\n  }\n  .aspect-ratio--6x4-m {\n    padding-bottom: 66.6%;\n  }\n  .aspect-ratio--4x6-m {\n    padding-bottom: 150%;\n  }\n  .aspect-ratio--8x5-m {\n    padding-bottom: 62.5%;\n  }\n  .aspect-ratio--5x8-m {\n    padding-bottom: 160%;\n  }\n  .aspect-ratio--7x5-m {\n    padding-bottom: 71.42%;\n  }\n  .aspect-ratio--5x7-m {\n    padding-bottom: 140%;\n  }\n  .aspect-ratio--1x1-m {\n    padding-bottom: 100%;\n  }\n  .aspect-ratio--object-m {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    z-index: 100;\n  }\n  .cover-m {\n    background-size: cover !important;\n  }\n  .contain-m {\n    background-size: contain !important;\n  }\n  .bg-center-m {\n    background-repeat: no-repeat;\n    background-position: center center;\n  }\n  .bg-top-m {\n    background-repeat: no-repeat;\n    background-position: top center;\n  }\n  .bg-right-m {\n    background-repeat: no-repeat;\n    background-position: center right;\n  }\n  .bg-bottom-m {\n    background-repeat: no-repeat;\n    background-position: bottom center;\n  }\n  .bg-left-m {\n    background-repeat: no-repeat;\n    background-position: center left;\n  }\n  .outline-m {\n    outline: 1px solid;\n  }\n  .outline-transparent-m {\n    outline: 1px solid transparent;\n  }\n  .outline-0-m {\n    outline: 0;\n  }\n  .ba-m {\n    border-style: solid;\n    border-width: 1px;\n  }\n  .bt-m {\n    border-top-style: solid;\n    border-top-width: 1px;\n  }\n  .br-m {\n    border-right-style: solid;\n    border-right-width: 1px;\n  }\n  .bb-m {\n    border-bottom-style: solid;\n    border-bottom-width: 1px;\n  }\n  .bl-m {\n    border-left-style: solid;\n    border-left-width: 1px;\n  }\n  .bn-m {\n    border-style: none;\n    border-width: 0;\n  }\n  .br0-m {\n    border-radius: 0;\n  }\n  .br1-m {\n    border-radius: 0.125rem;\n  }\n  .br2-m {\n    border-radius: 0.25rem;\n  }\n  .br3-m {\n    border-radius: 0.5rem;\n  }\n  .br4-m {\n    border-radius: 1rem;\n  }\n  .br-100-m {\n    border-radius: 100%;\n  }\n  .br-pill-m {\n    border-radius: 9999px;\n  }\n  .br--bottom-m {\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n  }\n  .br--top-m {\n    border-bottom-left-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .br--right-m {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n  .br--left-m {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .b--dotted-m {\n    border-style: dotted;\n  }\n  .b--dashed-m {\n    border-style: dashed;\n  }\n  .b--solid-m {\n    border-style: solid;\n  }\n  .b--none-m {\n    border-style: none;\n  }\n  .bw0-m {\n    border-width: 0;\n  }\n  .bw1-m {\n    border-width: 0.125rem;\n  }\n  .bw2-m {\n    border-width: 0.25rem;\n  }\n  .bw3-m {\n    border-width: 0.5rem;\n  }\n  .bw4-m {\n    border-width: 1rem;\n  }\n  .bw5-m {\n    border-width: 2rem;\n  }\n  .bt-0-m {\n    border-top-width: 0;\n  }\n  .br-0-m {\n    border-right-width: 0;\n  }\n  .bb-0-m {\n    border-bottom-width: 0;\n  }\n  .bl-0-m {\n    border-left-width: 0;\n  }\n  .shadow-1-m {\n    box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-2-m {\n    box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-3-m {\n    box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-4-m {\n    box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .shadow-5-m {\n    box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .top-0-m {\n    top: 0;\n  }\n  .left-0-m {\n    left: 0;\n  }\n  .right-0-m {\n    right: 0;\n  }\n  .bottom-0-m {\n    bottom: 0;\n  }\n  .top-1-m {\n    top: 1rem;\n  }\n  .left-1-m {\n    left: 1rem;\n  }\n  .right-1-m {\n    right: 1rem;\n  }\n  .bottom-1-m {\n    bottom: 1rem;\n  }\n  .top-2-m {\n    top: 2rem;\n  }\n  .left-2-m {\n    left: 2rem;\n  }\n  .right-2-m {\n    right: 2rem;\n  }\n  .bottom-2-m {\n    bottom: 2rem;\n  }\n  .top--1-m {\n    top: -1rem;\n  }\n  .right--1-m {\n    right: -1rem;\n  }\n  .bottom--1-m {\n    bottom: -1rem;\n  }\n  .left--1-m {\n    left: -1rem;\n  }\n  .top--2-m {\n    top: -2rem;\n  }\n  .right--2-m {\n    right: -2rem;\n  }\n  .bottom--2-m {\n    bottom: -2rem;\n  }\n  .left--2-m {\n    left: -2rem;\n  }\n  .absolute--fill-m {\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n  }\n  .cl-m {\n    clear: left;\n  }\n  .cr-m {\n    clear: right;\n  }\n  .cb-m {\n    clear: both;\n  }\n  .cn-m {\n    clear: none;\n  }\n  .dn-m {\n    display: none;\n  }\n  .di-m {\n    display: inline;\n  }\n  .db-m {\n    display: block;\n  }\n  .dib-m {\n    display: inline-block;\n  }\n  .dit-m {\n    display: inline-table;\n  }\n  .dt-m {\n    display: table;\n  }\n  .dtc-m {\n    display: table-cell;\n  }\n  .dt-row-m {\n    display: table-row;\n  }\n  .dt-row-group-m {\n    display: table-row-group;\n  }\n  .dt-column-m {\n    display: table-column;\n  }\n  .dt-column-group-m {\n    display: table-column-group;\n  }\n  .dt--fixed-m {\n    table-layout: fixed;\n    width: 100%;\n  }\n  .flex-m {\n    display: flex;\n  }\n  .inline-flex-m {\n    display: inline-flex;\n  }\n  .flex-auto-m {\n    flex: 1 1 auto;\n    min-width: 0; /* 1 */\n    min-height: 0; /* 1 */\n  }\n  .flex-none-m {\n    flex: none;\n  }\n  .flex-column-m {\n    flex-direction: column;\n  }\n  .flex-row-m {\n    flex-direction: row;\n  }\n  .flex-wrap-m {\n    flex-wrap: wrap;\n  }\n  .flex-nowrap-m {\n    flex-wrap: nowrap;\n  }\n  .flex-wrap-reverse-m {\n    flex-wrap: wrap-reverse;\n  }\n  .flex-column-reverse-m {\n    flex-direction: column-reverse;\n  }\n  .flex-row-reverse-m {\n    flex-direction: row-reverse;\n  }\n  .items-start-m {\n    align-items: flex-start;\n  }\n  .items-end-m {\n    align-items: flex-end;\n  }\n  .items-center-m {\n    align-items: center;\n  }\n  .items-baseline-m {\n    align-items: baseline;\n  }\n  .items-stretch-m {\n    align-items: stretch;\n  }\n  .self-start-m {\n    align-self: flex-start;\n  }\n  .self-end-m {\n    align-self: flex-end;\n  }\n  .self-center-m {\n    align-self: center;\n  }\n  .self-baseline-m {\n    align-self: baseline;\n  }\n  .self-stretch-m {\n    align-self: stretch;\n  }\n  .justify-start-m {\n    justify-content: flex-start;\n  }\n  .justify-end-m {\n    justify-content: flex-end;\n  }\n  .justify-center-m {\n    justify-content: center;\n  }\n  .justify-between-m {\n    justify-content: space-between;\n  }\n  .justify-around-m {\n    justify-content: space-around;\n  }\n  .content-start-m {\n    align-content: flex-start;\n  }\n  .content-end-m {\n    align-content: flex-end;\n  }\n  .content-center-m {\n    align-content: center;\n  }\n  .content-between-m {\n    align-content: space-between;\n  }\n  .content-around-m {\n    align-content: space-around;\n  }\n  .content-stretch-m {\n    align-content: stretch;\n  }\n  .order-0-m {\n    order: 0;\n  }\n  .order-1-m {\n    order: 1;\n  }\n  .order-2-m {\n    order: 2;\n  }\n  .order-3-m {\n    order: 3;\n  }\n  .order-4-m {\n    order: 4;\n  }\n  .order-5-m {\n    order: 5;\n  }\n  .order-6-m {\n    order: 6;\n  }\n  .order-7-m {\n    order: 7;\n  }\n  .order-8-m {\n    order: 8;\n  }\n  .order-last-m {\n    order: 99999;\n  }\n  .flex-grow-0-m {\n    flex-grow: 0;\n  }\n  .flex-grow-1-m {\n    flex-grow: 1;\n  }\n  .flex-shrink-0-m {\n    flex-shrink: 0;\n  }\n  .flex-shrink-1-m {\n    flex-shrink: 1;\n  }\n  .fl-m {\n    float: left;\n    _display: inline;\n  }\n  .fr-m {\n    float: right;\n    _display: inline;\n  }\n  .fn-m {\n    float: none;\n  }\n  .i-m {\n    font-style: italic;\n  }\n  .fs-normal-m {\n    font-style: normal;\n  }\n  .normal-m {\n    font-weight: normal;\n  }\n  .b-m {\n    font-weight: bold;\n  }\n  .fw1-m {\n    font-weight: 100;\n  }\n  .fw2-m {\n    font-weight: 200;\n  }\n  .fw3-m {\n    font-weight: 300;\n  }\n  .fw4-m {\n    font-weight: 400;\n  }\n  .fw5-m {\n    font-weight: 500;\n  }\n  .fw6-m {\n    font-weight: 600;\n  }\n  .fw7-m {\n    font-weight: 700;\n  }\n  .fw8-m {\n    font-weight: 800;\n  }\n  .fw9-m {\n    font-weight: 900;\n  }\n  .h1-m {\n    height: 1rem;\n  }\n  .h2-m {\n    height: 2rem;\n  }\n  .h3-m {\n    height: 4rem;\n  }\n  .h4-m {\n    height: 8rem;\n  }\n  .h5-m {\n    height: 16rem;\n  }\n  .h-25-m {\n    height: 25%;\n  }\n  .h-50-m {\n    height: 50%;\n  }\n  .h-75-m {\n    height: 75%;\n  }\n  .h-100-m {\n    height: 100%;\n  }\n  .min-h-100-m {\n    min-height: 100%;\n  }\n  .vh-25-m {\n    height: 25vh;\n  }\n  .vh-50-m {\n    height: 50vh;\n  }\n  .vh-75-m {\n    height: 75vh;\n  }\n  .vh-100-m {\n    height: 100vh;\n  }\n  .min-vh-100-m {\n    min-height: 100vh;\n  }\n  .h-auto-m {\n    height: auto;\n  }\n  .h-inherit-m {\n    height: inherit;\n  }\n  .tracked-m {\n    letter-spacing: 0.1em;\n  }\n  .tracked-tight-m {\n    letter-spacing: -0.05em;\n  }\n  .tracked-mega-m {\n    letter-spacing: 0.25em;\n  }\n  .lh-solid-m {\n    line-height: 1;\n  }\n  .lh-title-m {\n    line-height: 1.25;\n  }\n  .lh-copy-m {\n    line-height: 1.5;\n  }\n  .mw-100-m {\n    max-width: 100%;\n  }\n  .mw1-m {\n    max-width: 1rem;\n  }\n  .mw2-m {\n    max-width: 2rem;\n  }\n  .mw3-m {\n    max-width: 4rem;\n  }\n  .mw4-m {\n    max-width: 8rem;\n  }\n  .mw5-m {\n    max-width: 16rem;\n  }\n  .mw6-m {\n    max-width: 32rem;\n  }\n  .mw7-m {\n    max-width: 48rem;\n  }\n  .mw8-m {\n    max-width: 64rem;\n  }\n  .mw9-m {\n    max-width: 96rem;\n  }\n  .mw-none-m {\n    max-width: none;\n  }\n  .w1-m {\n    width: 1rem;\n  }\n  .w2-m {\n    width: 2rem;\n  }\n  .w3-m {\n    width: 4rem;\n  }\n  .w4-m {\n    width: 8rem;\n  }\n  .w5-m {\n    width: 16rem;\n  }\n  .w-10-m {\n    width: 10%;\n  }\n  .w-20-m {\n    width: 20%;\n  }\n  .w-25-m {\n    width: 25%;\n  }\n  .w-30-m {\n    width: 30%;\n  }\n  .w-33-m {\n    width: 33%;\n  }\n  .w-34-m {\n    width: 34%;\n  }\n  .w-40-m {\n    width: 40%;\n  }\n  .w-50-m {\n    width: 50%;\n  }\n  .w-60-m {\n    width: 60%;\n  }\n  .w-70-m {\n    width: 70%;\n  }\n  .w-75-m {\n    width: 75%;\n  }\n  .w-80-m {\n    width: 80%;\n  }\n  .w-90-m {\n    width: 90%;\n  }\n  .w-100-m {\n    width: 100%;\n  }\n  .w-third-m {\n    width: calc(100% / 3);\n  }\n  .w-two-thirds-m {\n    width: calc(100% / 1.5);\n  }\n  .w-auto-m {\n    width: auto;\n  }\n  .overflow-visible-m {\n    overflow: visible;\n  }\n  .overflow-hidden-m {\n    overflow: hidden;\n  }\n  .overflow-scroll-m {\n    overflow: scroll;\n  }\n  .overflow-auto-m {\n    overflow: auto;\n  }\n  .overflow-x-visible-m {\n    overflow-x: visible;\n  }\n  .overflow-x-hidden-m {\n    overflow-x: hidden;\n  }\n  .overflow-x-scroll-m {\n    overflow-x: scroll;\n  }\n  .overflow-x-auto-m {\n    overflow-x: auto;\n  }\n  .overflow-y-visible-m {\n    overflow-y: visible;\n  }\n  .overflow-y-hidden-m {\n    overflow-y: hidden;\n  }\n  .overflow-y-scroll-m {\n    overflow-y: scroll;\n  }\n  .overflow-y-auto-m {\n    overflow-y: auto;\n  }\n  .static-m {\n    position: static;\n  }\n  .relative-m {\n    position: relative;\n  }\n  .absolute-m {\n    position: absolute;\n  }\n  .fixed-m {\n    position: fixed;\n  }\n  .rotate-45-m {\n    -webkit-transform: rotate(45deg);\n    transform: rotate(45deg);\n  }\n  .rotate-90-m {\n    -webkit-transform: rotate(90deg);\n    transform: rotate(90deg);\n  }\n  .rotate-135-m {\n    -webkit-transform: rotate(135deg);\n    transform: rotate(135deg);\n  }\n  .rotate-180-m {\n    -webkit-transform: rotate(180deg);\n    transform: rotate(180deg);\n  }\n  .rotate-225-m {\n    -webkit-transform: rotate(225deg);\n    transform: rotate(225deg);\n  }\n  .rotate-270-m {\n    -webkit-transform: rotate(270deg);\n    transform: rotate(270deg);\n  }\n  .rotate-315-m {\n    -webkit-transform: rotate(315deg);\n    transform: rotate(315deg);\n  }\n  .pa0-m {\n    padding: 0;\n  }\n  .pa1-m {\n    padding: 0.25rem;\n  }\n  .pa2-m {\n    padding: 0.5rem;\n  }\n  .pa3-m {\n    padding: 1rem;\n  }\n  .pa4-m {\n    padding: 2rem;\n  }\n  .pa5-m {\n    padding: 4rem;\n  }\n  .pa6-m {\n    padding: 8rem;\n  }\n  .pa7-m {\n    padding: 16rem;\n  }\n  .pl0-m {\n    padding-left: 0;\n  }\n  .pl1-m {\n    padding-left: 0.25rem;\n  }\n  .pl2-m {\n    padding-left: 0.5rem;\n  }\n  .pl3-m {\n    padding-left: 1rem;\n  }\n  .pl4-m {\n    padding-left: 2rem;\n  }\n  .pl5-m {\n    padding-left: 4rem;\n  }\n  .pl6-m {\n    padding-left: 8rem;\n  }\n  .pl7-m {\n    padding-left: 16rem;\n  }\n  .pr0-m {\n    padding-right: 0;\n  }\n  .pr1-m {\n    padding-right: 0.25rem;\n  }\n  .pr2-m {\n    padding-right: 0.5rem;\n  }\n  .pr3-m {\n    padding-right: 1rem;\n  }\n  .pr4-m {\n    padding-right: 2rem;\n  }\n  .pr5-m {\n    padding-right: 4rem;\n  }\n  .pr6-m {\n    padding-right: 8rem;\n  }\n  .pr7-m {\n    padding-right: 16rem;\n  }\n  .pb0-m {\n    padding-bottom: 0;\n  }\n  .pb1-m {\n    padding-bottom: 0.25rem;\n  }\n  .pb2-m {\n    padding-bottom: 0.5rem;\n  }\n  .pb3-m {\n    padding-bottom: 1rem;\n  }\n  .pb4-m {\n    padding-bottom: 2rem;\n  }\n  .pb5-m {\n    padding-bottom: 4rem;\n  }\n  .pb6-m {\n    padding-bottom: 8rem;\n  }\n  .pb7-m {\n    padding-bottom: 16rem;\n  }\n  .pt0-m {\n    padding-top: 0;\n  }\n  .pt1-m {\n    padding-top: 0.25rem;\n  }\n  .pt2-m {\n    padding-top: 0.5rem;\n  }\n  .pt3-m {\n    padding-top: 1rem;\n  }\n  .pt4-m {\n    padding-top: 2rem;\n  }\n  .pt5-m {\n    padding-top: 4rem;\n  }\n  .pt6-m {\n    padding-top: 8rem;\n  }\n  .pt7-m {\n    padding-top: 16rem;\n  }\n  .pv0-m {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n  .pv1-m {\n    padding-top: 0.25rem;\n    padding-bottom: 0.25rem;\n  }\n  .pv2-m {\n    padding-top: 0.5rem;\n    padding-bottom: 0.5rem;\n  }\n  .pv3-m {\n    padding-top: 1rem;\n    padding-bottom: 1rem;\n  }\n  .pv4-m {\n    padding-top: 2rem;\n    padding-bottom: 2rem;\n  }\n  .pv5-m {\n    padding-top: 4rem;\n    padding-bottom: 4rem;\n  }\n  .pv6-m {\n    padding-top: 8rem;\n    padding-bottom: 8rem;\n  }\n  .pv7-m {\n    padding-top: 16rem;\n    padding-bottom: 16rem;\n  }\n  .ph0-m {\n    padding-left: 0;\n    padding-right: 0;\n  }\n  .ph1-m {\n    padding-left: 0.25rem;\n    padding-right: 0.25rem;\n  }\n  .ph2-m {\n    padding-left: 0.5rem;\n    padding-right: 0.5rem;\n  }\n  .ph3-m {\n    padding-left: 1rem;\n    padding-right: 1rem;\n  }\n  .ph4-m {\n    padding-left: 2rem;\n    padding-right: 2rem;\n  }\n  .ph5-m {\n    padding-left: 4rem;\n    padding-right: 4rem;\n  }\n  .ph6-m {\n    padding-left: 8rem;\n    padding-right: 8rem;\n  }\n  .ph7-m {\n    padding-left: 16rem;\n    padding-right: 16rem;\n  }\n  .ma0-m {\n    margin: 0;\n  }\n  .ma1-m {\n    margin: 0.25rem;\n  }\n  .ma2-m {\n    margin: 0.5rem;\n  }\n  .ma3-m {\n    margin: 1rem;\n  }\n  .ma4-m {\n    margin: 2rem;\n  }\n  .ma5-m {\n    margin: 4rem;\n  }\n  .ma6-m {\n    margin: 8rem;\n  }\n  .ma7-m {\n    margin: 16rem;\n  }\n  .ml0-m {\n    margin-left: 0;\n  }\n  .ml1-m {\n    margin-left: 0.25rem;\n  }\n  .ml2-m {\n    margin-left: 0.5rem;\n  }\n  .ml3-m {\n    margin-left: 1rem;\n  }\n  .ml4-m {\n    margin-left: 2rem;\n  }\n  .ml5-m {\n    margin-left: 4rem;\n  }\n  .ml6-m {\n    margin-left: 8rem;\n  }\n  .ml7-m {\n    margin-left: 16rem;\n  }\n  .mr0-m {\n    margin-right: 0;\n  }\n  .mr1-m {\n    margin-right: 0.25rem;\n  }\n  .mr2-m {\n    margin-right: 0.5rem;\n  }\n  .mr3-m {\n    margin-right: 1rem;\n  }\n  .mr4-m {\n    margin-right: 2rem;\n  }\n  .mr5-m {\n    margin-right: 4rem;\n  }\n  .mr6-m {\n    margin-right: 8rem;\n  }\n  .mr7-m {\n    margin-right: 16rem;\n  }\n  .mb0-m {\n    margin-bottom: 0;\n  }\n  .mb1-m {\n    margin-bottom: 0.25rem;\n  }\n  .mb2-m {\n    margin-bottom: 0.5rem;\n  }\n  .mb3-m {\n    margin-bottom: 1rem;\n  }\n  .mb4-m {\n    margin-bottom: 2rem;\n  }\n  .mb5-m {\n    margin-bottom: 4rem;\n  }\n  .mb6-m {\n    margin-bottom: 8rem;\n  }\n  .mb7-m {\n    margin-bottom: 16rem;\n  }\n  .mt0-m {\n    margin-top: 0;\n  }\n  .mt1-m {\n    margin-top: 0.25rem;\n  }\n  .mt2-m {\n    margin-top: 0.5rem;\n  }\n  .mt3-m {\n    margin-top: 1rem;\n  }\n  .mt4-m {\n    margin-top: 2rem;\n  }\n  .mt5-m {\n    margin-top: 4rem;\n  }\n  .mt6-m {\n    margin-top: 8rem;\n  }\n  .mt7-m {\n    margin-top: 16rem;\n  }\n  .mv0-m {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .mv1-m {\n    margin-top: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n  .mv2-m {\n    margin-top: 0.5rem;\n    margin-bottom: 0.5rem;\n  }\n  .mv3-m {\n    margin-top: 1rem;\n    margin-bottom: 1rem;\n  }\n  .mv4-m {\n    margin-top: 2rem;\n    margin-bottom: 2rem;\n  }\n  .mv5-m {\n    margin-top: 4rem;\n    margin-bottom: 4rem;\n  }\n  .mv6-m {\n    margin-top: 8rem;\n    margin-bottom: 8rem;\n  }\n  .mv7-m {\n    margin-top: 16rem;\n    margin-bottom: 16rem;\n  }\n  .mh0-m {\n    margin-left: 0;\n    margin-right: 0;\n  }\n  .mh1-m {\n    margin-left: 0.25rem;\n    margin-right: 0.25rem;\n  }\n  .mh2-m {\n    margin-left: 0.5rem;\n    margin-right: 0.5rem;\n  }\n  .mh3-m {\n    margin-left: 1rem;\n    margin-right: 1rem;\n  }\n  .mh4-m {\n    margin-left: 2rem;\n    margin-right: 2rem;\n  }\n  .mh5-m {\n    margin-left: 4rem;\n    margin-right: 4rem;\n  }\n  .mh6-m {\n    margin-left: 8rem;\n    margin-right: 8rem;\n  }\n  .mh7-m {\n    margin-left: 16rem;\n    margin-right: 16rem;\n  }\n  .na1-m {\n    margin: -0.25rem;\n  }\n  .na2-m {\n    margin: -0.5rem;\n  }\n  .na3-m {\n    margin: -1rem;\n  }\n  .na4-m {\n    margin: -2rem;\n  }\n  .na5-m {\n    margin: -4rem;\n  }\n  .na6-m {\n    margin: -8rem;\n  }\n  .na7-m {\n    margin: -16rem;\n  }\n  .nl1-m {\n    margin-left: -0.25rem;\n  }\n  .nl2-m {\n    margin-left: -0.5rem;\n  }\n  .nl3-m {\n    margin-left: -1rem;\n  }\n  .nl4-m {\n    margin-left: -2rem;\n  }\n  .nl5-m {\n    margin-left: -4rem;\n  }\n  .nl6-m {\n    margin-left: -8rem;\n  }\n  .nl7-m {\n    margin-left: -16rem;\n  }\n  .nr1-m {\n    margin-right: -0.25rem;\n  }\n  .nr2-m {\n    margin-right: -0.5rem;\n  }\n  .nr3-m {\n    margin-right: -1rem;\n  }\n  .nr4-m {\n    margin-right: -2rem;\n  }\n  .nr5-m {\n    margin-right: -4rem;\n  }\n  .nr6-m {\n    margin-right: -8rem;\n  }\n  .nr7-m {\n    margin-right: -16rem;\n  }\n  .nb1-m {\n    margin-bottom: -0.25rem;\n  }\n  .nb2-m {\n    margin-bottom: -0.5rem;\n  }\n  .nb3-m {\n    margin-bottom: -1rem;\n  }\n  .nb4-m {\n    margin-bottom: -2rem;\n  }\n  .nb5-m {\n    margin-bottom: -4rem;\n  }\n  .nb6-m {\n    margin-bottom: -8rem;\n  }\n  .nb7-m {\n    margin-bottom: -16rem;\n  }\n  .nt1-m {\n    margin-top: -0.25rem;\n  }\n  .nt2-m {\n    margin-top: -0.5rem;\n  }\n  .nt3-m {\n    margin-top: -1rem;\n  }\n  .nt4-m {\n    margin-top: -2rem;\n  }\n  .nt5-m {\n    margin-top: -4rem;\n  }\n  .nt6-m {\n    margin-top: -8rem;\n  }\n  .nt7-m {\n    margin-top: -16rem;\n  }\n  .strike-m {\n    text-decoration: line-through;\n  }\n  .underline-m {\n    text-decoration: underline;\n  }\n  .no-underline-m {\n    text-decoration: none;\n  }\n  .tl-m {\n    text-align: left;\n  }\n  .tr-m {\n    text-align: right;\n  }\n  .tc-m {\n    text-align: center;\n  }\n  .tj-m {\n    text-align: justify;\n  }\n  .ttc-m {\n    text-transform: capitalize;\n  }\n  .ttl-m {\n    text-transform: lowercase;\n  }\n  .ttu-m {\n    text-transform: uppercase;\n  }\n  .ttn-m {\n    text-transform: none;\n  }\n  .f-6-m,\n  .f-headline-m {\n    font-size: 6rem;\n  }\n  .f-5-m,\n  .f-subheadline-m {\n    font-size: 5rem;\n  }\n  .f1-m {\n    font-size: 3rem;\n  }\n  .f2-m {\n    font-size: 2.25rem;\n  }\n  .f3-m {\n    font-size: 1.5rem;\n  }\n  .f4-m {\n    font-size: 1.25rem;\n  }\n  .f5-m {\n    font-size: 1rem;\n  }\n  .f6-m {\n    font-size: 0.875rem;\n  }\n  .f7-m {\n    font-size: 0.75rem;\n  }\n  .measure-m {\n    max-width: 30em;\n  }\n  .measure-wide-m {\n    max-width: 34em;\n  }\n  .measure-narrow-m {\n    max-width: 20em;\n  }\n  .indent-m {\n    text-indent: 1em;\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .small-caps-m {\n    font-variant: small-caps;\n  }\n  .truncate-m {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .center-m {\n    margin-right: auto;\n    margin-left: auto;\n  }\n  .mr-auto-m {\n    margin-right: auto;\n  }\n  .ml-auto-m {\n    margin-left: auto;\n  }\n  .clip-m {\n    position: fixed !important;\n    _position: absolute !important;\n    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n    clip: rect(1px, 1px, 1px, 1px);\n  }\n  .ws-normal-m {\n    white-space: normal;\n  }\n  .nowrap-m {\n    white-space: nowrap;\n  }\n  .pre-m {\n    white-space: pre;\n  }\n  .v-base-m {\n    vertical-align: baseline;\n  }\n  .v-mid-m {\n    vertical-align: middle;\n  }\n  .v-top-m {\n    vertical-align: top;\n  }\n  .v-btm-m {\n    vertical-align: bottom;\n  }\n}\n@media screen and (min-width: 60em) {\n  .aspect-ratio-l {\n    height: 0;\n    position: relative;\n  }\n  .aspect-ratio--16x9-l {\n    padding-bottom: 56.25%;\n  }\n  .aspect-ratio--9x16-l {\n    padding-bottom: 177.77%;\n  }\n  .aspect-ratio--4x3-l {\n    padding-bottom: 75%;\n  }\n  .aspect-ratio--3x4-l {\n    padding-bottom: 133.33%;\n  }\n  .aspect-ratio--6x4-l {\n    padding-bottom: 66.6%;\n  }\n  .aspect-ratio--4x6-l {\n    padding-bottom: 150%;\n  }\n  .aspect-ratio--8x5-l {\n    padding-bottom: 62.5%;\n  }\n  .aspect-ratio--5x8-l {\n    padding-bottom: 160%;\n  }\n  .aspect-ratio--7x5-l {\n    padding-bottom: 71.42%;\n  }\n  .aspect-ratio--5x7-l {\n    padding-bottom: 140%;\n  }\n  .aspect-ratio--1x1-l {\n    padding-bottom: 100%;\n  }\n  .aspect-ratio--object-l {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    z-index: 100;\n  }\n  .cover-l {\n    background-size: cover !important;\n  }\n  .contain-l {\n    background-size: contain !important;\n  }\n  .bg-center-l {\n    background-repeat: no-repeat;\n    background-position: center center;\n  }\n  .bg-top-l {\n    background-repeat: no-repeat;\n    background-position: top center;\n  }\n  .bg-right-l {\n    background-repeat: no-repeat;\n    background-position: center right;\n  }\n  .bg-bottom-l {\n    background-repeat: no-repeat;\n    background-position: bottom center;\n  }\n  .bg-left-l {\n    background-repeat: no-repeat;\n    background-position: center left;\n  }\n  .outline-l {\n    outline: 1px solid;\n  }\n  .outline-transparent-l {\n    outline: 1px solid transparent;\n  }\n  .outline-0-l {\n    outline: 0;\n  }\n  .ba-l {\n    border-style: solid;\n    border-width: 1px;\n  }\n  .bt-l {\n    border-top-style: solid;\n    border-top-width: 1px;\n  }\n  .br-l {\n    border-right-style: solid;\n    border-right-width: 1px;\n  }\n  .bb-l {\n    border-bottom-style: solid;\n    border-bottom-width: 1px;\n  }\n  .bl-l {\n    border-left-style: solid;\n    border-left-width: 1px;\n  }\n  .bn-l {\n    border-style: none;\n    border-width: 0;\n  }\n  .br0-l {\n    border-radius: 0;\n  }\n  .br1-l {\n    border-radius: 0.125rem;\n  }\n  .br2-l {\n    border-radius: 0.25rem;\n  }\n  .br3-l {\n    border-radius: 0.5rem;\n  }\n  .br4-l {\n    border-radius: 1rem;\n  }\n  .br-100-l {\n    border-radius: 100%;\n  }\n  .br-pill-l {\n    border-radius: 9999px;\n  }\n  .br--bottom-l {\n    border-top-left-radius: 0;\n    border-top-right-radius: 0;\n  }\n  .br--top-l {\n    border-bottom-left-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .br--right-l {\n    border-top-left-radius: 0;\n    border-bottom-left-radius: 0;\n  }\n  .br--left-l {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n  .b--dotted-l {\n    border-style: dotted;\n  }\n  .b--dashed-l {\n    border-style: dashed;\n  }\n  .b--solid-l {\n    border-style: solid;\n  }\n  .b--none-l {\n    border-style: none;\n  }\n  .bw0-l {\n    border-width: 0;\n  }\n  .bw1-l {\n    border-width: 0.125rem;\n  }\n  .bw2-l {\n    border-width: 0.25rem;\n  }\n  .bw3-l {\n    border-width: 0.5rem;\n  }\n  .bw4-l {\n    border-width: 1rem;\n  }\n  .bw5-l {\n    border-width: 2rem;\n  }\n  .bt-0-l {\n    border-top-width: 0;\n  }\n  .br-0-l {\n    border-right-width: 0;\n  }\n  .bb-0-l {\n    border-bottom-width: 0;\n  }\n  .bl-0-l {\n    border-left-width: 0;\n  }\n  .shadow-1-l {\n    box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-2-l {\n    box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-3-l {\n    box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, 0.2);\n  }\n  .shadow-4-l {\n    box-shadow: 2px 2px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .shadow-5-l {\n    box-shadow: 4px 4px 8px 0 rgba(0, 0, 0, 0.2);\n  }\n  .top-0-l {\n    top: 0;\n  }\n  .left-0-l {\n    left: 0;\n  }\n  .right-0-l {\n    right: 0;\n  }\n  .bottom-0-l {\n    bottom: 0;\n  }\n  .top-1-l {\n    top: 1rem;\n  }\n  .left-1-l {\n    left: 1rem;\n  }\n  .right-1-l {\n    right: 1rem;\n  }\n  .bottom-1-l {\n    bottom: 1rem;\n  }\n  .top-2-l {\n    top: 2rem;\n  }\n  .left-2-l {\n    left: 2rem;\n  }\n  .right-2-l {\n    right: 2rem;\n  }\n  .bottom-2-l {\n    bottom: 2rem;\n  }\n  .top--1-l {\n    top: -1rem;\n  }\n  .right--1-l {\n    right: -1rem;\n  }\n  .bottom--1-l {\n    bottom: -1rem;\n  }\n  .left--1-l {\n    left: -1rem;\n  }\n  .top--2-l {\n    top: -2rem;\n  }\n  .right--2-l {\n    right: -2rem;\n  }\n  .bottom--2-l {\n    bottom: -2rem;\n  }\n  .left--2-l {\n    left: -2rem;\n  }\n  .absolute--fill-l {\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n  }\n  .cl-l {\n    clear: left;\n  }\n  .cr-l {\n    clear: right;\n  }\n  .cb-l {\n    clear: both;\n  }\n  .cn-l {\n    clear: none;\n  }\n  .dn-l {\n    display: none;\n  }\n  .di-l {\n    display: inline;\n  }\n  .db-l {\n    display: block;\n  }\n  .dib-l {\n    display: inline-block;\n  }\n  .dit-l {\n    display: inline-table;\n  }\n  .dt-l {\n    display: table;\n  }\n  .dtc-l {\n    display: table-cell;\n  }\n  .dt-row-l {\n    display: table-row;\n  }\n  .dt-row-group-l {\n    display: table-row-group;\n  }\n  .dt-column-l {\n    display: table-column;\n  }\n  .dt-column-group-l {\n    display: table-column-group;\n  }\n  .dt--fixed-l {\n    table-layout: fixed;\n    width: 100%;\n  }\n  .flex-l {\n    display: flex;\n  }\n  .inline-flex-l {\n    display: inline-flex;\n  }\n  .flex-auto-l {\n    flex: 1 1 auto;\n    min-width: 0; /* 1 */\n    min-height: 0; /* 1 */\n  }\n  .flex-none-l {\n    flex: none;\n  }\n  .flex-column-l {\n    flex-direction: column;\n  }\n  .flex-row-l {\n    flex-direction: row;\n  }\n  .flex-wrap-l {\n    flex-wrap: wrap;\n  }\n  .flex-nowrap-l {\n    flex-wrap: nowrap;\n  }\n  .flex-wrap-reverse-l {\n    flex-wrap: wrap-reverse;\n  }\n  .flex-column-reverse-l {\n    flex-direction: column-reverse;\n  }\n  .flex-row-reverse-l {\n    flex-direction: row-reverse;\n  }\n  .items-start-l {\n    align-items: flex-start;\n  }\n  .items-end-l {\n    align-items: flex-end;\n  }\n  .items-center-l {\n    align-items: center;\n  }\n  .items-baseline-l {\n    align-items: baseline;\n  }\n  .items-stretch-l {\n    align-items: stretch;\n  }\n  .self-start-l {\n    align-self: flex-start;\n  }\n  .self-end-l {\n    align-self: flex-end;\n  }\n  .self-center-l {\n    align-self: center;\n  }\n  .self-baseline-l {\n    align-self: baseline;\n  }\n  .self-stretch-l {\n    align-self: stretch;\n  }\n  .justify-start-l {\n    justify-content: flex-start;\n  }\n  .justify-end-l {\n    justify-content: flex-end;\n  }\n  .justify-center-l {\n    justify-content: center;\n  }\n  .justify-between-l {\n    justify-content: space-between;\n  }\n  .justify-around-l {\n    justify-content: space-around;\n  }\n  .content-start-l {\n    align-content: flex-start;\n  }\n  .content-end-l {\n    align-content: flex-end;\n  }\n  .content-center-l {\n    align-content: center;\n  }\n  .content-between-l {\n    align-content: space-between;\n  }\n  .content-around-l {\n    align-content: space-around;\n  }\n  .content-stretch-l {\n    align-content: stretch;\n  }\n  .order-0-l {\n    order: 0;\n  }\n  .order-1-l {\n    order: 1;\n  }\n  .order-2-l {\n    order: 2;\n  }\n  .order-3-l {\n    order: 3;\n  }\n  .order-4-l {\n    order: 4;\n  }\n  .order-5-l {\n    order: 5;\n  }\n  .order-6-l {\n    order: 6;\n  }\n  .order-7-l {\n    order: 7;\n  }\n  .order-8-l {\n    order: 8;\n  }\n  .order-last-l {\n    order: 99999;\n  }\n  .flex-grow-0-l {\n    flex-grow: 0;\n  }\n  .flex-grow-1-l {\n    flex-grow: 1;\n  }\n  .flex-shrink-0-l {\n    flex-shrink: 0;\n  }\n  .flex-shrink-1-l {\n    flex-shrink: 1;\n  }\n  .fl-l {\n    float: left;\n    _display: inline;\n  }\n  .fr-l {\n    float: right;\n    _display: inline;\n  }\n  .fn-l {\n    float: none;\n  }\n  .i-l {\n    font-style: italic;\n  }\n  .fs-normal-l {\n    font-style: normal;\n  }\n  .normal-l {\n    font-weight: normal;\n  }\n  .b-l {\n    font-weight: bold;\n  }\n  .fw1-l {\n    font-weight: 100;\n  }\n  .fw2-l {\n    font-weight: 200;\n  }\n  .fw3-l {\n    font-weight: 300;\n  }\n  .fw4-l {\n    font-weight: 400;\n  }\n  .fw5-l {\n    font-weight: 500;\n  }\n  .fw6-l {\n    font-weight: 600;\n  }\n  .fw7-l {\n    font-weight: 700;\n  }\n  .fw8-l {\n    font-weight: 800;\n  }\n  .fw9-l {\n    font-weight: 900;\n  }\n  .h1-l {\n    height: 1rem;\n  }\n  .h2-l {\n    height: 2rem;\n  }\n  .h3-l {\n    height: 4rem;\n  }\n  .h4-l {\n    height: 8rem;\n  }\n  .h5-l {\n    height: 16rem;\n  }\n  .h-25-l {\n    height: 25%;\n  }\n  .h-50-l {\n    height: 50%;\n  }\n  .h-75-l {\n    height: 75%;\n  }\n  .h-100-l {\n    height: 100%;\n  }\n  .min-h-100-l {\n    min-height: 100%;\n  }\n  .vh-25-l {\n    height: 25vh;\n  }\n  .vh-50-l {\n    height: 50vh;\n  }\n  .vh-75-l {\n    height: 75vh;\n  }\n  .vh-100-l {\n    height: 100vh;\n  }\n  .min-vh-100-l {\n    min-height: 100vh;\n  }\n  .h-auto-l {\n    height: auto;\n  }\n  .h-inherit-l {\n    height: inherit;\n  }\n  .tracked-l {\n    letter-spacing: 0.1em;\n  }\n  .tracked-tight-l {\n    letter-spacing: -0.05em;\n  }\n  .tracked-mega-l {\n    letter-spacing: 0.25em;\n  }\n  .lh-solid-l {\n    line-height: 1;\n  }\n  .lh-title-l {\n    line-height: 1.25;\n  }\n  .lh-copy-l {\n    line-height: 1.5;\n  }\n  .mw-100-l {\n    max-width: 100%;\n  }\n  .mw1-l {\n    max-width: 1rem;\n  }\n  .mw2-l {\n    max-width: 2rem;\n  }\n  .mw3-l {\n    max-width: 4rem;\n  }\n  .mw4-l {\n    max-width: 8rem;\n  }\n  .mw5-l {\n    max-width: 16rem;\n  }\n  .mw6-l {\n    max-width: 32rem;\n  }\n  .mw7-l {\n    max-width: 48rem;\n  }\n  .mw8-l {\n    max-width: 64rem;\n  }\n  .mw9-l {\n    max-width: 96rem;\n  }\n  .mw-none-l {\n    max-width: none;\n  }\n  .w1-l {\n    width: 1rem;\n  }\n  .w2-l {\n    width: 2rem;\n  }\n  .w3-l {\n    width: 4rem;\n  }\n  .w4-l {\n    width: 8rem;\n  }\n  .w5-l {\n    width: 16rem;\n  }\n  .w-10-l {\n    width: 10%;\n  }\n  .w-20-l {\n    width: 20%;\n  }\n  .w-25-l {\n    width: 25%;\n  }\n  .w-30-l {\n    width: 30%;\n  }\n  .w-33-l {\n    width: 33%;\n  }\n  .w-34-l {\n    width: 34%;\n  }\n  .w-40-l {\n    width: 40%;\n  }\n  .w-50-l {\n    width: 50%;\n  }\n  .w-60-l {\n    width: 60%;\n  }\n  .w-70-l {\n    width: 70%;\n  }\n  .w-75-l {\n    width: 75%;\n  }\n  .w-80-l {\n    width: 80%;\n  }\n  .w-90-l {\n    width: 90%;\n  }\n  .w-100-l {\n    width: 100%;\n  }\n  .w-third-l {\n    width: calc(100% / 3);\n  }\n  .w-two-thirds-l {\n    width: calc(100% / 1.5);\n  }\n  .w-auto-l {\n    width: auto;\n  }\n  .overflow-visible-l {\n    overflow: visible;\n  }\n  .overflow-hidden-l {\n    overflow: hidden;\n  }\n  .overflow-scroll-l {\n    overflow: scroll;\n  }\n  .overflow-auto-l {\n    overflow: auto;\n  }\n  .overflow-x-visible-l {\n    overflow-x: visible;\n  }\n  .overflow-x-hidden-l {\n    overflow-x: hidden;\n  }\n  .overflow-x-scroll-l {\n    overflow-x: scroll;\n  }\n  .overflow-x-auto-l {\n    overflow-x: auto;\n  }\n  .overflow-y-visible-l {\n    overflow-y: visible;\n  }\n  .overflow-y-hidden-l {\n    overflow-y: hidden;\n  }\n  .overflow-y-scroll-l {\n    overflow-y: scroll;\n  }\n  .overflow-y-auto-l {\n    overflow-y: auto;\n  }\n  .static-l {\n    position: static;\n  }\n  .relative-l {\n    position: relative;\n  }\n  .absolute-l {\n    position: absolute;\n  }\n  .fixed-l {\n    position: fixed;\n  }\n  .rotate-45-l {\n    -webkit-transform: rotate(45deg);\n    transform: rotate(45deg);\n  }\n  .rotate-90-l {\n    -webkit-transform: rotate(90deg);\n    transform: rotate(90deg);\n  }\n  .rotate-135-l {\n    -webkit-transform: rotate(135deg);\n    transform: rotate(135deg);\n  }\n  .rotate-180-l {\n    -webkit-transform: rotate(180deg);\n    transform: rotate(180deg);\n  }\n  .rotate-225-l {\n    -webkit-transform: rotate(225deg);\n    transform: rotate(225deg);\n  }\n  .rotate-270-l {\n    -webkit-transform: rotate(270deg);\n    transform: rotate(270deg);\n  }\n  .rotate-315-l {\n    -webkit-transform: rotate(315deg);\n    transform: rotate(315deg);\n  }\n  .pa0-l {\n    padding: 0;\n  }\n  .pa1-l {\n    padding: 0.25rem;\n  }\n  .pa2-l {\n    padding: 0.5rem;\n  }\n  .pa3-l {\n    padding: 1rem;\n  }\n  .pa4-l {\n    padding: 2rem;\n  }\n  .pa5-l {\n    padding: 4rem;\n  }\n  .pa6-l {\n    padding: 8rem;\n  }\n  .pa7-l {\n    padding: 16rem;\n  }\n  .pl0-l {\n    padding-left: 0;\n  }\n  .pl1-l {\n    padding-left: 0.25rem;\n  }\n  .pl2-l {\n    padding-left: 0.5rem;\n  }\n  .pl3-l {\n    padding-left: 1rem;\n  }\n  .pl4-l {\n    padding-left: 2rem;\n  }\n  .pl5-l {\n    padding-left: 4rem;\n  }\n  .pl6-l {\n    padding-left: 8rem;\n  }\n  .pl7-l {\n    padding-left: 16rem;\n  }\n  .pr0-l {\n    padding-right: 0;\n  }\n  .pr1-l {\n    padding-right: 0.25rem;\n  }\n  .pr2-l {\n    padding-right: 0.5rem;\n  }\n  .pr3-l {\n    padding-right: 1rem;\n  }\n  .pr4-l {\n    padding-right: 2rem;\n  }\n  .pr5-l {\n    padding-right: 4rem;\n  }\n  .pr6-l {\n    padding-right: 8rem;\n  }\n  .pr7-l {\n    padding-right: 16rem;\n  }\n  .pb0-l {\n    padding-bottom: 0;\n  }\n  .pb1-l {\n    padding-bottom: 0.25rem;\n  }\n  .pb2-l {\n    padding-bottom: 0.5rem;\n  }\n  .pb3-l {\n    padding-bottom: 1rem;\n  }\n  .pb4-l {\n    padding-bottom: 2rem;\n  }\n  .pb5-l {\n    padding-bottom: 4rem;\n  }\n  .pb6-l {\n    padding-bottom: 8rem;\n  }\n  .pb7-l {\n    padding-bottom: 16rem;\n  }\n  .pt0-l {\n    padding-top: 0;\n  }\n  .pt1-l {\n    padding-top: 0.25rem;\n  }\n  .pt2-l {\n    padding-top: 0.5rem;\n  }\n  .pt3-l {\n    padding-top: 1rem;\n  }\n  .pt4-l {\n    padding-top: 2rem;\n  }\n  .pt5-l {\n    padding-top: 4rem;\n  }\n  .pt6-l {\n    padding-top: 8rem;\n  }\n  .pt7-l {\n    padding-top: 16rem;\n  }\n  .pv0-l {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n  .pv1-l {\n    padding-top: 0.25rem;\n    padding-bottom: 0.25rem;\n  }\n  .pv2-l {\n    padding-top: 0.5rem;\n    padding-bottom: 0.5rem;\n  }\n  .pv3-l {\n    padding-top: 1rem;\n    padding-bottom: 1rem;\n  }\n  .pv4-l {\n    padding-top: 2rem;\n    padding-bottom: 2rem;\n  }\n  .pv5-l {\n    padding-top: 4rem;\n    padding-bottom: 4rem;\n  }\n  .pv6-l {\n    padding-top: 8rem;\n    padding-bottom: 8rem;\n  }\n  .pv7-l {\n    padding-top: 16rem;\n    padding-bottom: 16rem;\n  }\n  .ph0-l {\n    padding-left: 0;\n    padding-right: 0;\n  }\n  .ph1-l {\n    padding-left: 0.25rem;\n    padding-right: 0.25rem;\n  }\n  .ph2-l {\n    padding-left: 0.5rem;\n    padding-right: 0.5rem;\n  }\n  .ph3-l {\n    padding-left: 1rem;\n    padding-right: 1rem;\n  }\n  .ph4-l {\n    padding-left: 2rem;\n    padding-right: 2rem;\n  }\n  .ph5-l {\n    padding-left: 4rem;\n    padding-right: 4rem;\n  }\n  .ph6-l {\n    padding-left: 8rem;\n    padding-right: 8rem;\n  }\n  .ph7-l {\n    padding-left: 16rem;\n    padding-right: 16rem;\n  }\n  .ma0-l {\n    margin: 0;\n  }\n  .ma1-l {\n    margin: 0.25rem;\n  }\n  .ma2-l {\n    margin: 0.5rem;\n  }\n  .ma3-l {\n    margin: 1rem;\n  }\n  .ma4-l {\n    margin: 2rem;\n  }\n  .ma5-l {\n    margin: 4rem;\n  }\n  .ma6-l {\n    margin: 8rem;\n  }\n  .ma7-l {\n    margin: 16rem;\n  }\n  .ml0-l {\n    margin-left: 0;\n  }\n  .ml1-l {\n    margin-left: 0.25rem;\n  }\n  .ml2-l {\n    margin-left: 0.5rem;\n  }\n  .ml3-l {\n    margin-left: 1rem;\n  }\n  .ml4-l {\n    margin-left: 2rem;\n  }\n  .ml5-l {\n    margin-left: 4rem;\n  }\n  .ml6-l {\n    margin-left: 8rem;\n  }\n  .ml7-l {\n    margin-left: 16rem;\n  }\n  .mr0-l {\n    margin-right: 0;\n  }\n  .mr1-l {\n    margin-right: 0.25rem;\n  }\n  .mr2-l {\n    margin-right: 0.5rem;\n  }\n  .mr3-l {\n    margin-right: 1rem;\n  }\n  .mr4-l {\n    margin-right: 2rem;\n  }\n  .mr5-l {\n    margin-right: 4rem;\n  }\n  .mr6-l {\n    margin-right: 8rem;\n  }\n  .mr7-l {\n    margin-right: 16rem;\n  }\n  .mb0-l {\n    margin-bottom: 0;\n  }\n  .mb1-l {\n    margin-bottom: 0.25rem;\n  }\n  .mb2-l {\n    margin-bottom: 0.5rem;\n  }\n  .mb3-l {\n    margin-bottom: 1rem;\n  }\n  .mb4-l {\n    margin-bottom: 2rem;\n  }\n  .mb5-l {\n    margin-bottom: 4rem;\n  }\n  .mb6-l {\n    margin-bottom: 8rem;\n  }\n  .mb7-l {\n    margin-bottom: 16rem;\n  }\n  .mt0-l {\n    margin-top: 0;\n  }\n  .mt1-l {\n    margin-top: 0.25rem;\n  }\n  .mt2-l {\n    margin-top: 0.5rem;\n  }\n  .mt3-l {\n    margin-top: 1rem;\n  }\n  .mt4-l {\n    margin-top: 2rem;\n  }\n  .mt5-l {\n    margin-top: 4rem;\n  }\n  .mt6-l {\n    margin-top: 8rem;\n  }\n  .mt7-l {\n    margin-top: 16rem;\n  }\n  .mv0-l {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .mv1-l {\n    margin-top: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n  .mv2-l {\n    margin-top: 0.5rem;\n    margin-bottom: 0.5rem;\n  }\n  .mv3-l {\n    margin-top: 1rem;\n    margin-bottom: 1rem;\n  }\n  .mv4-l {\n    margin-top: 2rem;\n    margin-bottom: 2rem;\n  }\n  .mv5-l {\n    margin-top: 4rem;\n    margin-bottom: 4rem;\n  }\n  .mv6-l {\n    margin-top: 8rem;\n    margin-bottom: 8rem;\n  }\n  .mv7-l {\n    margin-top: 16rem;\n    margin-bottom: 16rem;\n  }\n  .mh0-l {\n    margin-left: 0;\n    margin-right: 0;\n  }\n  .mh1-l {\n    margin-left: 0.25rem;\n    margin-right: 0.25rem;\n  }\n  .mh2-l {\n    margin-left: 0.5rem;\n    margin-right: 0.5rem;\n  }\n  .mh3-l {\n    margin-left: 1rem;\n    margin-right: 1rem;\n  }\n  .mh4-l {\n    margin-left: 2rem;\n    margin-right: 2rem;\n  }\n  .mh5-l {\n    margin-left: 4rem;\n    margin-right: 4rem;\n  }\n  .mh6-l {\n    margin-left: 8rem;\n    margin-right: 8rem;\n  }\n  .mh7-l {\n    margin-left: 16rem;\n    margin-right: 16rem;\n  }\n  .na1-l {\n    margin: -0.25rem;\n  }\n  .na2-l {\n    margin: -0.5rem;\n  }\n  .na3-l {\n    margin: -1rem;\n  }\n  .na4-l {\n    margin: -2rem;\n  }\n  .na5-l {\n    margin: -4rem;\n  }\n  .na6-l {\n    margin: -8rem;\n  }\n  .na7-l {\n    margin: -16rem;\n  }\n  .nl1-l {\n    margin-left: -0.25rem;\n  }\n  .nl2-l {\n    margin-left: -0.5rem;\n  }\n  .nl3-l {\n    margin-left: -1rem;\n  }\n  .nl4-l {\n    margin-left: -2rem;\n  }\n  .nl5-l {\n    margin-left: -4rem;\n  }\n  .nl6-l {\n    margin-left: -8rem;\n  }\n  .nl7-l {\n    margin-left: -16rem;\n  }\n  .nr1-l {\n    margin-right: -0.25rem;\n  }\n  .nr2-l {\n    margin-right: -0.5rem;\n  }\n  .nr3-l {\n    margin-right: -1rem;\n  }\n  .nr4-l {\n    margin-right: -2rem;\n  }\n  .nr5-l {\n    margin-right: -4rem;\n  }\n  .nr6-l {\n    margin-right: -8rem;\n  }\n  .nr7-l {\n    margin-right: -16rem;\n  }\n  .nb1-l {\n    margin-bottom: -0.25rem;\n  }\n  .nb2-l {\n    margin-bottom: -0.5rem;\n  }\n  .nb3-l {\n    margin-bottom: -1rem;\n  }\n  .nb4-l {\n    margin-bottom: -2rem;\n  }\n  .nb5-l {\n    margin-bottom: -4rem;\n  }\n  .nb6-l {\n    margin-bottom: -8rem;\n  }\n  .nb7-l {\n    margin-bottom: -16rem;\n  }\n  .nt1-l {\n    margin-top: -0.25rem;\n  }\n  .nt2-l {\n    margin-top: -0.5rem;\n  }\n  .nt3-l {\n    margin-top: -1rem;\n  }\n  .nt4-l {\n    margin-top: -2rem;\n  }\n  .nt5-l {\n    margin-top: -4rem;\n  }\n  .nt6-l {\n    margin-top: -8rem;\n  }\n  .nt7-l {\n    margin-top: -16rem;\n  }\n  .strike-l {\n    text-decoration: line-through;\n  }\n  .underline-l {\n    text-decoration: underline;\n  }\n  .no-underline-l {\n    text-decoration: none;\n  }\n  .tl-l {\n    text-align: left;\n  }\n  .tr-l {\n    text-align: right;\n  }\n  .tc-l {\n    text-align: center;\n  }\n  .tj-l {\n    text-align: justify;\n  }\n  .ttc-l {\n    text-transform: capitalize;\n  }\n  .ttl-l {\n    text-transform: lowercase;\n  }\n  .ttu-l {\n    text-transform: uppercase;\n  }\n  .ttn-l {\n    text-transform: none;\n  }\n  .f-6-l,\n  .f-headline-l {\n    font-size: 6rem;\n  }\n  .f-5-l,\n  .f-subheadline-l {\n    font-size: 5rem;\n  }\n  .f1-l {\n    font-size: 3rem;\n  }\n  .f2-l {\n    font-size: 2.25rem;\n  }\n  .f3-l {\n    font-size: 1.5rem;\n  }\n  .f4-l {\n    font-size: 1.25rem;\n  }\n  .f5-l {\n    font-size: 1rem;\n  }\n  .f6-l {\n    font-size: 0.875rem;\n  }\n  .f7-l {\n    font-size: 0.75rem;\n  }\n  .measure-l {\n    max-width: 30em;\n  }\n  .measure-wide-l {\n    max-width: 34em;\n  }\n  .measure-narrow-l {\n    max-width: 20em;\n  }\n  .indent-l {\n    text-indent: 1em;\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n  .small-caps-l {\n    font-variant: small-caps;\n  }\n  .truncate-l {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .center-l {\n    margin-right: auto;\n    margin-left: auto;\n  }\n  .mr-auto-l {\n    margin-right: auto;\n  }\n  .ml-auto-l {\n    margin-left: auto;\n  }\n  .clip-l {\n    position: fixed !important;\n    _position: absolute !important;\n    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n    clip: rect(1px, 1px, 1px, 1px);\n  }\n  .ws-normal-l {\n    white-space: normal;\n  }\n  .nowrap-l {\n    white-space: nowrap;\n  }\n  .pre-l {\n    white-space: pre;\n  }\n  .v-base-l {\n    vertical-align: baseline;\n  }\n  .v-mid-l {\n    vertical-align: middle;\n  }\n  .v-top-l {\n    vertical-align: top;\n  }\n  .v-btm-l {\n    vertical-align: bottom;\n  }\n}"
  },
  {
    "path": "examples/github_search/common_github_search/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../../analysis_options.yaml\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/common_github_search.dart",
    "content": "export 'src/github_cache.dart';\nexport 'src/github_client.dart';\nexport 'src/github_repository.dart';\nexport 'src/github_search_bloc/github_search_bloc.dart';\nexport 'src/github_search_bloc/github_search_event.dart';\nexport 'src/github_search_bloc/github_search_state.dart';\nexport 'src/models/models.dart';\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_cache.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\n\nclass GithubCache {\n  final _cache = <String, SearchResult>{};\n\n  SearchResult? get(String term) => _cache[term];\n\n  void set(String term, SearchResult result) => _cache[term] = result;\n\n  bool contains(String term) => _cache.containsKey(term);\n\n  void remove(String term) => _cache.remove(term);\n\n  void close() {\n    _cache.clear();\n  }\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_client.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\n\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:http/http.dart' as http;\n\nclass GithubClient {\n  GithubClient({\n    http.Client? httpClient,\n    this.baseUrl = 'https://api.github.com/search/repositories?q=',\n  }) : _httpClient = httpClient ?? http.Client();\n\n  final String baseUrl;\n  final http.Client _httpClient;\n\n  Future<SearchResult> search(String term) async {\n    final response = await _httpClient.get(Uri.parse('$baseUrl$term'));\n    final results = json.decode(response.body) as Map<String, dynamic>;\n\n    if (response.statusCode == 200) {\n      return SearchResult.fromJson(results);\n    } else {\n      throw SearchResultError.fromJson(results);\n    }\n  }\n\n  void close() {\n    _httpClient.close();\n  }\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_repository.dart",
    "content": "import 'dart:async';\n\nimport 'package:common_github_search/common_github_search.dart';\n\nclass GithubRepository {\n  GithubRepository({GithubCache? cache, GithubClient? client})\n    : _cache = cache ?? GithubCache(),\n      _client = client ?? GithubClient();\n\n  final GithubCache _cache;\n  final GithubClient _client;\n\n  Future<SearchResult> search(String term) async {\n    final cachedResult = _cache.get(term);\n    if (cachedResult != null) {\n      return cachedResult;\n    }\n    final result = await _client.search(term);\n    _cache.set(term, result);\n    return result;\n  }\n\n  void dispose() {\n    _cache.close();\n    _client.close();\n  }\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:common_github_search/common_github_search.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\nconst _duration = Duration(milliseconds: 300);\n\nEventTransformer<Event> debounce<Event>(Duration duration) {\n  return (events, mapper) => events.debounce(duration).switchMap(mapper);\n}\n\nclass GithubSearchBloc extends Bloc<GithubSearchEvent, GithubSearchState> {\n  GithubSearchBloc({required GithubRepository githubRepository})\n    : _githubRepository = githubRepository,\n      super(SearchStateEmpty()) {\n    on<TextChanged>(_onTextChanged, transformer: debounce(_duration));\n  }\n\n  final GithubRepository _githubRepository;\n\n  Future<void> _onTextChanged(\n    TextChanged event,\n    Emitter<GithubSearchState> emit,\n  ) async {\n    final searchTerm = event.text;\n\n    if (searchTerm.isEmpty) return emit(SearchStateEmpty());\n\n    emit(SearchStateLoading());\n\n    try {\n      final results = await _githubRepository.search(searchTerm);\n      emit(SearchStateSuccess(results.items));\n    } catch (error) {\n      emit(\n        error is SearchResultError\n            ? SearchStateError(error.message)\n            : const SearchStateError('something went wrong'),\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_event.dart",
    "content": "import 'package:equatable/equatable.dart';\n\nsealed class GithubSearchEvent extends Equatable {\n  const GithubSearchEvent();\n}\n\nfinal class TextChanged extends GithubSearchEvent {\n  const TextChanged({required this.text});\n\n  final String text;\n\n  @override\n  List<Object> get props => [text];\n\n  @override\n  String toString() => 'TextChanged { text: $text }';\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_state.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\nimport 'package:equatable/equatable.dart';\n\nsealed class GithubSearchState extends Equatable {\n  const GithubSearchState();\n\n  @override\n  List<Object> get props => [];\n}\n\nfinal class SearchStateEmpty extends GithubSearchState {}\n\nfinal class SearchStateLoading extends GithubSearchState {}\n\nfinal class SearchStateSuccess extends GithubSearchState {\n  const SearchStateSuccess(this.items);\n\n  final List<SearchResultItem> items;\n\n  @override\n  List<Object> get props => [items];\n\n  @override\n  String toString() => 'SearchStateSuccess { items: ${items.length} }';\n}\n\nfinal class SearchStateError extends GithubSearchState {\n  const SearchStateError(this.error);\n\n  final String error;\n\n  @override\n  List<Object> get props => [error];\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/models/github_user.dart",
    "content": "class GithubUser {\n  const GithubUser({\n    required this.login,\n    required this.avatarUrl,\n  });\n\n  factory GithubUser.fromJson(Map<String, dynamic> json) {\n    return GithubUser(\n      login: json['login'] as String,\n      avatarUrl: json['avatar_url'] as String,\n    );\n  }\n\n  final String login;\n  final String avatarUrl;\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/models/models.dart",
    "content": "export 'github_user.dart';\nexport 'search_result.dart';\nexport 'search_result_error.dart';\nexport 'search_result_item.dart';\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/models/search_result.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\n\nclass SearchResult {\n  const SearchResult({required this.items});\n\n  factory SearchResult.fromJson(Map<String, dynamic> json) {\n    final items = (json['items'] as List<dynamic>)\n        .map(\n          (dynamic item) =>\n              SearchResultItem.fromJson(item as Map<String, dynamic>),\n        )\n        .toList();\n    return SearchResult(items: items);\n  }\n\n  final List<SearchResultItem> items;\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/models/search_result_error.dart",
    "content": "class SearchResultError implements Exception {\n  SearchResultError({required this.message});\n\n  factory SearchResultError.fromJson(Map<String, dynamic> json) {\n    return SearchResultError(\n      message: json['message'] as String,\n    );\n  }\n\n  final String message;\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/lib/src/models/search_result_item.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\n\nclass SearchResultItem {\n  const SearchResultItem({\n    required this.fullName,\n    required this.htmlUrl,\n    required this.owner,\n  });\n\n  factory SearchResultItem.fromJson(Map<String, dynamic> json) {\n    return SearchResultItem(\n      fullName: json['full_name'] as String,\n      htmlUrl: json['html_url'] as String,\n      owner: GithubUser.fromJson(json['owner'] as Map<String, dynamic>),\n    );\n  }\n\n  final String fullName;\n  final String htmlUrl;\n  final GithubUser owner;\n}\n"
  },
  {
    "path": "examples/github_search/common_github_search/pubspec.yaml",
    "content": "name: common_github_search\ndescription: Shared Code between AngularDart and Flutter\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  equatable: ^2.0.0\n  http: ^1.0.0\n  stream_transform: ^2.0.0\ndev_dependencies:\n  bloc_lint: ^0.3.0\n"
  },
  {
    "path": "examples/github_search/common_github_search/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../../packages/bloc\n  bloc_lint:\n    path: ../../../packages/bloc_lint\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_dir/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.pub-cache/\n.pub/\n/build/\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/README.md",
    "content": "[![build](https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg)](https://github.com/felangel/bloc/actions)\n\n# flutter_github_search\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../../analysis_options.yaml\n\nanalyzer:\n  exclude: [build/**]\n\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/ios/.gitignore",
    "content": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '13.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\n  target 'RunnerTests' do\n    inherit! :search_paths\n  end\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/lib/main.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_github_search/search_form.dart';\n\nvoid main() => runApp(const App());\n\nclass App extends StatelessWidget {\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider(\n      create: (_) => GithubRepository(),\n      dispose: (repository) => repository.dispose(),\n      child: MaterialApp(\n        title: 'GitHub Search',\n        home: Scaffold(\n          appBar: AppBar(title: const Text('GitHub Search')),\n          body: BlocProvider(\n            create: (context) => GithubSearchBloc(\n              githubRepository: context.read<GithubRepository>(),\n            ),\n            child: const SearchForm(),\n          ),\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/lib/search_form.dart",
    "content": "import 'package:common_github_search/common_github_search.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:url_launcher/url_launcher.dart';\n\nclass SearchForm extends StatelessWidget {\n  const SearchForm({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      children: <Widget>[\n        _SearchBar(),\n        _SearchBody(),\n      ],\n    );\n  }\n}\n\nclass _SearchBar extends StatefulWidget {\n  @override\n  State<_SearchBar> createState() => _SearchBarState();\n}\n\nclass _SearchBarState extends State<_SearchBar> {\n  final _textController = TextEditingController();\n  late GithubSearchBloc _githubSearchBloc;\n\n  @override\n  void initState() {\n    super.initState();\n    _githubSearchBloc = context.read<GithubSearchBloc>();\n  }\n\n  @override\n  void dispose() {\n    _textController.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return TextField(\n      controller: _textController,\n      autocorrect: false,\n      onChanged: (text) {\n        _githubSearchBloc.add(\n          TextChanged(text: text),\n        );\n      },\n      decoration: InputDecoration(\n        prefixIcon: const Icon(Icons.search),\n        suffixIcon: GestureDetector(\n          onTap: _onClearTapped,\n          child: const Icon(Icons.clear),\n        ),\n        border: InputBorder.none,\n        hintText: 'Enter a search term',\n      ),\n    );\n  }\n\n  void _onClearTapped() {\n    _textController.text = '';\n    _githubSearchBloc.add(const TextChanged(text: ''));\n  }\n}\n\nclass _SearchBody extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<GithubSearchBloc, GithubSearchState>(\n      builder: (context, state) {\n        return switch (state) {\n          SearchStateEmpty() => const Text('Please enter a term to begin'),\n          SearchStateLoading() => const CircularProgressIndicator.adaptive(),\n          SearchStateError() => Text(state.error),\n          SearchStateSuccess() =>\n            state.items.isEmpty\n                ? const Text('No Results')\n                : Expanded(child: _SearchResults(items: state.items)),\n        };\n      },\n    );\n  }\n}\n\nclass _SearchResults extends StatelessWidget {\n  const _SearchResults({required this.items});\n\n  final List<SearchResultItem> items;\n\n  @override\n  Widget build(BuildContext context) {\n    return ListView.builder(\n      itemCount: items.length,\n      itemBuilder: (BuildContext context, int index) {\n        return _SearchResultItem(item: items[index]);\n      },\n    );\n  }\n}\n\nclass _SearchResultItem extends StatelessWidget {\n  const _SearchResultItem({required this.item});\n\n  final SearchResultItem item;\n\n  @override\n  Widget build(BuildContext context) {\n    return ListTile(\n      leading: CircleAvatar(\n        child: Image.network(item.owner.avatarUrl),\n      ),\n      title: Text(item.fullName),\n      onTap: () => launchUrl(Uri.parse(item.htmlUrl)),\n    );\n  }\n}\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/pubspec.yaml",
    "content": "name: flutter_github_search\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.10.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  common_github_search:\n    path: ../common_github_search\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.0.1\n  url_launcher: ^6.0.0\n\nflutter:\n  uses-material-design: true\ndev_dependencies:\n  bloc_lint: ^0.3.0\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../../packages/bloc\n  bloc_lint:\n    path: ../../../packages/bloc_lint\n  flutter_bloc:\n    path: ../../../packages/flutter_bloc\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"flutter_github_search\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>flutter_github_search</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/github_search/flutter_github_search/web/manifest.json",
    "content": "{\n    \"name\": \"flutter_github_search\",\n    \"short_name\": \"flutter_github_search\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "extensions/intellij/README.md",
    "content": "# Bloc Plugin for IntelliJ and Android Studio\n\n![dialog](https://github.com/felangel/bloc/raw/master/extensions/intellij/assets/dialog.png)\n\n## Introduction\n\nBloc plugin for [IntelliJ](https://www.jetbrains.com/idea/) and [Android Studio](https://developer.android.com/studio/) with support for the [Bloc Library](https://bloclibrary.dev) and provides tools for effectively creating Blocs and Cubits for both [Flutter](https://flutter.dev/) and [AngularDart](https://angulardart.dev/) apps.\n\n## Installation\n\nYou can find the plugin in the official IntelliJ and Android Studio marketplace:\n\n- [Bloc](https://plugins.jetbrains.com/plugin/12129-bloc)\n\n### How to use\n\nSimply right click on the File Project view, go to `New -> Bloc Class`, give it a name, select if you want to use [Equatable](https://github.com/felangel/equatable), and click on `OK` to see all the boilerplate generated.\n\n### Quick code action\n\nWrapping a widget is also possible with `Alt + ENTER` shortcut.\nIf you wish to disable this quick code action `(Bloc) Wrap with` you can do it so by going to\n`Settings - Editor - Intentions - Bloc`.\n\n![intention_settings](https://github.com/felangel/bloc/raw/master/extensions/intellij/assets/intention_settings.png)\n\n### Equatable props generator\n\nRight click and use `Generate -> Equatable Props` to automatically generate the `props` override when using `Equatable`.\n\n![equatable_props_override](https://github.com/felangel/bloc/raw/master/extensions/intellij/assets/equatable_props_override.png)\n\n## Snippets\n\n### Bloc\n\n| Shortcut            | Description                                     |\n| ------------------- | ----------------------------------------------- |\n| `importbloc`        | Imports `package:bloc`                          |\n| `importflutterbloc` | Imports `package:flutter_bloc`                  |\n| `importbloctest`    | Imports `package:bloc_test`                     |\n| `bloc`              | Creates a bloc class                            |\n| `cubit`             | Creates a cubit class                           |\n| `blocobserver`      | Creates a `BlocObserver` class                  |\n| `blocprovider`      | Creates a `BlocProvider` widget                 |\n| `multiblocprovider` | Creates a `MultiBlocProvider` widget            |\n| `repoprovider`      | Creates a `RepositoryProvider` widget           |\n| `multirepoprovider` | Creates a `MultiRepositoryProvider` widget      |\n| `blocbuilder`       | Creates a `BlocBuilder` widget                  |\n| `bloclistener`      | Creates a `BlocListener` widget                 |\n| `multibloclistener` | Creates a `MultiBlocListener` widget            |\n| `blocconsumer`      | Creates a `BlocConsumer` widget                 |\n| `blocof`            | Shortcut for `BlocProvider.of()`                |\n| `repoof`            | Shortcut for `RepositoryProvider.of()`          |\n| `read`              | Shortcut for `context.read()`                   |\n| `watch`             | Shortcut for `context.watch()`                  |\n| `select`            | Shortcut for `context.select()`                 |\n| `blocstate`         | Creates a state class                           |\n| `blocevent`         | Creates an event class                          |\n| `bloctest`          | Creates a `blocTest` with build, act and expect |\n| `mockbloc`          | Creates a class extenting `MockBloc`            |\n| `mockcubit`         | Creates a class extending `MockCubit`           |\n| `fake`              | Creates a class extending `Fake`                |\n\n### Freezed Bloc\n\n| Shortcut     | Description                                        |\n| ------------ | -------------------------------------------------- |\n| `feventwhen` | Creates an event handler with freeze.when function |\n| `feventmap`  | Creates an event handler with freeze.map function  |\n| `fstate`     | Creates a sub state                                |\n| `fevent`     | Creates a sub event                                |\n\n## Deployment\n\nUsing [Plugin Repository](http://www.jetbrains.org/intellij/sdk/docs/plugin_repository/index.html)\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/.gitignore",
    "content": "# User-specific stuff:\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/dictionaries\nlocal.properties\n\n# Sensitive or high-churn files:\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.xml\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n\n# Gradle:\n.idea/**/gradle.xml\n.idea/**/libraries\n\n.gradle\n.gradle/*\n\nbuild/\nbuild/*\n\n## File-based project format:\n*.iws\n\n## Plugin-specific files:\n\n# IntelliJ\n/out/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    java\n    id(\"org.jetbrains.intellij.platform\") version \"2.5.0\"\n    kotlin(\"jvm\") version \"2.1.0\"\n    idea\n}\n\ngroup = \"com.bloc\"\nversion = \"4.1.11\"\n\nval lsp4ijVersion: String by project\nval lsp4jVersion: String by project\nval caseFormatVersion: String by project\nval apacheCommonsTextVersion: String by project\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_17\n    targetCompatibility = JavaVersion.VERSION_17\n}\n\nrepositories {\n    mavenCentral()\n    exclusiveContent {\n        forRepository {\n            maven {\n                setUrl(\"https://mvn.falsepattern.com/releases\")\n                name = \"mavenpattern\"\n            }\n        }\n        filter {\n            includeModule(\"com.redhat.devtools.intellij\", \"lsp4ij\")\n        }\n    }\n    intellijPlatform {\n        defaultRepositories()\n    }\n}\n\nintellijPlatform {\n    pluginConfiguration {\n        ideaVersion {\n            untilBuild = provider { null }\n        }\n    }\n    pluginVerification {\n        ides {\n            recommended()\n        }\n    }\n}\n\ndependencies {\n    intellijPlatform {\n        intellijIdeaCommunity(\"2023.3\")\n        bundledPlugin(\"com.intellij.java\")\n        plugin(\"com.redhat.devtools.lsp4ij:$lsp4ijVersion\")\n    }\n    testImplementation(kotlin(\"test\"))\n    compileOnly(\"com.redhat.devtools.intellij:lsp4ij:$lsp4ijVersion\")\n    compileOnly(\"org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion\")\n    implementation(\"com.fleshgrinder.kotlin:case-format:$caseFormatVersion\")\n    implementation(\"org.apache.commons:commons-text:$apacheCommonsTextVersion\")\n}\n\ntasks {\n    compileKotlin {\n        compilerOptions {\n            jvmTarget.set(JvmTarget.JVM_17)\n        }\n    }\n\n    compileTestKotlin {\n        compilerOptions {\n            jvmTarget.set(JvmTarget.JVM_17)\n        }\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.5-all.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/gradle.properties",
    "content": "kotlin.code.style=official\nkotlin.stdlib.default.dependency=true\n# Libraries versions\nlsp4jVersion=0.21.1\nlsp4ijVersion=0.12.0\ncaseFormatVersion=0.2.0\napacheCommonsTextVersion=1.13.1"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/settings.gradle",
    "content": "rootProject.name = 'intellij_generator_plugin'\n\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/BlocTemplateType.java",
    "content": "package com.bloc.intellij_generator_plugin.action;\n\npublic enum BlocTemplateType {\n    BASIC,\n    EQUATABLE,\n    FREEZED\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/GenerateBlocAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.action\n\nimport com.bloc.intellij_generator_plugin.generator.BlocGeneratorFactory\nimport com.bloc.intellij_generator_plugin.generator.BlocGenerator\nimport com.intellij.lang.java.JavaLanguage\nimport com.intellij.openapi.actionSystem.*\nimport com.intellij.openapi.application.ApplicationManager\nimport com.intellij.openapi.command.CommandProcessor\nimport com.intellij.openapi.project.Project\nimport com.intellij.psi.*\n\nclass GenerateBlocAction : AnAction(), GenerateBlocDialog.Listener {\n    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT\n\n    private lateinit var dataContext: DataContext\n\n    override fun actionPerformed(e: AnActionEvent) {\n        val dialog = GenerateBlocDialog(this)\n        dialog.show()\n    }\n\n    override fun onGenerateBlocClicked(\n        blocName: String?,\n        blocTemplateType: BlocTemplateType,\n    ) {\n        blocName?.let { name ->\n            val generators = BlocGeneratorFactory.getBlocGenerators(name, blocTemplateType)\n            generate(generators)\n        }\n    }\n\n    override fun update(e: AnActionEvent) {\n        e.dataContext.let {\n            this.dataContext = it\n            val presentation = e.presentation\n            presentation.isEnabled = true\n        }\n    }\n\n    private fun generate(mainSourceGenerators: List<BlocGenerator>) {\n        val project = CommonDataKeys.PROJECT.getData(dataContext)\n        val view = LangDataKeys.IDE_VIEW.getData(dataContext)\n        val directory = view?.orChooseDirectory\n        ApplicationManager.getApplication().runWriteAction {\n            CommandProcessor.getInstance().executeCommand(\n                project, {\n                    mainSourceGenerators.forEach { createSourceFile(project!!, it, directory!!) }\n                }, \"Generate a new Bloc\", null\n            )\n        }\n    }\n\n    private fun createSourceFile(project: Project, generator: BlocGenerator, directory: PsiDirectory) {\n        val fileName = generator.fileName()\n        val existingPsiFile = directory.findFile(fileName)\n        if (existingPsiFile != null) {\n            val document = PsiDocumentManager.getInstance(project).getDocument(existingPsiFile)\n            document?.insertString(document.textLength, \"\\n\" + generator.generate())\n            return\n        }\n        val psiFile = PsiFileFactory.getInstance(project)\n            .createFileFromText(fileName, JavaLanguage.INSTANCE, generator.generate())\n        directory.add(psiFile)\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/GenerateBlocDialog.form",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<form xmlns=\"http://www.intellij.com/uidesigner/form/\" version=\"1\" bind-to-class=\"com.bloc.intellij_generator_plugin.action.GenerateBlocDialog\">\n  <grid id=\"27dc6\" binding=\"contentPanel\" layout-manager=\"GridLayoutManager\" row-count=\"2\" column-count=\"2\" same-size-horizontally=\"false\" same-size-vertically=\"false\" hgap=\"-1\" vgap=\"-1\">\n    <margin top=\"0\" left=\"0\" bottom=\"0\" right=\"0\"/>\n    <constraints>\n      <xy x=\"20\" y=\"20\" width=\"500\" height=\"400\"/>\n    </constraints>\n    <properties/>\n    <border type=\"none\"/>\n    <children>\n      <component id=\"fa116\" class=\"javax.swing.JTextField\" binding=\"blocNameTextField\">\n        <constraints>\n          <grid row=\"0\" column=\"1\" row-span=\"1\" col-span=\"1\" vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\" indent=\"0\" use-parent-layout=\"false\">\n            <preferred-size width=\"150\" height=\"-1\"/>\n          </grid>\n        </constraints>\n        <properties>\n          <toolTipText value=\"Bloc name goes here (e.g. Login)\"/>\n        </properties>\n      </component>\n      <component id=\"9e8d8\" class=\"javax.swing.JLabel\">\n        <constraints>\n          <grid row=\"0\" column=\"0\" row-span=\"1\" col-span=\"1\" vsize-policy=\"0\" hsize-policy=\"0\" anchor=\"8\" fill=\"0\" indent=\"0\" use-parent-layout=\"false\"/>\n        </constraints>\n        <properties>\n          <text value=\"Name:\"/>\n        </properties>\n      </component>\n      <component id=\"29a5e\" class=\"javax.swing.JComboBox\" binding=\"style\">\n        <constraints>\n          <grid row=\"1\" column=\"1\" row-span=\"1\" col-span=\"1\" vsize-policy=\"0\" hsize-policy=\"2\" anchor=\"8\" fill=\"1\" indent=\"0\" use-parent-layout=\"false\"/>\n        </constraints>\n        <properties>\n          <model>\n            <item value=\"Basic\"/>\n            <item value=\"Equatable\"/>\n            <item value=\"Freezed\"/>\n          </model>\n        </properties>\n      </component>\n      <component id=\"ed03a\" class=\"javax.swing.JLabel\">\n        <constraints>\n          <grid row=\"1\" column=\"0\" row-span=\"1\" col-span=\"1\" vsize-policy=\"0\" hsize-policy=\"0\" anchor=\"8\" fill=\"0\" indent=\"0\" use-parent-layout=\"false\"/>\n        </constraints>\n        <properties>\n          <text value=\"Style\"/>\n        </properties>\n      </component>\n    </children>\n  </grid>\n  <buttonGroups>\n    <group name=\"radionButtonGroup\">\n      <member id=\"bb6d1\"/>\n      <member id=\"317bf\"/>\n      <member id=\"2b62e\"/>\n      <member id=\"900f4\"/>\n    </group>\n  </buttonGroups>\n</form>\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/GenerateBlocDialog.java",
    "content": "package com.bloc.intellij_generator_plugin.action;\n\nimport com.intellij.openapi.ui.DialogWrapper;\nimport org.jetbrains.annotations.Nullable;\n\nimport javax.swing.*;\nimport java.util.Objects;\n\npublic class GenerateBlocDialog extends DialogWrapper {\n\n    private final Listener listener;\n    private JTextField blocNameTextField;\n    private JPanel contentPanel;\n    private JComboBox<String> style;\n\n    public GenerateBlocDialog(final Listener listener) {\n        super(false);\n        this.listener = listener;\n        init();\n    }\n\n    @Nullable\n    @Override\n    protected JComponent createCenterPanel() {\n        return contentPanel;\n    }\n\n    @Override\n    protected void doOKAction() {\n        super.doOKAction();\n        BlocTemplateType blocTemplateType;\n        final String selectedStyle = Objects.requireNonNull(style.getSelectedItem()).toString();\n        if (Objects.equals(selectedStyle, \"Equatable\")) {\n            blocTemplateType = BlocTemplateType.EQUATABLE;\n        } else if (Objects.equals(selectedStyle, \"Freezed\")) {\n            blocTemplateType = BlocTemplateType.FREEZED;\n        } else {\n            blocTemplateType = BlocTemplateType.BASIC;\n        }\n        this.listener.onGenerateBlocClicked(blocNameTextField.getText(), blocTemplateType);\n    }\n\n    @Nullable\n    @Override\n    public JComponent getPreferredFocusedComponent() {\n        return blocNameTextField;\n    }\n\n    public interface Listener {\n        void onGenerateBlocClicked(String blocName, BlocTemplateType blocTemplateType);\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/GenerateCubitAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.action\n\nimport com.bloc.intellij_generator_plugin.generator.CubitGeneratorFactory\nimport com.bloc.intellij_generator_plugin.generator.CubitGenerator\nimport com.intellij.lang.java.JavaLanguage\nimport com.intellij.openapi.actionSystem.*\nimport com.intellij.openapi.application.ApplicationManager\nimport com.intellij.openapi.command.CommandProcessor\nimport com.intellij.openapi.project.Project\nimport com.intellij.psi.*\n\nclass GenerateCubitAction : AnAction(), GenerateBlocDialog.Listener {\n    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT\n\n    private lateinit var dataContext: DataContext\n\n    override fun actionPerformed(e: AnActionEvent) {\n        val dialog = GenerateBlocDialog(this)\n        dialog.show()\n    }\n\n    override fun onGenerateBlocClicked(name: String?, blocTemplateType: BlocTemplateType) {\n        name?.let {\n            val generators = CubitGeneratorFactory.getCubitGenerators(it, blocTemplateType)\n            generate(generators)\n        }\n    }\n\n    override fun update(e: AnActionEvent) {\n        e.dataContext.let {\n            this.dataContext = it\n            val presentation = e.presentation\n            presentation.isEnabled = true\n        }\n    }\n\n    protected fun generate(mainSourceGenerators: List<CubitGenerator>) {\n        val project = CommonDataKeys.PROJECT.getData(dataContext)\n        val view = LangDataKeys.IDE_VIEW.getData(dataContext)\n        val directory = view?.orChooseDirectory\n        ApplicationManager.getApplication().runWriteAction {\n            CommandProcessor.getInstance().executeCommand(\n                project, {\n                    mainSourceGenerators.forEach { createSourceFile(project!!, it, directory!!) }\n                }, \"Generate a new Cubit\", null\n            )\n        }\n    }\n\n    private fun createSourceFile(project: Project, generator: CubitGenerator, directory: PsiDirectory) {\n        val fileName = generator.fileName()\n        val existingPsiFile = directory.findFile(fileName)\n        if (existingPsiFile != null) {\n            val document = PsiDocumentManager.getInstance(project).getDocument(existingPsiFile)\n            document?.insertString(document.textLength, \"\\n\" + generator.generate())\n            return\n        }\n        val psiFile = PsiFileFactory.getInstance(project)\n            .createFileFromText(fileName, JavaLanguage.INSTANCE, generator.generate())\n        directory.add(psiFile)\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/action/GenerateEquatablePropsAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.action\n\nimport com.intellij.lang.ASTNode\nimport com.intellij.openapi.actionSystem.ActionUpdateThread\nimport com.intellij.openapi.actionSystem.AnAction\nimport com.intellij.openapi.actionSystem.AnActionEvent\nimport com.intellij.openapi.actionSystem.CommonDataKeys\nimport com.intellij.openapi.command.WriteCommandAction\nimport com.intellij.openapi.editor.Document\nimport com.intellij.psi.PsiDocumentManager\nimport com.intellij.psi.PsiElement\nimport com.intellij.psi.codeStyle.CodeStyleManager\nimport com.intellij.psi.util.PsiUtilBase\n\n\nclass GenerateEquatablePropsAction : AnAction() {\n    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT\n\n    private var propsNullable = false\n\n    override fun update(event: AnActionEvent) {\n        super.update(event)\n        val action = event.presentation\n        val editor = event.dataContext.getData(CommonDataKeys.EDITOR) ?: return\n        val project = event.project ?: return\n        val currentFile = PsiUtilBase.getPsiFileInEditor(editor, project)\n        action.isEnabledAndVisible = currentFile?.name?.endsWith(\".dart\") == true\n    }\n\n    override fun actionPerformed(event: AnActionEvent) {\n        val project = event.project ?: return\n        val editor = event.dataContext.getData(CommonDataKeys.EDITOR) ?: return\n\n        val currentFile = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return\n        val currentOffset = editor.caretModel.currentCaret.offset\n        val element = currentFile.findElementAt(currentOffset) ?: return\n\n        val classNode = findClassDefinition(element) ?: return\n        val members = findAllClassMembers(classNode) ?: return\n        val memberNames = getPropsList(editor.document, members)\n\n        val nullStr = if (propsNullable) \"?\" else \"\"\n        propsNullable = false\n\n        val props: String = reformatProps(memberNames.filter { s -> s != \"\" })\n        val propsStr = \"@override\\nList<Object$nullStr> get props => [$props];\"\n\n        WriteCommandAction.runWriteCommandAction(project) {\n            editor.document.insertString(currentOffset, propsStr)\n            PsiDocumentManager.getInstance(project).commitDocument(editor.document)\n            CodeStyleManager.getInstance(project).reformat(currentFile)\n        }\n    }\n\n    private fun findClassDefinition(element: PsiElement): ASTNode? {\n        var node: ASTNode? = element.node\n        while (node != null) {\n            if (node.toString() == \"Element(CLASS_DEFINITION)\") {\n                break\n            }\n            node = node.treeParent\n        }\n        return node\n    }\n\n    private fun findAllClassMembers(node: ASTNode) =\n        node.getChildren(null).find { astNode -> astNode.toString() == \"Element(CLASS_BODY)\" }\n            ?.getChildren(null)?.find { astNode -> astNode.toString() == \"Element(CLASS_MEMBERS)\" }?.getChildren(null)\n            ?.filter { astNode -> astNode.toString() == \"Element(VAR_DECLARATION_LIST)\" }\n\n    private fun findNonAccessModifiers(memberNode: ASTNode) = memberNode.getChildren(null)\n        .filter { astNode -> astNode.toString().startsWith(\"PsiElement(\") }\n\n    private fun findMemberType(memberNode: ASTNode) = memberNode.getChildren(null)\n        .find { astNode -> astNode.toString() == \"Element(TYPE)\" }\n\n    private fun findMemberName(memberNode: ASTNode) =\n        memberNode.getChildren(null).find { astNode -> astNode.toString() == \"Element(COMPONENT_NAME)\" }\n            ?.getChildren(null)\n            ?.find { astNode -> astNode.toString() == \"Element(ID)\" }?.getChildren(null)\n            ?.find { astNode -> astNode.toString() == \"PsiElement(IDENTIFIER)\" }\n\n    private fun getPropsList(\n        doc: Document,\n        members: List<ASTNode>\n    ) = members.map { n ->\n        val memberNode = n.firstChildNode\n        if (memberNode.toString() == \"Element(VAR_ACCESS_DECLARATION)\") {\n            val accessModifiers = findNonAccessModifiers(memberNode)\n\n            // list only class members with non-access modifier \"final\", but not with \"static final\" or without any\n            if (accessModifiers.size == 1 && accessModifiers[0].toString() == \"PsiElement(final)\") {\n                val type = findMemberType(memberNode)?.psi\n                if (!propsNullable && type != null && doc.getText(type.textRange).endsWith(\"?\")) {\n                    propsNullable = true\n                }\n\n                val member = findMemberName(memberNode)?.psi\n\n                return@map if (member == null) \"\" else doc.getText(member.textRange)\n            }\n        }\n        return@map \"\"\n    }\n\n    private fun reformatProps(memberNames: List<String>): String {\n        var props = \"\"\n        for ((i, s) in memberNames.withIndex()) {\n            if (memberNames.size == 1 || (i == memberNames.size - 1)) {\n                props += s\n            } else {\n                props += \"$s, \"\n            }\n        }\n\n        if (props.length >= 45) {\n            // line probably longer than 80 chars\n            props += \",\"\n        }\n\n        return props\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/BlocGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.fleshgrinder.extensions.kotlin.toLowerSnakeCase\nimport com.fleshgrinder.extensions.kotlin.toUpperCamelCase\nimport com.google.common.io.CharStreams\nimport org.apache.commons.text.StringSubstitutor\nimport java.io.InputStreamReader\n\nabstract class BlocGenerator(\n    private val name: String,\n    blocTemplateType: BlocTemplateType,\n    templateName: String\n) {\n\n    private val TEMPLATE_BLOC_PASCAL_CASE = \"bloc_pascal_case\"\n    private val TEMPLATE_BLOC_SNAKE_CASE = \"bloc_snake_case\"\n\n    private val templateString: String\n    private val templateValues: MutableMap<String, String>\n\n    init {\n        templateValues = mutableMapOf(\n            TEMPLATE_BLOC_PASCAL_CASE to pascalCase(),\n            TEMPLATE_BLOC_SNAKE_CASE to snakeCase()\n        )\n        try {\n            val templateFolder = when (blocTemplateType) {\n                BlocTemplateType.BASIC -> \"bloc_basic\"\n                BlocTemplateType.EQUATABLE -> \"bloc_equatable\"\n                BlocTemplateType.FREEZED -> \"bloc_freezed\"\n            }\n            val resource = \"/templates/$templateFolder/$templateName.dart.template\"\n            val resourceAsStream = BlocGenerator::class.java.getResourceAsStream(resource)\n            templateString = CharStreams.toString(InputStreamReader(resourceAsStream!!, Charsets.UTF_8)).replace(\"\\r\\n\", \"\\n\").replace(\"\\r\",\"\\n\");\n        } catch (e: Exception) {\n            throw RuntimeException(e)\n        }\n    }\n\n    abstract fun fileName(): String\n\n    fun generate(): String {\n        val substitutor = StringSubstitutor(templateValues, \"{{\", \"}}\", '\\\\')\n        return substitutor.replace(templateString)\n    }\n\n    private fun pascalCase(): String = name.toUpperCamelCase().replace(\"Bloc\", \"\")\n\n    fun snakeCase() = name.toLowerSnakeCase().replace(\"_bloc\", \"\")\n\n    fun fileExtension() = \"dart\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/BlocGeneratorFactory.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.components.BlocEventGenerator\nimport com.bloc.intellij_generator_plugin.generator.components.BlocGenerator\nimport com.bloc.intellij_generator_plugin.generator.components.BlocStateGenerator\n\nobject BlocGeneratorFactory {\n    fun getBlocGenerators(\n        name: String,\n        blocTemplateType: BlocTemplateType,\n    ): List<com.bloc.intellij_generator_plugin.generator.BlocGenerator> {\n        val bloc = BlocGenerator(name, blocTemplateType)\n        val event = BlocEventGenerator(name, blocTemplateType)\n        val state = BlocStateGenerator(name, blocTemplateType)\n        return listOf(bloc, event, state)\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/CubitGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.fleshgrinder.extensions.kotlin.toLowerSnakeCase\nimport com.fleshgrinder.extensions.kotlin.toUpperCamelCase\nimport com.google.common.io.CharStreams\nimport org.apache.commons.text.StringSubstitutor\nimport java.io.InputStreamReader\n\nabstract class CubitGenerator(\n    private val name: String,\n    blocTemplateType: BlocTemplateType,\n    templateName: String\n) {\n\n    private val TEMPLATE_CUBIT_PASCAL_CASE = \"cubit_pascal_case\"\n    private val TEMPLATE_CUBIT_SNAKE_CASE = \"cubit_snake_case\"\n\n    private val templateString: String\n    private val templateValues: MutableMap<String, String>\n\n    init {\n        templateValues = mutableMapOf(\n            TEMPLATE_CUBIT_PASCAL_CASE to pascalCase(),\n            TEMPLATE_CUBIT_SNAKE_CASE to snakeCase()\n        )\n        try {\n            val templateFolder = when (blocTemplateType) {\n                BlocTemplateType.BASIC -> \"cubit_basic\"\n                BlocTemplateType.EQUATABLE -> \"cubit_equatable\"\n                BlocTemplateType.FREEZED -> \"cubit_freezed\"\n            }\n            val resource = \"/templates/$templateFolder/$templateName.dart.template\"\n            val resourceAsStream = CubitGenerator::class.java.getResourceAsStream(resource)\n            templateString = CharStreams.toString(InputStreamReader(resourceAsStream!!, Charsets.UTF_8)).replace(\"\\r\\n\", \"\\n\").replace(\"\\r\",\"\\n\");\n        } catch (e: Exception) {\n            throw RuntimeException(e)\n        }\n    }\n\n    abstract fun fileName(): String\n\n    fun generate(): String {\n        val substitutor = StringSubstitutor(templateValues, \"{{\", \"}}\", '\\\\')\n        return substitutor.replace(templateString)\n    }\n\n    private fun pascalCase(): String = name.toUpperCamelCase().replace(\"Cubit\", \"\")\n\n    fun snakeCase() = name.toLowerSnakeCase().replace(\"_cubit\", \"\")\n\n    fun fileExtension() = \"dart\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/CubitGeneratorFactory.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.components.CubitGenerator\nimport com.bloc.intellij_generator_plugin.generator.components.CubitStateGenerator\n\nobject CubitGeneratorFactory {\n    fun getCubitGenerators(\n        name: String,\n        blocTemplateType: BlocTemplateType,\n    ): List<com.bloc.intellij_generator_plugin.generator.CubitGenerator> {\n        val cubit = CubitGenerator(name, blocTemplateType)\n        val state = CubitStateGenerator(name, blocTemplateType)\n        return listOf(cubit, state)\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/components/BlocEventGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator.components\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.BlocGenerator\n\nclass BlocEventGenerator(\n        blocName: String,\n        blocTemplateType: BlocTemplateType\n) : BlocGenerator(blocName, blocTemplateType, templateName = \"bloc_event\") {\n\n    override fun fileName() = \"${snakeCase()}_event.${fileExtension()}\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/components/BlocGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator.components\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.BlocGenerator\n\nclass BlocGenerator(\n        name: String,\n        blocTemplateType: BlocTemplateType\n) : BlocGenerator(name, blocTemplateType, templateName = \"bloc\") {\n    override fun fileName() = \"${snakeCase()}_bloc.${fileExtension()}\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/components/BlocStateGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator.components\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.BlocGenerator\n\nclass BlocStateGenerator(\n        name: String,\n        blocTemplateType: BlocTemplateType\n) : BlocGenerator(name, blocTemplateType, templateName = \"bloc_state\") {\n    override fun fileName() = \"${snakeCase()}_state.${fileExtension()}\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/components/CubitGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator.components\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.CubitGenerator\n\nclass CubitGenerator(\n        name: String,\n        blocTemplateType: BlocTemplateType\n) : CubitGenerator(name, blocTemplateType, templateName = \"cubit\") {\n    override fun fileName() = \"${snakeCase()}_cubit.${fileExtension()}\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/generator/components/CubitStateGenerator.kt",
    "content": "package com.bloc.intellij_generator_plugin.generator.components\n\nimport com.bloc.intellij_generator_plugin.action.BlocTemplateType\nimport com.bloc.intellij_generator_plugin.generator.CubitGenerator\n\nclass CubitStateGenerator(\n        name: String,\n        blocTemplateType: BlocTemplateType\n) : CubitGenerator(name, blocTemplateType, templateName = \"cubit_state\") {\n    override fun fileName() = \"${snakeCase()}_state.${fileExtension()}\"\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocConvertToMultiBlocListenerIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocConvertToMultiBlocListenerIntentionAction : BlocConvertToMultiIntentionAction(SnippetType.MultiBlocListener) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Convert to MultiBlocListener\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocConvertToMultiBlocProviderIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocConvertToMultiBlocProviderIntentionAction : BlocConvertToMultiIntentionAction(SnippetType.MultiBlocProvider) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Convert to MultiBlocProvider\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocConvertToMultiIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nimport com.bloc.intellij_generator_plugin.intention_action.Common.Companion.invokeSnippetAction\nimport com.bloc.intellij_generator_plugin.intention_action.WrapHelper.Companion.blocWidgetChildFinder\nimport com.bloc.intellij_generator_plugin.intention_action.WrapHelper.Companion.callExpressionFinder\nimport com.intellij.codeInsight.intention.IntentionAction\nimport com.intellij.codeInsight.intention.PsiElementBaseIntentionAction\nimport com.intellij.openapi.command.WriteCommandAction\nimport com.intellij.openapi.editor.Editor\nimport com.intellij.openapi.project.Project\nimport com.intellij.psi.PsiElement\nimport com.intellij.util.IncorrectOperationException\n\nabstract class BlocConvertToMultiIntentionAction(private val snippetType: SnippetType) :\n    PsiElementBaseIntentionAction(),\n    IntentionAction {\n    var callExpressionElement: PsiElement? = null\n    var blocChildCallExpressionElement: PsiElement? = null\n\n    override fun getFamilyName(): String = text\n\n    override fun isAvailable(project: Project, editor: Editor?, psiElement: PsiElement): Boolean {\n        val shouldDisplayWrapMenu = Common.shouldDisplayWrapMenu(editor, project, psiElement)\n        if (!shouldDisplayWrapMenu) return false\n\n        callExpressionElement = callExpressionFinder(psiElement)\n        if (callExpressionElement == null) return false\n\n        return shouldDisplayConvertMenu()\n    }\n\n    private fun shouldDisplayConvertMenu(): Boolean {\n        val widgetName = callExpressionElement?.text ?: return false\n        if (widgetName.startsWith(snippetType.toString().removePrefix(\"Multi\"))\n        ) {\n            val blocChildWidget = blocWidgetChildFinder(callExpressionElement!!) ?: return false\n            blocChildCallExpressionElement = blocChildWidget\n            return true\n        }\n        return false\n    }\n\n    @Throws(IncorrectOperationException::class)\n    override fun invoke(project: Project, editor: Editor, element: PsiElement) {\n        val runnable = Runnable {\n            invokeSnippetAction(\n                project,\n                editor,\n                snippetType,\n                callExpressionElement!!,\n                blocChildCallExpressionElement!!\n            )\n        }\n        WriteCommandAction.runWriteCommandAction(project, runnable)\n    }\n\n    override fun startInWriteAction(): Boolean = true\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocConvertToMultiRepositoryProviderIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocConvertToMultiRepositoryProviderIntentionAction :\n    BlocConvertToMultiIntentionAction(SnippetType.MultiRepositoryProvider) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Convert to MultiRepositoryProvider\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithBlocBuilderIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithBlocBuilderIntentionAction : BlocWrapWithIntentionAction(SnippetType.BlocBuilder) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with BlocBuilder\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithBlocConsumerIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithBlocConsumerIntentionAction : BlocWrapWithIntentionAction(SnippetType.BlocConsumer) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with BlocConsumer\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithBlocListenerIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithBlocListenerIntentionAction : BlocWrapWithIntentionAction(SnippetType.BlocListener) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with BlocListener\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithBlocProviderIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithBlocProviderIntentionAction : BlocWrapWithIntentionAction(SnippetType.BlocProvider) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with BlocProvider\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithBlocSelectorIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithBlocSelectorIntentionAction : BlocWrapWithIntentionAction(SnippetType.BlocSelector) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with BlocSelector\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nimport com.bloc.intellij_generator_plugin.intention_action.Common.Companion.invokeSnippetAction\nimport com.bloc.intellij_generator_plugin.intention_action.WrapHelper.Companion.callExpressionFinder\nimport com.intellij.codeInsight.intention.IntentionAction\nimport com.intellij.codeInsight.intention.PsiElementBaseIntentionAction\nimport com.intellij.openapi.command.WriteCommandAction\nimport com.intellij.openapi.editor.Editor\nimport com.intellij.openapi.project.Project\nimport com.intellij.psi.PsiElement\nimport com.intellij.util.IncorrectOperationException\n\nabstract class BlocWrapWithIntentionAction(private val snippetType: SnippetType) : PsiElementBaseIntentionAction(),\n    IntentionAction {\n    var callExpressionElement: PsiElement? = null\n\n    /**\n     * Returns text for name of this family of intentions.\n     * It is used to externalize \"auto-show\" state of intentions.\n     * It is also the directory name for the descriptions.\n     *\n     * @return the intention family name.\n     */\n    override fun getFamilyName(): String = text\n\n    /**\n     * Checks whether this intention is available at the caret offset in file - the caret must sit on a widget call.\n     * If this condition is met, this intention's entry is shown in the available intentions list.\n     *\n     *\n     * Note: this method must do its checks quickly and return.\n     *\n     * @param project a reference to the Project object being edited.\n     * @param editor  a reference to the object editing the project source\n     * @param psiElement a reference to the PSI element currently under the caret\n     * @return `true` if the caret is in a literal string element, so this functionality should be added to the\n     * intention menu or `false` for all other types of caret positions\n     */\n    override fun isAvailable(project: Project, editor: Editor?, psiElement: PsiElement): Boolean {\n        val shouldDisplay = Common.shouldDisplayWrapMenu(editor, project, psiElement)\n        if (shouldDisplay) {\n            callExpressionElement = callExpressionFinder(psiElement)\n            return callExpressionElement != null\n        }\n        return false\n    }\n\n    /**\n     * Called when user selects this intention action from the available intentions list.\n     *\n     * @param project a reference to the Project object being edited.\n     * @param editor  a reference to the object editing the project source\n     * @param element a reference to the PSI element currently under the caret\n     * @throws IncorrectOperationException Thrown by underlying (Psi model) write action context\n     * when manipulation of the psi tree fails.\n     */\n    @Throws(IncorrectOperationException::class)\n    override fun invoke(project: Project, editor: Editor, element: PsiElement) {\n        val runnable = Runnable { invokeSnippetAction(project, editor, snippetType, callExpressionElement!!, null) }\n        WriteCommandAction.runWriteCommandAction(project, runnable)\n    }\n\n    /**\n     * Indicates this intention action expects the Psi framework to provide the write action context for any changes.\n     *\n     * @return `true` if the intention requires a write action context to be provided or `false` if this\n     * intention action will start a write action\n     */\n    override fun startInWriteAction(): Boolean = true\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/BlocWrapWithRepositoryProviderIntentionAction.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nclass BlocWrapWithRepositoryProviderIntentionAction : BlocWrapWithIntentionAction(SnippetType.RepositoryProvider) {\n    /**\n     * If this action is applicable, returns the text to be shown in the list of intention actions available.\n     */\n    override fun getText(): String {\n        return \"Wrap with RepositoryProvider\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/Common.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nimport com.intellij.openapi.application.ApplicationManager\nimport com.intellij.openapi.command.WriteCommandAction\nimport com.intellij.openapi.editor.Editor\nimport com.intellij.openapi.project.Project\nimport com.intellij.openapi.util.TextRange\nimport com.intellij.psi.PsiDocumentManager\nimport com.intellij.psi.PsiElement\nimport com.intellij.psi.PsiFile\nimport com.intellij.psi.codeStyle.CodeStyleManager\n\nclass Common {\n    companion object {\n        fun shouldDisplayWrapMenu(\n            editor: Editor?,\n            project: Project,\n            psiElement: PsiElement\n        ): Boolean {\n            if (editor == null) {\n                return false\n            }\n            val currentFile = getCurrentFile(project, editor)\n            if (currentFile != null && !currentFile.name.endsWith(\".dart\")) {\n                return false\n            }\n            if (psiElement.toString() != \"PsiElement(IDENTIFIER)\") {\n                return false\n            }\n            return true\n        }\n\n        fun getCurrentFile(project: Project, editor: Editor): PsiFile? =\n            PsiDocumentManager.getInstance(project).getPsiFile(editor.document)\n\n        fun invokeSnippetAction(\n            project: Project,\n            editor: Editor,\n            snippetType: SnippetType?,\n            callExpressionElement: PsiElement,\n            blocChildWidget: PsiElement?\n        ) {\n            val document = editor.document\n            val elementSelectionRange = callExpressionElement.textRange\n            val offsetStart = elementSelectionRange.startOffset\n            val offsetEnd = elementSelectionRange.endOffset\n            if (!WrapHelper.isSelectionValid(offsetStart, offsetEnd)) {\n                return\n            }\n            val selectedText = document.getText(TextRange.create(offsetStart, offsetEnd))\n\n            val replaceWith: String\n            if (blocChildWidget != null) {\n                val blocChildWidgetText = blocChildWidget.text\n                val movedBlocWithoutChild = selectedText.replaceFirst(blocChildWidgetText, \"\")\n                    .replaceFirst(\"[^\\\\S\\\\r\\\\n]*child: ,\\\\s*\".toRegex(), \"\")\n                replaceWith = Snippets.getSnippet(snippetType, blocChildWidgetText, movedBlocWithoutChild)\n            } else {\n                replaceWith = Snippets.getSnippet(snippetType, \"\", selectedText)\n            }\n\n            // wrap the widget:\n            WriteCommandAction.runWriteCommandAction(project) {\n                document.replaceString(\n                    offsetStart,\n                    offsetEnd,\n                    replaceWith\n                )\n            }\n\n            // place cursors to specify types:\n            val prefixSelection = Snippets.PREFIX_SELECTION\n            val snippetArr =\n                arrayOf(Snippets.BLOC_SNIPPET_KEY, Snippets.STATE_SNIPPET_KEY, Snippets.REPOSITORY_SNIPPET_KEY)\n            val caretModel = editor.caretModel\n            caretModel.removeSecondaryCarets()\n            for (snippet in snippetArr) {\n                if (!replaceWith.contains(snippet)) {\n                    continue\n                }\n                val caretOffset = offsetStart + replaceWith.indexOf(snippet)\n                val visualPos = editor.offsetToVisualPosition(caretOffset)\n                caretModel.addCaret(visualPos)\n\n                // select snippet prefix keys:\n                val currentCaret = caretModel.currentCaret\n                currentCaret.setSelection(caretOffset, caretOffset + prefixSelection.length)\n            }\n            val initialCaret = caretModel.allCarets[0]\n            if (!initialCaret.hasSelection()) {\n                // initial position from where was triggered the intention action\n                caretModel.removeCaret(initialCaret)\n            }\n\n            // reformat file:\n            ApplicationManager.getApplication().runWriteAction {\n                PsiDocumentManager.getInstance(project).commitDocument(document)\n                val currentFile = getCurrentFile(project, editor)\n                if (currentFile != null) {\n                    val unformattedText = document.text\n                    val unformattedLineCount = document.lineCount\n                    CodeStyleManager.getInstance(project).reformat(currentFile)\n                    val formattedLineCount = document.lineCount\n\n                    // file was incorrectly formatted, revert formatting\n                    if (formattedLineCount > unformattedLineCount + 3) {\n                        document.setText(unformattedText)\n                        PsiDocumentManager.getInstance(project).commitDocument(document)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/SnippetType.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nenum class SnippetType {\n    BlocBuilder, BlocSelector, BlocListener, BlocProvider, BlocConsumer, RepositoryProvider, MultiBlocProvider, MultiRepositoryProvider, MultiBlocListener,\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/Snippets.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nobject Snippets {\n    const val PREFIX_SELECTION = \"Subject\"\n\n    private const val SUFFIX_BLOC = \"Bloc\"\n    private const val SUFFIX_STATE = \"State\"\n    private const val SUFFIX_REPOSITORY = \"Repository\"\n\n    const val SELECTED_STATE_SNIPPET_KEY = \"SelectedState\"\n    const val BLOC_SNIPPET_KEY = PREFIX_SELECTION + SUFFIX_BLOC\n    const val STATE_SNIPPET_KEY = PREFIX_SELECTION + SUFFIX_STATE\n    const val REPOSITORY_SNIPPET_KEY = PREFIX_SELECTION + SUFFIX_REPOSITORY\n\n    @JvmStatic\n    fun getSnippet(snippetType: SnippetType?, blocWidget: String, widget: String): String {\n        return when (snippetType) {\n            SnippetType.BlocSelector -> blocSelectorSnippet(widget)\n            SnippetType.BlocListener -> blocListenerSnippet(widget)\n            SnippetType.BlocProvider -> blocProviderSnippet(widget)\n            SnippetType.BlocConsumer -> blocConsumerSnippet(widget)\n            SnippetType.RepositoryProvider -> repositoryProviderSnippet(widget)\n            SnippetType.MultiBlocProvider -> multiBlocProviderSnippet(blocWidget, widget)\n            SnippetType.MultiBlocListener -> multiBlocListenerSnippet(blocWidget, widget)\n            SnippetType.MultiRepositoryProvider -> multiRepositoryProviderSnippet(blocWidget, widget)\n            else -> blocBuilderSnippet(widget)\n        }\n    }\n\n    private fun blocBuilderSnippet(widget: String): String {\n        return \"BlocBuilder<$BLOC_SNIPPET_KEY, $STATE_SNIPPET_KEY>(\\n\" +\n                \"  builder: (context, state) {\\n\" +\n                \"    return $widget;\\n\" +\n                \"  },\\n\" +\n                \")\"\n    }\n\n    private fun blocSelectorSnippet(widget: String): String {\n        return \"BlocSelector<$BLOC_SNIPPET_KEY, $STATE_SNIPPET_KEY, $SELECTED_STATE_SNIPPET_KEY>(\\n\" +\n                \"  selector: (state) {\\n\" +\n                \"    // TODO: return selected state\\n\" +\n                \"  },\\n\" +\n                \"  builder: (context, state) {\\n\" +\n                \"    return $widget;\\n\" +\n                \"  },\\n\" +\n                \")\"\n    }\n\n    private fun blocListenerSnippet(widget: String): String {\n        return \"BlocListener<$BLOC_SNIPPET_KEY, $STATE_SNIPPET_KEY>(\\n\" +\n                \"  listener: (context, state) {\\n\" +\n                \"    // TODO: implement listener\\n\" +\n                \"  },\\n\" +\n                \"  child: $widget,\\n\" +\n                \")\"\n    }\n\n    private fun blocProviderSnippet(widget: String): String {\n        return \"BlocProvider(\\n\" +\n                \"  create: (context) => $BLOC_SNIPPET_KEY(),\\n\" +\n                \"  child: $widget,\\n\" +\n                \")\"\n    }\n\n    private fun blocConsumerSnippet(widget: String): String {\n        return \"BlocConsumer<$BLOC_SNIPPET_KEY, $STATE_SNIPPET_KEY>(\\n\" +\n                \"  listener: (context, state) {\\n\" +\n                \"    // TODO: implement listener\\n\" +\n                \"  },\\n\" +\n                \"  builder: (context, state) {\\n\" +\n                \"    return $widget;\\n\" +\n                \"  },\\n\" +\n                \")\"\n    }\n\n    private fun repositoryProviderSnippet(widget: String): String {\n        return \"RepositoryProvider(\\n\" +\n                \"  create: (context) => $REPOSITORY_SNIPPET_KEY(),\\n\" +\n                \"    child: $widget,\\n\" +\n                \")\"\n    }\n\n    private fun multiBlocProviderSnippet(blocChildWidget: String, widget: String): String {\n        return \"MultiBlocProvider(\\n\" +\n                \"  providers: [\\n\" +\n                \"    $widget,\\n\" +\n                \"    BlocProvider(\\n\" +\n                \"      create: (context) => $BLOC_SNIPPET_KEY(),\\n\" +\n                \"    ),\\n\" +\n                \"  ],\\n\" +\n                \"  child: $blocChildWidget,\\n\" +\n                \")\"\n    }\n\n    private fun multiBlocListenerSnippet(blocChildWidget: String, widget: String): String {\n        return \"MultiBlocListener(\\n\" +\n                \"  listeners: [\\n\" +\n                \"    $widget,\\n\" +\n                \"    BlocListener<$BLOC_SNIPPET_KEY, $STATE_SNIPPET_KEY>(\\n\" +\n                \"      listener: (context, state) {\\n\" +\n                \"        // TODO: implement listener\\n\" +\n                \"      },\\n\" +\n                \"    ),\\n\" +\n                \"  ],\\n\" +\n                \"  child: $blocChildWidget,\\n\" +\n                \")\"\n    }\n\n    private fun multiRepositoryProviderSnippet(blocChildWidget: String, widget: String): String {\n        return \"MultiRepositoryProvider(\\n\" +\n                \"  providers: [\\n\" +\n                \"    $widget,\\n\" +\n                \"    RepositoryProvider(\\n\" +\n                \"      create: (context) => $REPOSITORY_SNIPPET_KEY(),\\n\" +\n                \"    ),\\n\" +\n                \"  ],\\n\" +\n                \"  child: $blocChildWidget,\\n\" +\n                \")\"\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/intention_action/WrapHelper.kt",
    "content": "package com.bloc.intellij_generator_plugin.intention_action\n\nimport com.intellij.lang.ASTNode\nimport com.intellij.psi.PsiElement\n\nclass WrapHelper {\n    companion object {\n        fun callExpressionFinder(psiElement: PsiElement): PsiElement? {\n            var psiElementFinder: PsiElement? = psiElement.parent\n\n            for (i in 1..10) {\n                if (psiElementFinder == null) {\n                    return null\n                }\n\n                if (psiElementFinder.toString() == \"CALL_EXPRESSION\") {\n                    if (psiElementFinder.text.startsWith(psiElement.text)) {\n                        return psiElementFinder\n                    }\n                    return null\n                }\n                psiElementFinder = psiElementFinder.parent\n            }\n            return null\n        }\n\n        fun blocWidgetChildFinder(element: PsiElement): PsiElement? {\n            val node: ASTNode = element.node ?: return null\n            val childArgument = findChildArgument(node) ?: return null\n            return findChildWidget(childArgument)\n        }\n\n        private fun findChildArgument(node: ASTNode) = node.getChildren(null)\n            .find { astNode -> astNode.toString() == \"Element(ARGUMENTS)\" }?.getChildren(null)\n            ?.find { astNode -> astNode.toString() == \"Element(ARGUMENT_LIST)\" }?.getChildren(null)\n            ?.firstOrNull { astNode -> astNode.toString() == \"Element(NAMED_ARGUMENT)\" && astNode.text.startsWith(\"child: \") }\n\n        private fun findChildWidget(childArgument: ASTNode) = childArgument.getChildren(null)\n            .firstOrNull { astNode -> astNode.toString() == \"Element(CALL_EXPRESSION)\" }?.psi\n\n        fun isSelectionValid(start: Int, end: Int): Boolean {\n            if (start <= -1 || end <= -1) {\n                return false\n            }\n\n            if (start >= end) {\n                return false\n            }\n\n            return true\n        }\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/language_server/BlocLanguageServer.kt",
    "content": "package com.bloc.intellij_generator_plugin.language_server\n\nimport com.intellij.execution.configurations.GeneralCommandLine\nimport com.intellij.openapi.diagnostic.Logger\nimport com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider\nimport com.redhat.devtools.lsp4ij.server.StreamConnectionProvider\nimport com.intellij.openapi.application.PathManager\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.net.URL\nimport java.net.URI\n\nprivate const val BLOC_TOOLS_VERSION = \"0.1.0-dev.21\"\n\nenum class OperatingSystem(val value: String) {\n    Linux(\"linux\"),\n    MacOS(\"macos\"),\n    Windows(\"windows\"),\n    Unknown(\"-\");\n\n    companion object {\n        fun fromSystemProperty(): OperatingSystem {\n            val os = System.getProperty(\"os.name\").lowercase()\n            return when {\n                os.contains(\"win\") -> Windows\n                os.contains(\"mac\") -> MacOS\n                os.contains(\"nix\") || os.contains(\"nux\") || os.contains(\"aix\") -> Linux\n                else -> Unknown\n            }\n        }\n    }\n}\n\nenum class Architecture(val value: String) {\n    X64(\"x64\"),\n    Arm64(\"arm64\"),\n    Unknown(\"-\");\n\n    companion object {\n        fun fromSystemProperty(): Architecture {\n            val arch = System.getProperty(\"os.arch\").lowercase()\n            return when (arch) {\n                in listOf(\"amd64\", \"x86_64\") -> X64\n                in listOf(\"aarch64\", \"arm64\") -> Arm64\n                else -> Unknown\n            }\n        }\n    }\n}\n\nclass BlocLanguageServer() {\n    private val logger = Logger.getInstance(BlocLanguageServer::class.java)\n    private var provider: OSProcessStreamConnectionProvider? = null\n\n    fun areBlocToolsInstalled(): Boolean {\n        val executable = getBlocToolsExecutableFile()\n        return executable?.exists() == true\n    }\n\n    fun installBlocTools(): Boolean {\n        val executableFile = getBlocToolsExecutableFile() ?: return false\n        try {\n            val uri =\n                URI(\"https://github.com/felangel/bloc/releases/download/bloc_tools-v$BLOC_TOOLS_VERSION/${executableFile.name}\")\n            val cacheDir = getCacheDirectory()\n            cacheDir.mkdirs()\n            downloadFile(uri.toURL(), executableFile)\n            makeExecutable(executableFile)\n        } catch (e: Exception) {\n            logger.error(\"Failed to download bloc tools\", e)\n        }\n        return executableFile.exists()\n    }\n\n    fun getConnectionProvider(): StreamConnectionProvider? {\n        val executable = getBlocToolsExecutableFile() ?: return null\n        if (!executable.exists()) return null\n\n        val commandLine = GeneralCommandLine(executable.absolutePath, \"language-server\")\n        provider = OSProcessStreamConnectionProvider(commandLine)\n        return provider\n    }\n\n    private fun getBlocToolsExecutableFile(): File? {\n        val os = OperatingSystem.fromSystemProperty()\n        val arch = Architecture.fromSystemProperty()\n\n        if (os == OperatingSystem.Unknown || arch == Architecture.Unknown) {\n            logger.warn(\"Unsupported OS or architecture\")\n            return null\n        }\n\n        val fileName = \"bloc_${os.value}_${arch.value}\"\n        val cacheDir = getCacheDirectory()\n        return File(cacheDir, fileName)\n    }\n\n    private fun makeExecutable(file: File) {\n        if (!file.setExecutable(true, false)) {\n            logger.error(\"Failed to set executable: ${file.absolutePath}\")\n        }\n    }\n\n    private fun downloadFile(url: URL, outputFile: File) {\n        url.openStream().use { input ->\n            FileOutputStream(outputFile).use { output ->\n                input.copyTo(output)\n            }\n        }\n    }\n\n    private fun getCacheDirectory(): File {\n        val pluginCacheDir = File(PathManager.getSystemPath(), \"bloc-tools/$BLOC_TOOLS_VERSION\")\n        pluginCacheDir.mkdirs()\n        return pluginCacheDir\n    }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/language_server/BlocLanguageServerFactory.kt",
    "content": "package com.bloc.intellij_generator_plugin.language_server\n\nimport com.intellij.openapi.diagnostic.Logger\nimport com.intellij.openapi.project.Project\nimport com.redhat.devtools.lsp4ij.LanguageServerFactory\nimport com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures\nimport com.redhat.devtools.lsp4ij.server.StreamConnectionProvider\nimport java.io.InputStream\nimport java.io.OutputStream\n\n@Suppress(\"UnstableApiUsage\")\nclass BlocLanguageServerFactory : LanguageServerFactory {\n    private val logger = Logger.getInstance(BlocLanguageServer::class.java)\n\n    override fun createClientFeatures(): LSPClientFeatures {\n        val features = LSPClientFeatures()\n        features.setServerInstaller(BlocLanguageServerInstaller())\n        return features\n    }\n\n    override fun createConnectionProvider(project: Project): StreamConnectionProvider {\n        val languageServer = BlocLanguageServer()\n        return languageServer.getConnectionProvider() ?: run {\n            logger.warn(\"Bloc Language Server could not be initialized — falling back to no-op connection provider\")\n            NoopConnectionProvider()\n        }\n    }\n}\n\nclass NoopConnectionProvider : StreamConnectionProvider {\n    override fun start() {\n        // no-op\n    }\n\n    override fun stop() {\n        // no-op\n    }\n\n    override fun getInputStream(): InputStream? = null\n\n    override fun getOutputStream(): OutputStream? = null\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/language_server/BlocLanguageServerInstaller.kt",
    "content": "package com.bloc.intellij_generator_plugin.language_server\n\nimport com.intellij.openapi.progress.ProgressIndicator\nimport com.intellij.openapi.progress.ProgressManager\nimport com.redhat.devtools.lsp4ij.installation.LanguageServerInstallerBase\n\nclass BlocLanguageServerInstaller : LanguageServerInstallerBase() {\n    private val languageServer = BlocLanguageServer();\n\n    override fun checkServerInstalled(indicator: ProgressIndicator): Boolean {\n        progress(\"Checking if the language server is installed...\", indicator)\n        val installed = languageServer.areBlocToolsInstalled()\n        ProgressManager.checkCanceled()\n        return installed\n    }\n\n    @Throws(Exception::class)\n    override fun install(indicator: ProgressIndicator) {\n        progress(\"Installing bloc tools...\", indicator)\n        val installed = languageServer.installBlocTools()\n        ProgressManager.checkCanceled()\n        if (!installed) throw Exception(\"Failed to install bloc tools\")\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/live_templates/BlocContext.kt",
    "content": "package com.bloc.intellij_generator_plugin.live_templates\n\nimport com.intellij.codeInsight.template.TemplateActionContext\nimport com.intellij.codeInsight.template.TemplateContextType\n\nclass BlocContext : TemplateContextType(\"Bloc\") {\n    override fun isInContext(templateActionContext: TemplateActionContext): Boolean {\n        return templateActionContext.file.name.endsWith(\".dart\")\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/java/com/bloc/intellij_generator_plugin/util/BlocPluginNotification.kt",
    "content": "package com.bloc.intellij_generator_plugin.util\n\nimport com.intellij.notification.NotificationGroupManager\nimport com.intellij.notification.NotificationType\nimport com.intellij.openapi.project.Project\n\nobject BlocPluginNotification {\n    private const val NOTIFICATION_GROUP = \"Bloc Plugin Notifications\"\n\n    fun notify(project: Project, content: String, type: NotificationType) {\n        NotificationGroupManager.getInstance()\n            .getNotificationGroup(NOTIFICATION_GROUP)\n            .createNotification(content, type)\n            .notify(project)\n    }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/META-INF/plugin.xml",
    "content": "<idea-plugin>\n    <id>com.bloc.intellij_generator_plugin</id>\n    <name>Bloc</name>\n    <vendor>felangel</vendor>\n\n    <description>\n        <![CDATA[\n            Bloc Library Tools for effectively creating blocs and cubits for both Flutter and AngularDart apps.\n        ]]>\n    </description>\n\n    <change-notes><![CDATA[\n        <ul>\n          <li>v4.1.11 - Bump bloc_tools version to 0.1.0-dev.21</li>\n          <li>v4.1.10 - Bump bloc_tools version to 0.1.0-dev.20</li>\n          <li>v4.1.9 - Bump bloc_tools version to 0.1.0-dev.19</li>\n          <li>v4.1.8 - Bump bloc_tools version to 0.1.0-dev.18</li>\n          <li>v4.1.7 - Various linter improvements and performance optimizations</li>\n          <li>v4.1.6 - Fix increased CPU usage when using fvm</li>\n          <li>v4.1.5 - Use precompiled bloc_tools executable for LSP</li>\n          <li>v4.1.4 - Include environment during command execution</li>\n          <li>v4.1.3 - Bump bloc_tools version to 0.1.0-dev.12</li>\n          <li>v4.1.2 - Fix language server execution on Windows and New Bloc/Cubit Actions</li>\n          <li>v4.1.1 - Add Dart SDK constraint to language server</li>\n          <li>v4.1.0 - Add language server with bloc lint integration</li>\n          <li>v4.0.2 - Support for Intellij 2024.2</li>\n          <li>v4.0.1 - Support for Intellij 2024</li>\n          <li>v4.0.0 - Support for Sealed Classes and Dependency Upgrades</li>\n          <li>v3.4.0 - Support for Freezed</li>\n          <li>v3.3.0 - Support for Mock snippet</li>\n          <li>v3.2.0 - Support for MockBloc, MockCubit, and Fake snippets</li>\n          <li>v3.1.0 - Support for Wrap with BlocSelector</li>\n          <li>v3.0.0 - Support for Bloc v7.2.0 (on<Event> API)</li>\n          <li>v2.4.0 - Add Convert to Multi Widget Actions</li>\n          <li>v2.3.0 - Add Equatable Props Generator and Live Templates Bloc/Cubit selection support</li>\n          <li>v2.2.1 - Fix Wrapping Errors</li>\n          <li>v2.2.0 - Wrap With Quick Action Support</li>\n          <li>v2.1.0 - Live Template Support</li>\n          <li>v2.0.0 - Support for Bloc v6.0.0</li>\n          <li>v1.8.0 - Support for Cubit</li>\n          <li>v1.7.1 - Fix handle trailing _bloc in name</li>\n          <li>v1.7.0 - Support for Equatable v1.0.0 & Bloc v5.0.0</li>\n          <li>v1.6.0 - Support for Equatable v0.6.0</li>\n          <li>v1.5.1 - Minor dependency updates</li>\n          <li>v1.5 - Fix Overwrite Issue and Updates for implicit-dynamic support with Equatable</li>\n          <li>v1.4 - Bug Fixes for Bloc Generator Name (Case Sensitivity)</li>\n          <li>v1.3 - Bug Fixes for IntelliJ and Android Studio Compatibility</li>\n          <li>v1.2 - Support for IntelliJ 2019</li>\n          <li>v1.1 - Removed currentState from mapEventToState method</li>\n          <li>v1.0 - Initial release</li>\n        </ul>\n        ]]>\n    </change-notes>\n\n    <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->\n    <idea-version since-build=\"233\" until-build=\"243.*\"/>\n\n    <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html\n       on how to target different products -->\n    <depends>com.intellij.modules.lang</depends>\n    <depends>com.intellij.modules.java</depends>\n    <depends>com.redhat.devtools.lsp4ij</depends>\n\n    <actions>\n        <action class=\"com.bloc.intellij_generator_plugin.action.GenerateCubitAction\"\n                description=\"Generate a new Cubit\"\n                id=\"GenerateCubitId\"\n                icon=\"/icons/cubit.png\"\n                text=\"Cubit Class\">\n            <add-to-group\n                    group-id=\"NewGroup\"\n                    anchor=\"first\"/>\n        </action>\n        <action class=\"com.bloc.intellij_generator_plugin.action.GenerateBlocAction\"\n                description=\"Generate a new Bloc\"\n                id=\"GenerateBlocId\"\n                icon=\"/icons/bloc.png\"\n                text=\"Bloc Class\">\n            <add-to-group\n                    group-id=\"NewGroup\"\n                    anchor=\"first\"/>\n        </action>\n        <action class=\"com.bloc.intellij_generator_plugin.action.GenerateEquatablePropsAction\"\n                description=\"Generate Equatable Props Override\"\n                id=\"GenerateEquatablePropsId\"\n                text=\"Equatable Props\">\n            <add-to-group group-id=\"GenerateGroup\"\n                          anchor=\"last\"/>\n        </action>\n    </actions>\n\n    <extensions defaultExtensionNs=\"com.intellij\">\n        <notificationGroup id=\"Bloc Plugin Notifications\" displayType=\"BALLOON\"/>\n    </extensions>\n\n    <extensions defaultExtensionNs=\"com.intellij\">\n        <defaultLiveTemplates file=\"/liveTemplates/Bloc.xml\"/>\n        <liveTemplateContext contextId=\"BLOC\"\n                             implementation=\"com.bloc.intellij_generator_plugin.live_templates.BlocContext\"\n        />\n    </extensions>\n\n    <extensions defaultExtensionNs=\"com.intellij\">\n        <intentionAction>\n            <className>com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithBlocProviderIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithBlocBuilderIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithBlocSelectorIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithBlocListenerIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithBlocConsumerIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>\n                com.bloc.intellij_generator_plugin.intention_action.BlocWrapWithRepositoryProviderIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>\n                com.bloc.intellij_generator_plugin.intention_action.BlocConvertToMultiBlocProviderIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>\n                com.bloc.intellij_generator_plugin.intention_action.BlocConvertToMultiBlocListenerIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n        <intentionAction>\n            <className>\n                com.bloc.intellij_generator_plugin.intention_action.BlocConvertToMultiRepositoryProviderIntentionAction\n            </className>\n            <category>Bloc</category>\n        </intentionAction>\n    </extensions>\n\n    <extensions defaultExtensionNs=\"com.redhat.devtools.lsp4ij\">\n        <server id=\"blocLanguageServer\"\n                name=\"Bloc Language Server\"\n                factoryClass=\"com.bloc.intellij_generator_plugin.language_server.BlocLanguageServerFactory\">\n            <description><![CDATA[\n                Official lint rules for development when using the bloc state management library.\n            ]]></description>\n        </server>\n        <fileNamePatternMapping patterns=\"*.dart\"\n                                serverId=\"blocLanguageServer\"\n                                languageId=\"dart\"/>\n        <fileNamePatternMapping patterns=\"analysis_options.yaml\"\n                                serverId=\"blocLanguageServer\"\n                                languageId=\"yaml\"/>\n    </extensions>\n</idea-plugin>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocListenerIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: MultiBlocListener(\n          listeners: [\n            BlocListener<CounterBloc, CounterState>(\n              listener: (context, state) {\n                print(state);\n              },\n            ),\n            BlocListener<SubjectBloc, SubjectState>(\n              listener: (context, state) {\n                // TODO: implement listener\n              },\n            ),\n          ],\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocListenerIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocListener<CounterBloc, CounterState>(\n          listener: (context, state) {\n            print(state);\n          },\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocListenerIntentionAction/description.html",
    "content": "<html>\n<body>Converts BlocListener widget to MultiBlocListener.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocProviderIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: MultiBlocProvider(\n          providers: [\n            BlocProvider(\n              create: (context) => CounterBloc(),\n            ),\n            BlocProvider(\n              create: (context) => SubjectBloc(),\n            ),\n          ],\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocProviderIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocProvider(\n          create: (context) => CounterBloc(),\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiBlocProviderIntentionAction/description.html",
    "content": "<html>\n<body>Converts BlocProvider widget to MultiBlocProvider.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiRepositoryProviderIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: MultiRepositoryProvider(\n          providers: [\n            RepositoryProvider(\n              create: (context) => NumberRepository(),\n            ),\n            RepositoryProvider(\n              create: (context) => SubjectRepository(),\n            ),\n          ],\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiRepositoryProviderIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: RepositoryProvider(\n          create: (context) => NumberRepository(),\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocConvertToMultiRepositoryProviderIntentionAction/description.html",
    "content": "<html>\n<body>Converts RepositoryProvider widget to MultiRepositoryProvider.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocBuilderIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocBuilder<SubjectBloc, SubjectState>(\n          builder: (context, state) {\n            return Text('Example');\n          },\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocBuilderIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocBuilderIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a BlocBuilder.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocConsumerIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocConsumer<SubjectBloc, SubjectState>(\n          listener: (context, state) {\n            // TODO: implement listener\n          },\n          builder: (context, state) {\n            return Text('Example');\n          },\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocConsumerIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocConsumerIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a BlocConsumer.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocListenerIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocListener<SubjectBloc, SubjectState>(\n          listener: (context, state) {\n            // TODO: implement listener}\n          },\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocListenerIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocListenerIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a BlocListener.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocProviderIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocProvider(\n          create: (context) => SubjectBloc(),\n          child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocProviderIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocProviderIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a BlocProvider.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocSelectorIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: BlocSelector<SubjectBloc, SubjectState, SelectedState>(\n          selector: (state) {\n            // TODO: return selected state\n          },\n          builder: (state) {\n            return Text('Example');\n          },\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocSelectorIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithBlocSelectorIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a BlocSelector.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithRepositoryProviderIntentionAction/after.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: RepositoryProvider(\n          create: (context) => SubjectRepository(),\n            child: Text('Example'),\n        ),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithRepositoryProviderIntentionAction/before.java.template",
    "content": "class CounterScreen extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Center(\n        child: Text('Example'),\n      ),\n    );\n  }\n}"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/intentionDescriptions/BlocWrapWithRepositoryProviderIntentionAction/description.html",
    "content": "<html>\n<body>Wraps the current widget in a RepositoryProvider.</body>\n</html>"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/liveTemplates/Bloc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<templateSet group=\"Bloc\">\n    <!--        WIDGETS        -->\n    <template name=\"blocbuilder\"\n              value=\"BlocBuilder&lt;$Subject$$EnumBloc$, $Subject$State&gt;(&#10;  builder: (context, state) {&#10;    return $Container$;&#10;  },&#10;)\"\n              description=\"BlocBuilder widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"blocconsumer\"\n              value=\"BlocConsumer&lt;$Subject$$EnumBloc$, $Subject$State&gt;(&#10;  listener: (context, state) {&#10;    // TODO: implement listener&#10;  },&#10;  builder: (context, state) {&#10;    return $Container$;&#10;  },&#10;)\"\n              description=\"BlocConsumer widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"bloclistener\"\n              value=\"BlocListener&lt;$Subject$$EnumBloc$, $Subject$State&gt;(&#10;  listener: (context, state) {&#10;    // TODO: implement listener&#10;  },&#10;  child: $Container$,&#10;)\"\n              description=\"BlocListener widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"blocprovider\"\n              value=\"BlocProvider&lt;$Subject$$EnumBloc$&gt;(&#10;  create: (context) =&gt; $Subject$$EnumBloc$(),&#10;  child: $Container$,&#10;)\"\n              description=\"BlocProvider widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"blocselector\"\n              value=\"BlocSelector&lt;$Subject$$EnumBloc$, $Subject$State, $SelectedState$&gt;(&#10;  // TODO: change dynamic type&#10;  selector: (state) {&#10;    // TODO: return selected state based on the provided state.&#10;    return state.$completion$;&#10;  },&#10;  builder: (context, state) {&#10;    // TODO: return widget here based on the selected state.&#10;    return $Widget$;&#10;  },&#10;)&#10;\"\n              description=\"BlocSelector widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"completion\" expression=\"complete()\" defaultValue=\"\" alwaysStopAt=\"true\"/>\n        <variable name=\"SelectedState\" expression=\"\" defaultValue=\"&quot;dynamic&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Widget\" expression=\"\" defaultValue=\"&quot;Text(state.toString())&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"multibloclistener\"\n              value=\"MultiBlocListener(&#10;  listeners: [&#10;    BlocListener&lt;$Subject$$EnumBloc$, $Subject$State&gt;(&#10;      listener: (context, state) {&#10;        // TODO: implement listener&#10;      },&#10;    ),&#10;    BlocListener&lt;$Subject2$$EnumBloc2$, $Subject2$State&gt;(&#10;      listener: (context, state) {&#10;        // TODO: implement listener&#10;      },&#10;    ),&#10;  ],&#10;  child: $Container$,&#10;)\"\n              description=\"MultiBlocListener widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Subject2\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc2\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"multiblocprovider\"\n              value=\"MultiBlocProvider(&#10;  providers: [&#10;    BlocProvider&lt;$Subject$$EnumBloc$&gt;(&#10;      create: (context) =&gt; $Subject$$EnumBloc$(),&#10;    ),&#10;    BlocProvider&lt;$Subject2$$EnumBloc2$&gt;(&#10;      create: (context) =&gt; $Subject2$$EnumBloc2$(),&#10;    ),&#10;  ],&#10;  child: $Container$,&#10;)\"\n              description=\"MultiBlocProvider widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Subject2\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc2\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"multirepoprovider\"\n              value=\"MultiRepositoryProvider(&#10;  providers: [&#10;    RepositoryProvider(&#10;      create: (context) =&gt; $Subject$Repository(),&#10;    ),&#10;    RepositoryProvider(&#10;      create: (context) =&gt; $Subject2$Repository(),&#10;    ),&#10;  ],&#10;  child: $Container$,&#10;)\"\n              description=\"MultiRepositoryProvider widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Subject2\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"repoprovider\"\n              value=\"RepositoryProvider(&#10;  create: (context) =&gt; $Subject$Repository(),&#10;  child: $Container$,&#10;)\"\n              description=\"RepositoryProvider widget\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Container\" expression=\"\" defaultValue=\"&quot;Container()&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n        </context>\n    </template>\n\n    <!--        CLASSES        -->\n    <template name=\"bloc\"\n              value=\"class $Subject$Bloc extends Bloc&lt;$Subject$Event, $Subject$State&gt; {&#10;  $Subject$Bloc() : super($Subject$Initial()) {&#10;    on&lt;$Subject$Event&gt;((event, emit) {&#10;        // TODO: implement event handler    &#10;    });&#10;  }&#10;}\"\n              description=\"bloc class\" toReformat=\"true\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"onevent\"\n              value=\"on&lt;$eventName$&gt;((event, emit) {&#10;  // TODO: implement event handler&#10;});\"\n              description=\"register a new event handler\" toReformat=\"false\"\n              toShortenFQNames=\"true\">\n        <variable name=\"eventName\" expression=\"\" defaultValue=\"&quot;EventName&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"blocevent\"\n              value=\"class $Subject$$Noun$$Verb$ extends $Subject$Event {&#10;  const $Subject$$Noun$$Verb$();&#10;&#10;  &#10;&#10;  @override&#10;  List&lt;Object&gt; get props =&gt; [];&#10;}\"\n              description=\"bloc event class\" toReformat=\"true\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Noun\" expression=\"\" defaultValue=\"&quot;Noun&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Verb\" expression=\"\" defaultValue=\"&quot;Verb&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"blocobserver\"\n              value=\"import 'package:bloc/bloc.dart';&#10;&#10;class $My$BlocObserver extends BlocObserver {&#10;  @override&#10;  void onEvent(Bloc bloc, Object? event) {&#10;    super.onEvent(bloc, event);&#10;    print('${bloc.runtimeType} $event');&#10;  }&#10;&#10;  @override&#10;  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {&#10;    print('${bloc.runtimeType} $error $stackTrace');&#10;    super.onError(bloc, error, stackTrace);&#10;  }&#10;&#10;  @override&#10;  void onChange(BlocBase bloc, Change change) {&#10;    super.onChange(bloc, change);&#10;    print('${bloc.runtimeType} $change');&#10;  }&#10;&#10;  @override&#10;  void onTransition(Bloc bloc, Transition transition) {&#10;    super.onTransition(bloc, transition);&#10;    print('${bloc.runtimeType} $transition');&#10;  }&#10;}\"\n              description=\"BlocObserver class\" toReformat=\"true\" toShortenFQNames=\"true\">\n        <variable name=\"My\" expression=\"\" defaultValue=\"&quot;My&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"blocstate\"\n              value=\"class $Subject$$Verb$State extends $Subject$State {&#10;  const $Subject$$Verb$State();&#10;&#10;  &#10;&#10;  @override&#10;  List&lt;Object&gt; get props =&gt; [];&#10;}\"\n              description=\"bloc state class\" toReformat=\"true\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"Verb\" expression=\"\" defaultValue=\"&quot;Verb&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"cubit\"\n              value=\"class $Subject$Cubit extends Cubit&lt;$Subject$State&gt; {&#10;  $Subject$Cubit() : super($Subject$Initial());&#10;}\"\n              description=\"cubit class\" toReformat=\"true\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n\n    <!--        MISC        -->\n    <template name=\"read\" value=\"context.read&lt;$Subject$$EnumBloc$&gt;()\" description=\"context.read()\"\n              toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"repoof\" value=\"RepositoryProvider.of&lt;$Subject$Repository&gt;(context)\"\n              description=\"RepositoryProvider.of()\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"select\"\n              value=\"context.select(($Subject$$EnumBloc$ $enumBlocLowercase$) =&gt; $enumBlocLowercase$.state)\"\n              description=\"context.select()\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"enumBlocLowercase\" expression=\"lowercaseAndDash(EnumBloc)\" defaultValue=\"&quot;bloc&quot;\"\n                  alwaysStopAt=\"false\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"watch\" value=\"context.watch&lt;$Subject$$EnumBloc$&gt;()\" description=\"context.watch()\"\n              toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"blocof\" value=\"BlocProvider.of&lt;$Subject$$EnumBloc$&gt;(context)\" description=\"BlocProvider.of()\"\n              toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"bloctest\"\n              value=\"blocTest&lt;$Subject$$EnumBloc$, $Subject$State&gt;(&#10;  $DESCRIPTION$,&#10;  build: () =&gt; $Subject$$EnumBloc$(),&#10;  act: ($enumBlocLowercase$) {&#10;    // TODO: implement&#10;  },&#10;  expect: () =&gt; &lt;$Subject$State&gt;[&#10;    // TODO: implement&#10;  ],&#10;);\"\n              description=\"blocTest with build, act and expect\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"EnumBloc\" expression=\"enum(&quot;Bloc&quot;,&quot;Cubit&quot;)\" defaultValue=\"&quot;Bloc&quot;\"\n                  alwaysStopAt=\"true\"/>\n        <variable name=\"DESCRIPTION\" expression=\"\" defaultValue=\"&quot;'TODO: description'&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"enumBlocLowercase\" expression=\"lowercaseAndDash(EnumBloc)\" defaultValue=\"&quot;bloc&quot;\"\n                  alwaysStopAt=\"false\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"importbloc\" value=\"import 'package:bloc/bloc.dart';\" description=\"Import package:bloc\"\n              toReformat=\"false\" toShortenFQNames=\"true\">\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"importflutterbloc\" value=\"import 'package:flutter_bloc/flutter_bloc.dart';\"\n              description=\"Import package:flutter_bloc\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"importbloctest\" value=\"import 'package:bloc_test/bloc_test.dart';\"\n              description=\"Import package:bloc_test\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <context>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"mockbloc\"\n              value=\"class Mock$Subject$Bloc extends MockBloc&lt;$Subject$Event, $Subject$State&gt; implements $Subject$Bloc {}\"\n              description=\"Create a mock bloc\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"mockcubit\"\n              value=\"class Mock$Subject$Cubit extends MockCubit&lt;$Subject$State&gt; implements $Subject$Cubit {}\"\n              description=\"Create a mock cubit\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"fake\" value=\"class Fake$Subject$ extends Fake implements $Subject$ {}\"\n              description=\"Create a fake object\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n    <template name=\"mock\" value=\"class Mock$Subject$ extends Mock implements $Subject$ {}\"\n              description=\"Create a mock object\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"Subject\" expression=\"\" defaultValue=\"&quot;Subject&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"DART\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"true\"/>\n        </context>\n    </template>\n\n    <!--        FREEZED        -->\n    <template name=\"fstate\" value=\"const factory $CLASS_NAME$.$stateName$() = _$stateName$;\"\n              description=\"freezed sub state\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"CLASS_NAME\" expression=\"dartClassName()\" defaultValue=\"&quot;&quot;\" alwaysStopAt=\"false\"/>\n        <variable name=\"stateName\" expression=\"\" defaultValue=\"&quot;stateName&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"fevent\" value=\"const factory $CLASS_NAME$.$eventName$() = _$eventName$;\"\n              description=\"freezed sub event\" toReformat=\"false\" toShortenFQNames=\"true\">\n        <variable name=\"CLASS_NAME\" expression=\"dartClassName()\" defaultValue=\"&quot;&quot;\" alwaysStopAt=\"false\"/>\n        <variable name=\"eventName\" expression=\"\" defaultValue=\"&quot;eventName&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"feventmap\"\n              value=\"void _on$eventName$Event($eventName$Event event, Emitter&lt;$BASE_CLASS_NAME$State&gt; emit) {&#10;&#10;}\"\n              description=\"event handler with freeze.map function\" toReformat=\"false\"\n              toShortenFQNames=\"true\">\n        <variable name=\"BASE_CLASS_NAME\"\n                  expression=\"regularExpression(dartClassName(), &quot;(Bloc)|(Cubit)&quot;, &quot;&quot;)\"\n                  defaultValue=\"&quot;&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"eventName\" expression=\"\" defaultValue=\"&quot;eventName&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n    <template name=\"feventwhen\"\n              value=\"void _on$eventName$Event(Emitter&lt;$BASE_CLASS_NAME$State&gt; emit) {&#10;&#10;}\"\n              description=\"event handler with freeze.when function\" toReformat=\"false\"\n              toShortenFQNames=\"true\">\n        <variable name=\"BASE_CLASS_NAME\"\n                  expression=\"regularExpression(dartClassName(), &quot;(Bloc)|(Cubit)&quot;, &quot;&quot;)\"\n                  defaultValue=\"&quot;&quot;\" alwaysStopAt=\"true\"/>\n        <variable name=\"eventName\" expression=\"\" defaultValue=\"&quot;eventName&quot;\" alwaysStopAt=\"true\"/>\n        <context>\n            <option name=\"BLOC\" value=\"true\"/>\n            <option name=\"DART_TOPLEVEL\" value=\"false\"/>\n        </context>\n    </template>\n</templateSet>\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_basic/bloc.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart '{{bloc_snake_case}}_event.dart';\npart '{{bloc_snake_case}}_state.dart';\n\nclass {{bloc_pascal_case}}Bloc extends Bloc<{{bloc_pascal_case}}Event, {{bloc_pascal_case}}State> {\n  {{bloc_pascal_case}}Bloc() : super({{bloc_pascal_case}}Initial()) {\n    on<{{bloc_pascal_case}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_basic/bloc_event.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\n@immutable\nsealed class {{bloc_pascal_case}}Event {}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_basic/bloc_state.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\n@immutable\nsealed class {{bloc_pascal_case}}State {}\n\nfinal class {{bloc_pascal_case}}Initial extends {{bloc_pascal_case}}State {}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_equatable/bloc.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '{{bloc_snake_case}}_event.dart';\npart '{{bloc_snake_case}}_state.dart';\n\nclass {{bloc_pascal_case}}Bloc extends Bloc<{{bloc_pascal_case}}Event, {{bloc_pascal_case}}State> {\n  {{bloc_pascal_case}}Bloc() : super({{bloc_pascal_case}}Initial()) {\n    on<{{bloc_pascal_case}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_equatable/bloc_event.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\nsealed class {{bloc_pascal_case}}Event extends Equatable {\n  const {{bloc_pascal_case}}Event();\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_equatable/bloc_state.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\nsealed class {{bloc_pascal_case}}State extends Equatable {\n  const {{bloc_pascal_case}}State();\n}\n\nfinal class {{bloc_pascal_case}}Initial extends {{bloc_pascal_case}}State {\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_freezed/bloc.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '{{bloc_snake_case}}_event.dart';\npart '{{bloc_snake_case}}_state.dart';\npart '{{bloc_snake_case}}_bloc.freezed.dart';\n\nclass {{bloc_pascal_case}}Bloc extends Bloc<{{bloc_pascal_case}}Event, {{bloc_pascal_case}}State> {\n  {{bloc_pascal_case}}Bloc() : super(const {{bloc_pascal_case}}State.initial()) {\n    on<{{bloc_pascal_case}}Event>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_freezed/bloc_event.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\n@freezed\nclass {{bloc_pascal_case}}Event with _${{bloc_pascal_case}}Event {\n  const factory {{bloc_pascal_case}}Event.started() = _Started;\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/bloc_freezed/bloc_state.dart.template",
    "content": "part of '{{bloc_snake_case}}_bloc.dart';\n\n@freezed\nclass {{bloc_pascal_case}}State with _${{bloc_pascal_case}}State {\n  const factory {{bloc_pascal_case}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_basic/cubit.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart '{{cubit_snake_case}}_state.dart';\n\nclass {{cubit_pascal_case}}Cubit extends Cubit<{{cubit_pascal_case}}State> {\n  {{cubit_pascal_case}}Cubit() : super({{cubit_pascal_case}}Initial());\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_basic/cubit_state.dart.template",
    "content": "part of '{{cubit_snake_case}}_cubit.dart';\n\n@immutable\nsealed class {{cubit_pascal_case}}State {}\n\nfinal class {{cubit_pascal_case}}Initial extends {{cubit_pascal_case}}State {}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_equatable/cubit.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '{{cubit_snake_case}}_state.dart';\n\nclass {{cubit_pascal_case}}Cubit extends Cubit<{{cubit_pascal_case}}State> {\n  {{cubit_pascal_case}}Cubit() : super({{cubit_pascal_case}}Initial());\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_equatable/cubit_state.dart.template",
    "content": "part of '{{cubit_snake_case}}_cubit.dart';\n\nsealed class {{cubit_pascal_case}}State extends Equatable {\n  const {{cubit_pascal_case}}State();\n}\n\nfinal class {{cubit_pascal_case}}Initial extends {{cubit_pascal_case}}State {\n  @override\n  List<Object> get props => [];\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_freezed/cubit.dart.template",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '{{cubit_snake_case}}_state.dart';\npart '{{cubit_snake_case}}_cubit.freezed.dart';\n\nclass {{cubit_pascal_case}}Cubit extends Cubit<{{cubit_pascal_case}}State> {\n  {{cubit_pascal_case}}Cubit() : super(const {{cubit_pascal_case}}State.initial());\n}\n"
  },
  {
    "path": "extensions/intellij/intellij_generator_plugin/src/main/resources/templates/cubit_freezed/cubit_state.dart.template",
    "content": "part of '{{cubit_snake_case}}_cubit.dart';\n\n@freezed\nclass {{cubit_pascal_case}}State with _${{cubit_pascal_case}}State {\n  const factory {{cubit_pascal_case}}State.initial() = _Initial;\n}\n"
  },
  {
    "path": "extensions/vscode/.gitignore",
    "content": "node_modules\nout/\ndist/\n*.vsix"
  },
  {
    "path": "extensions/vscode/.vscode/launch.json",
    "content": "// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Run Extension\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"runtimeExecutable\": \"${execPath}\",\n      \"args\": [\"--extensionDevelopmentPath=${workspaceFolder}\"],\n      \"outFiles\": [\"${workspaceFolder}/dist/**/*.js\"],\n      \"preLaunchTask\": \"npm: compile\"\n    }\n  ]\n}\n"
  },
  {
    "path": "extensions/vscode/.vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\nnode_modules\nout/**\nsrc/**\n.gitignore\nvsc-extension-quickstart.md\nwebpack.config.js\n**/tsconfig.json\n**/tslint.json\n**/*.map\n**/*.ts"
  },
  {
    "path": "extensions/vscode/CHANGELOG.md",
    "content": "# 6.8.13\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.21`\n- deps: various dependency upgrades\n\n# 6.8.12\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.20`\n\n# 6.8.11\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.19`\n- deps: various dependency upgrades\n\n# 6.8.10\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.18`\n- deps: various dependency upgrades\n- docs: fix broken link in `README`\n\n# 6.8.9\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.17`\n\n# 6.8.8\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.16`\n  - fix: increased CPU usage when using `fvm`\n- deps: various dependency upgrades\n\n# 6.8.7\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.15`\n\n# 6.8.6\n\n- fix: support for x64 and arm64 `bloc_tools` executables\n\n# 6.8.5\n\n- fix: install precompiled `bloc_tools` executable\n- deps: upgrade to `bloc_tools: 0.1.0-dev.13`\n\n# 6.8.4\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.12`\n\n# 6.8.3\n\n- fix: add Dart SDK constraint for language server\n\n# 6.8.2\n\n- deps: upgrade to `bloc_tools: 0.1.0-dev.11`\n\n# 6.8.1\n\n- feat: improve bloc tools install/upgrade ux\n- fix: various language server bugs on windows\n- deps: upgrade to `bloc_tools: 0.1.0-dev.10`\n\n# 6.8.0\n\n- feat: add language server\n\n# 6.7.0\n\n- deps: upgrade vscode engine to ^1.75.0\n- deps: upgrade webpack and braces\n\n# 6.6.6\n\n- fix: wrap with preserves escaping\n- fix: wrap with preserves `const`\n- docs: update docs site references\n- chore: update copyright year\n\n# 6.6.5\n\n- fix: show context menu conditionally\n\n# 6.6.4\n\n- feat: add `useSealedClasses` to extension settings\n- docs: fix `README` badges\n- deps: upgrade various dependencies\n\n# 6.6.3\n\n- fix: wrap with interpolation\n- deps: upgrade various dependencies\n\n# 6.6.2\n\n- fix: rename element to bloc in `context.select` snippet\n\n# 6.6.1\n\n- fix: bloc and cubit template casing\n\n# 6.6.0\n\n- feat: add private mock snippets\n  - `_mockbloc`\n  - `_mockcubit`\n  - `_fake`\n  - `_mock`\n\n# 6.5.1\n\n- fix: reduce bundle size\n\n# 6.5.0\n\n- fix: update `BlocObserver` snippet to resolve Dart analyzer warning\n- feat: determine versions via `pubspec.lock`\n- feat: add `Mock` snippet\n- chore: upgrade dependencies\n\n# 6.4.0\n\n- feat: add `_onevent` snippet for creating an internal event handler\n- feat: improve `onevent` snippet to infer default event type\n- feat: add new extension settings:\n  - `bloc.newBlocTemplate.createDirectory` (defaults to true)\n  - `bloc.newCubitTemplate.createDirectory` (defaults to true)\n- refactor: remove deprecated snippets:\n  - `feventwhen`\n  - `feventmap`\n\n# 6.3.0\n\n- feat: add snippets for:\n  - `MockBloc`\n  - `MockCubit`\n  - `Fake`\n\n# 6.2.0\n\n- feat: query latest package versions from pub.dev\n\n# 6.1.0\n\n- feat: update to latest packages\n  - bloc -> ^7.2.1\n  - flutter_bloc -> ^7.3.3\n  - bloc_test -> ^8.5.0\n- fix:(vscode): escape $ in \"wrap with\" and \"convert to\"\n\n# 6.0.1\n\n- chore: remove unnecessary abstract keyword from freezed template\n\n# 6.0.0\n\n- **BREAKING**: update to bloc ^7.2.0\n  - update snippets to use `on<Event>` instead of deprecated `mapEventToState`\n- feat: add `onevent` snippet to register a new `EventHandler`\n- feat: update to latest packages\n  - bloc -> ^7.2.0\n  - flutter_bloc -> ^7.3.0\n  - angular_bloc -> ^7.1.0\n  - bloc_test -> ^8.2.0\n  - replay_bloc -> ^0.1.0\n  - bloc_concurrency -> ^0.1.0\n  - sealed_flutter_bloc -> ^7.1.0\n\n# 5.8.0\n\n- feat: add \"Convert to...\" Multi-Widget Actions\n  - `Convert to MultiBlocListener`\n  - `Convert to MultiBlocProvider`\n  - `Convert to MultiRepositoryProvider`\n\n# 5.7.0\n\n- feat: add snippets for `BlocSelector`\n- feat: add `Wrap with BlocSelector` action\n- feat: update to latest packages\n  - bloc_test -> ^8.1.0\n  - equatable -> ^2.0.3\n  - flutter_bloc -> ^7.1.0\n  - hydrated_bloc -> ^7.0.1\n  - sealed_flutter_bloc -> ^7.0.0\n\n# 5.6.2\n\n- fix: \"Wrap with...\" selection issues\n\n# 5.6.1\n\n- feat: add snippets for `bloc_test`\n- feat: add snippets for importing `bloc`, `bloc_test` and `flutter_bloc`\n- fix: show code actions in refactorings menu\n\n# 5.6.0\n\n- feat: freezed classes no longer require [abstract](https://pub.dev/packages/freezed#the-abstract-keyword) keyword when using freezed >= 0.14.0\n- feat: update `BlocObserver` snippets to support null safety and bloc v7.0.0\n- feat: update to latest packages\n  - bloc -> ^7.0.0\n  - bloc_test -> ^8.0.0\n  - equatable -> ^2.0.0\n  - flutter_bloc -> ^7.0.0\n  - hydrated_bloc -> ^7.0.0\n\n# 5.5.1\n\n- feat: improve `context.select` extension snippet\n\n# 5.5.0\n\n- feat: add `checkForUpdates` configuration in extension settings\n\n# 5.4.0\n\n- feat: add snippets for:\n  - `context.read`\n  - `context.select`\n  - `context.watch`\n- feat: update to latest packages\n  - angular_bloc -> ^6.0.1\n  - bloc -> ^6.1.0\n  - bloc_test -> ^7.1.0\n  - equatable -> ^1.2.5\n  - flutter_bloc -> ^6.1.0\n  - hydrated_bloc -> ^6.0.3\n  - sealed_flutter_bloc -> ^6.0.0\n\n# 5.3.2\n\n- fix: update dependencies to fix potential security vulnerabilities\n\n# 5.3.1\n\n- fix: freezed cubit template typo\n\n# 5.3.0\n\n- feat: make templates configurable via workspace settings\n- feat: improve cubit state equatable template\n- fix: remove unused dependency in freezed bloc template\n\n# 5.2.0\n\n- feat: updates to snippets\n  - `contextbloc` -> `ctxbloc`\n  - `contextrepository` -> `ctxrepo`\n  - `repositoryof` -> `repoof`\n  - `repositoryprovider` -> `repoprovider`\n  - `multirepositoryprovider` -> `multirepoprovider`\n  - `blocstate` (new)\n  - `blocevent` (new)\n\n# 5.1.1\n\n- fix: freezed template typo\n\n# 5.1.0\n\n- feat: add new bloc/cubit support for `package:freezed`\n- feat: add snippet support for `package:freezed`\n  - `fstate`: new freezed state\n  - `fevent`: new freezed event\n  - `feventwhen`: new freezed event.when helper function\n  - `feventmap`: new freezed event.map helper function\n- feat: add bloc code actions\n  - Wrap with `BlocBuilder`\n  - Wrap with `BlocListener`\n  - Wrap with `BlocConsumer`\n  - Wrap with `BlocProvider`\n  - Wrap with `RepositoryProvider`\n\n# 5.0.0\n\n- **BREAKING**: update to latest bloc packages\n  - bloc -> ^6.0.0\n  - bloc_test -> ^7.0.0\n  - flutter_bloc -> ^6.0.0\n  - hydrated_bloc -> ^6.0.0\n- fix: remove error dialog when no `pubspec.yaml` found in root\n\n# 4.2.2\n\n- fix: Equatable not being recognized on Windows\n\n# 4.2.1\n\n- fix: `CubitObserver` snippet fixes\n- fix: `BlocObserver` snippet fixes\n\n# 4.2.0\n\n- feat: Add Open Migration Guide for `bloc`, `flutter_bloc`, and `hydrated_bloc`\n\n# 4.1.1\n\n- fix: package version analysis on dev_dependencies\n\n# 4.1.0\n\n- Include \"Cubit: New Cubit\" command to generate a cubit and state\n- Include Cubit Ecosystem when performing package version analysis\n- Infer Equatable Usage\n- Update snippets to support\n  - `Cubit`\n  - `CubitBuilder`\n  - `CubitListener`\n  - `MultiCubitListener`\n  - `CubitConsumer`\n  - `CubitProvider`\n  - `MultiCubitProvider`\n  - `CubitObserver`\n  - `context.cubit()`\n  - `CubitProvider.of()`\n\n# 4.0.0\n\nUpdate latest package versions:\n\n- equatable -> ^1.2.0\n- bloc -> ^5.0.0\n- bloc_test -> ^6.0.0\n- flutter_bloc -> ^5.0.0\n- hydrated_bloc -> ^5.0.0\n\n# 3.6.0\n\nUpdate latest package versions:\n\n- bloc_test -> ^5.1.0\n- hydrated_bloc -> ^4.0.0\n- sealed_flutter_bloc -> ^4.0.0\n\n# 3.5.0\n\nUpdate latest package versions:\n\n- bloc_test -> ^5.0.0\n- equatable -> ^1.1.1\n- angular_bloc -> ^4.0.0\n- bloc -> ^4.0.0\n- flutter_bloc -> ^4.0.0\n\n# 3.4.0\n\nUpdate snippets to support\n\n- `context.bloc<MyBloc>()`\n- `context.repository<MyRepository>()`\n\n# 3.3.0\n\nUpdate latest package versions:\n\n- bloc_test -> ^4.0.0\n- equatable -> ^1.1.0\n- flutter_bloc -> ^3.2.0\n\n# 3.2.0\n\nUpdate bloc generator to use `parts` (removes barrel file)\nUpdate dependency analyzer to handle `any` version\n\n# 3.1.0\n\nUpdate to support flutter_bloc `v3.1.0`\nUpdate Snippets for:\n\n- `BlocConsumer` (blocconsumer)\n- `BlocProvider.of` (blocof)\n- `RepositoryProvider.of` (repositoryof)\n\n# 3.0.1\n\nHotfix for `command 'extension.new-bloc' not found`\n\n# 3.0.0\n\nUpdate to support bloc `v3.0.0`\n\n# 2.2.0\n\nUpdate snippets to support flutter_bloc `v2.1.0`\n\n# 2.1.0\n\nUpdate to support equatable `v1.0.0`\n\n# 2.0.0\n\nUpdate to support bloc `v2.0.0`\n\n# 1.0.0\n\nUpdate to support bloc `v1.0.0`\n\n# 0.13.0\n\nUpdate Snippets to support changes in bloc `v0.16.0`\n\n# 0.12.2\n\nAdd Update Action to automatically update outdated dependencies\n\n# 0.12.1\n\nAdd detection for outdated dependencies in workspace.\n\n# 0.12.0\n\nUpdate Snippets and New Bloc to support changes in equatable `v0.6.0`\n\n# 0.11.1\n\n`Equatable` enhancement to address `implicit-dynamic` warning ([#463](https://github.com/felangel/bloc/pull/463)).\n\n# 0.11.0\n\nUpdate Snippets to support changes in flutter_bloc `v0.20.0`\n\n# 0.10.1\n\n- Minor Documentation Updates\n\n# 0.10.0\n\nUpdate Snippets for:\n\n- `RepositoryProvider`\n- `MultiRepositoryProvider`\n- `MultiBlocProvider`\n- `MultiBlocListener`\n\nto support changes in flutter_bloc `v0.19.0`\n\n# 0.9.0\n\nUpdate Snippets for:\n\n- `ImmutableProvider`\n- `ImmutableProviderTree`\n\nto support changes in flutter_bloc `v0.18.0`\n\n# 0.8.0\n\nUpdate Snippets for:\n\n- `BlocProvider`\n- `BlocProviderTree`\n\nto support changes in flutter_bloc `v0.17.0`\n\n# 0.7.0\n\nUpdate Snippets for:\n\n- `BlocProvider`\n- `BlocProviderTree`\n\nto support changes in flutter_bloc `v0.16.0`\n\n# 0.6.2\n\nUpdated `BlocDelegate` snippet to support changes in bloc `v0.13.0`\n\n# 0.6.1\n\nBloc generation does not overwrite existing bloc files; it attempts to merge instead.\n\n# 0.6.0\n\nAdded Snippets for:\n\n- `BlocListenerTree`\n\n# 0.5.0\n\nAdded Snippets for:\n\n- `BlocListener`\n\n# 0.4.2\n\nUpdate BlocDelegate Snippet to include calls to `super`\n\n# 0.4.1\n\nAdded Snippets for:\n\n- `BlocEvent`\n- `BlocState`\n\n# 0.4.0\n\nSupport for `bloc v0.11.0`\n\n# 0.3.2\n\nAdded `@immutable` decorator to bloc events and states\n\n# 0.3.1\n\nAdded Snippet for `BlocDelegate`\n\n# 0.3.0\n\nAdded Snippets for `BlocBuilder`, `BlocProvider`, and `BlocProviderTree`\n\n# 0.2.1\n\nUpdate `Equatable` usage to be \"advanced\"\n\n# 0.2.0\n\nUpdated to include \"Bloc: New Bloc\" to generate full bloc directory structure.\n\n# 0.1.3\n\nMinor Documentation Updates\n\n# 0.1.2\n\nMinor Documentation Updates\n\n# 0.1.1\n\nMinor Documentation Updates\n\n# 0.1.0\n\nInitial Version of the Extension.\n\n- Includes the ability to create a Bloc snippet\n"
  },
  {
    "path": "extensions/vscode/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "extensions/vscode/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc.png\" height=\"100\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n</p>\n<p align=\"center\">\n<a href=\"https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc\"><img src=\"https://img.shields.io/visual-studio-marketplace/v/FelixAngelov.bloc\" alt=\"Version\"></a>\n<a href=\"https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc\"><img src=\"https://img.shields.io/visual-studio-marketplace/d/FelixAngelov.bloc\" alt=\"Downloads\"></a>\n<a href=\"https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc\"><img src=\"https://img.shields.io/visual-studio-marketplace/i/FelixAngelov.bloc\" alt=\"Installs\"></a>\n<a href=\"https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc\"><img src=\"https://img.shields.io/visual-studio-marketplace/r/FelixAngelov.bloc\" alt=\"Ratings\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"http://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\n## Overview\n\n[VSCode](https://code.visualstudio.com/) support for the [Bloc Library](https://bloclibrary.dev) and provides tools for effectively creating [Blocs](https://github.com/felangel/bloc) and [Cubits](https://github.com/felangel/cubit) for both [Flutter](https://flutter.dev/) and [AngularDart](https://angulardart.dev/) apps.\n\n## Installation\n\nBloc can be installed from the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) or by [searching within VSCode](https://code.visualstudio.com/docs/editor/extension-gallery#_search-for-an-extension).\n\n## Language Server\n\nBy default, the bloc language server started which enables custom bloc diagnotic reporting. See [the official documentation](https://bloclibrary.dev/lint) for more information about configuring the linter and the various supported lint rules.\n\n## Commands\n\n| Command            | Description          |\n| ------------------ | -------------------- |\n| `Bloc: New Bloc`   | Generate a new Bloc  |\n| `Cubit: New Cubit` | Generate a new Cubit |\n\nYou can activate the commands by launching the command palette (View -> Command Palette) and running entering the command name or you can right click on the directory in which you'd like to create the bloc/cubit and select the command from the context menu.\n\n![demo](https://raw.githubusercontent.com/felangel/bloc/master/extensions/vscode/assets/new-bloc-usage.gif)\n\n## Code Actions\n\n| Action                               | Description                                                            |\n| ------------------------------------ | ---------------------------------------------------------------------- |\n| `Convert to MultiBlocListener`       | Converts current `BlocListener` into a `MultiBlocListener`             |\n| `Convert to MultiBlocProvider`       | Converts current `BlocProvider` into a `MultiBlocProvider`             |\n| `Convert to MultiRepositoryProvider` | Converts current `RepositoryProvider` into a `MultiRepositoryProvider` |\n| `Wrap with BlocBuilder`              | Wraps current widget in a `BlocBuilder`                                |\n| `Wrap with BlocSelector`             | Wraps current widget in a `BlocSelector`                               |\n| `Wrap with BlocListener`             | Wraps current widget in a `BlocListener`                               |\n| `Wrap with BlocConsumer`             | Wraps current widget in a `BlocConsumer`                               |\n| `Wrap with BlocProvider`             | Wraps current widget in a `BlocProvider`                               |\n| `Wrap with RepositoryProvider`       | Wraps current widget in a `RepositoryProvider`                         |\n\n![demo](https://raw.githubusercontent.com/felangel/bloc/master/extensions/vscode/assets/wrap-with-usage.gif)\n\n## Snippets\n\n### Bloc\n\n| Shortcut            | Description                                   |\n| ------------------- | --------------------------------------------- |\n| `importbloc`        | Imports `package:bloc`                        |\n| `importflutterbloc` | Imports `package:flutter_bloc`                |\n| `importbloctest`    | Imports `package:bloc_test`                   |\n| `bloc`              | Creates a bloc class                          |\n| `cubit`             | Creates a cubit class                         |\n| `onevent`           | Register a new `EventHandler`                 |\n| `_onevent`          | Define a new `EventHandler`                   |\n| `blocobserver`      | Creates a `BlocObserver` class                |\n| `blocprovider`      | Creates a `BlocProvider` widget               |\n| `multiblocprovider` | Creates a `MultiBlocProvider` widget          |\n| `repoprovider`      | Creates a `RepositoryProvider` widget         |\n| `multirepoprovider` | Creates a `MultiRepositoryProvider` widget    |\n| `blocbuilder`       | Creates a `BlocBuilder` widget                |\n| `blocselector`      | Creates a `BlocSelector` widget               |\n| `bloclistener`      | Creates a `BlocListener` widget               |\n| `multibloclistener` | Creates a `MultiBlocListener` widget          |\n| `blocconsumer`      | Creates a `BlocConsumer` widget               |\n| `blocof`            | Shortcut for `BlocProvider.of()`              |\n| `repoof`            | Shortcut for `RepositoryProvider.of()`        |\n| `read`              | Shortcut for `context.read()`                 |\n| `watch`             | Shortcut for `context.watch()`                |\n| `select`            | Shortcut for `context.select()`               |\n| `blocstate`         | Creates a state class                         |\n| `blocevent`         | Creates an event class                        |\n| `bloctest`          | Creates a `blocTest`                          |\n| `mockbloc`          | Creates a class extending `MockBloc`          |\n| `_mockbloc`         | Creates a private class extending `MockBloc`  |\n| `mockcubit`         | Creates a class extending `MockCubit`         |\n| `_mockcubit`        | Creates a private class extending `MockCubit` |\n| `fake`              | Creates a class extending `Fake`              |\n| `_fake`             | Creates a private class extending `Fake`      |\n| `mock`              | Creates a class extending `Mock`              |\n| `_mock`             | Creates a private class extending `Mock`      |\n\n### Freezed Bloc\n\n| Shortcut | Description             |\n| -------- | ----------------------- |\n| `fstate` | Creates a freezed state |\n| `fevent` | Creates a freezed event |\n"
  },
  {
    "path": "extensions/vscode/package.json",
    "content": "{\n  \"name\": \"bloc\",\n  \"displayName\": \"bloc\",\n  \"description\": \"Support for the bloc library and provides tools for effectively creating blocs for both Flutter and AngularDart apps.\",\n  \"version\": \"6.8.13\",\n  \"publisher\": \"FelixAngelov\",\n  \"bugs\": {\n    \"url\": \"https://github.com/felangel/bloc/issues\",\n    \"email\": \"felangelov@gmail.com\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/felangel/bloc\"\n  },\n  \"homepage\": \"https://bloclibrary.dev\",\n  \"engines\": {\n    \"vscode\": \"^1.75.0\"\n  },\n  \"categories\": [\n    \"Snippets\",\n    \"Programming Languages\"\n  ],\n  \"keywords\": [\n    \"dart\",\n    \"flutter\",\n    \"angulardart\",\n    \"bloc\",\n    \"state-management\",\n    \"language-server\"\n  ],\n  \"icon\": \"assets/logo.png\",\n  \"activationEvents\": [\n    \"workspaceContains:**/pubspec.yaml\"\n  ],\n  \"main\": \"./dist/extension\",\n  \"contributes\": {\n    \"configuration\": [\n      {\n        \"title\": \"Bloc\",\n        \"properties\": {\n          \"bloc.checkForUpdates\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to check if you are using the latest package versions at startup.\"\n          },\n          \"bloc.languageServer.enabled\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to run the bloc language server at startup.\"\n          },\n          \"bloc.newBlocTemplate.type\": {\n            \"type\": \"string\",\n            \"default\": \"auto\",\n            \"enum\": [\n              \"auto\",\n              \"equatable\",\n              \"freezed\",\n              \"simple\"\n            ],\n            \"enumDescriptions\": [\n              \"automatically pick template based on dependencies\",\n              \"always use equatable template\",\n              \"always use freezed template\",\n              \"always use simple template\"\n            ]\n          },\n          \"bloc.newBlocTemplate.createDirectory\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to create a bloc directory when creating a new bloc.\"\n          },\n          \"bloc.newBlocTemplate.useSealedClasses\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to use sealed classes when creating a new bloc.\"\n          },\n          \"bloc.newCubitTemplate.type\": {\n            \"type\": \"string\",\n            \"default\": \"auto\",\n            \"enum\": [\n              \"auto\",\n              \"equatable\",\n              \"freezed\",\n              \"simple\"\n            ],\n            \"enumDescriptions\": [\n              \"automatically pick template based on dependencies\",\n              \"always use equatable template\",\n              \"always use freezed template\",\n              \"always use simple template\"\n            ]\n          },\n          \"bloc.newCubitTemplate.createDirectory\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to create a cubit directory when creating a new cubit.\"\n          },\n          \"bloc.newCubitTemplate.useSealedClasses\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"description\": \"Whether to use sealed classes when creating a new cubit.\"\n          }\n        }\n      }\n    ],\n    \"commands\": [\n      {\n        \"command\": \"extension.new-bloc\",\n        \"title\": \"Bloc: New Bloc\",\n        \"icon\": \"assets/logo.png\"\n      },\n      {\n        \"command\": \"extension.new-cubit\",\n        \"title\": \"Bloc: New Cubit\",\n        \"icon\": \"assets/logo.png\"\n      },\n      {\n        \"command\": \"extension.convert-multibloclistener\",\n        \"title\": \"Convert to MultiBlocListener\"\n      },\n      {\n        \"command\": \"extension.convert-multiblocprovider\",\n        \"title\": \"Convert to MultiBlocProvider\"\n      },\n      {\n        \"command\": \"extension.convert-multirepositoryprovider\",\n        \"title\": \"Convert to MultiRepositoryProvider\"\n      },\n      {\n        \"command\": \"extension.wrap-blocbuilder\",\n        \"title\": \"Wrap with BlocBuilder\"\n      },\n      {\n        \"command\": \"extension.wrap-blocselector\",\n        \"title\": \"Wrap with BlocSelector\"\n      },\n      {\n        \"command\": \"extension.wrap-bloclistener\",\n        \"title\": \"Wrap with BlocListener\"\n      },\n      {\n        \"command\": \"extension.wrap-blocconsumer\",\n        \"title\": \"Wrap with BlocConsumer\"\n      },\n      {\n        \"command\": \"extension.wrap-blocprovider\",\n        \"title\": \"Wrap with BlocProvider\"\n      },\n      {\n        \"command\": \"extension.wrap-repositoryprovider\",\n        \"title\": \"Wrap with RepositoryProvider\"\n      }\n    ],\n    \"menus\": {\n      \"explorer/context\": [\n        {\n          \"command\": \"extension.new-bloc\",\n          \"group\": \"blocGroup@1\",\n          \"when\": \"explorerResourceIsFolder && bloc.showContextMenu\"\n        },\n        {\n          \"command\": \"extension.new-cubit\",\n          \"group\": \"blocGroup@1\",\n          \"when\": \"explorerResourceIsFolder && bloc.showContextMenu\"\n        }\n      ],\n      \"commandPalette\": [\n        {\n          \"command\": \"extension.convert-multibloclistener\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.convert-multiblocprovider\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.convert-multirepositoryprovider\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-blocbuilder\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-blocselector\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-bloclistener\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-blocconsumer\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-blocprovider\",\n          \"when\": \"editorLangId == dart\"\n        },\n        {\n          \"command\": \"extension.wrap-repositoryprovider\",\n          \"when\": \"editorLangId == dart\"\n        }\n      ]\n    },\n    \"snippets\": [\n      {\n        \"language\": \"dart\",\n        \"path\": \"./snippets/bloc.json\"\n      },\n      {\n        \"language\": \"dart\",\n        \"path\": \"./snippets/bloc_test.json\"\n      },\n      {\n        \"language\": \"dart\",\n        \"path\": \"./snippets/flutter_bloc.json\"\n      },\n      {\n        \"language\": \"dart\",\n        \"path\": \"./snippets/freezed_bloc.json\"\n      }\n    ]\n  },\n  \"scripts\": {\n    \"vscode:prepublish\": \"webpack --mode production\",\n    \"webpack\": \"webpack --mode development\",\n    \"webpack-dev\": \"webpack --mode development --watch\",\n    \"test-compile\": \"tsc -p ./\",\n    \"compile\": \"tsc -p ./\",\n    \"watch\": \"tsc -watch -p ./\"\n  },\n  \"devDependencies\": {\n    \"@types/change-case\": \"^2.3.1\",\n    \"@types/js-yaml\": \"^3.12.1\",\n    \"@types/lodash\": \"^4.14.121\",\n    \"@types/mkdirp\": \"^0.5.2\",\n    \"@types/mocha\": \"^2.2.42\",\n    \"@types/node\": \"^12.7.8\",\n    \"@types/node-fetch\": \"^2.0.0\",\n    \"@types/rimraf\": \"^2.0.2\",\n    \"@types/semver\": \"^6.0.2\",\n    \"@types/uuid\": \"^8.3.4\",\n    \"@types/vscode\": \"^1.56.0\",\n    \"ts-loader\": \"^9.4.2\",\n    \"tslint\": \"^5.12.1\",\n    \"typescript\": \"^5.0.0\",\n    \"webpack\": \"^5.105.0\",\n    \"webpack-cli\": \"^5.0.1\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.13.5\",\n    \"change-case\": \"^4.1.2\",\n    \"js-yaml\": \"^4.1.1\",\n    \"lodash\": \"^4.17.23\",\n    \"mkdirp\": \"^0.5.1\",\n    \"node-fetch\": \"^2.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"semver\": \"^6.3.1\",\n    \"uuid\": \"^8.3.2\",\n    \"vscode-languageclient\": \"^7.0.0\"\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/snippets/bloc.json",
    "content": "{\n  \"Bloc\": {\n    \"prefix\": \"bloc\",\n    \"body\": [\n      \"class ${1:Subject}Bloc extends Bloc<$1Event, $1State> {\",\n      \"\\t${1:Subject}Bloc() : super(${1:Subject}Initial()) {\",\n      \"\\t\\ton<$1Event>((event, emit) {\",\n      \"\\t\\t\\t${2:// TODO: implement event handler}\",\n      \"\\t\\t});\",\n      \"\\t}\",\n      \"}\"\n    ]\n  },\n  \"Cubit\": {\n    \"prefix\": \"cubit\",\n    \"body\": [\n      \"class ${1:Subject}Cubit extends Cubit<$1State> {\",\n      \"\\t${1:Subject}Cubit() : super(${1:Subject}Initial());\",\n      \"}\"\n    ]\n  },\n  \"BlocObserver\": {\n    \"prefix\": \"blocobserver\",\n    \"body\": [\n      \"import 'package:bloc/bloc.dart';\",\n      \"\",\n      \"class ${1:My}BlocObserver extends BlocObserver {\",\n      \"\\t@override\",\n      \"\\tvoid onEvent(Bloc bloc, Object? event) {\",\n      \"\\t\\tsuper.onEvent(bloc, event);\",\n      \"\\t\\t${2:// TODO: implement onEvent}\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onError(BlocBase bloc, Object error, StackTrace stackTrace) {\",\n      \"\\t\\t${3:// TODO: implement onError}\",\n      \"\\t\\tsuper.onError(bloc, error, stackTrace);\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onChange(BlocBase bloc, Change change) {\",\n      \"\\t\\tsuper.onChange(bloc, change);\",\n      \"\\t\\t${4:// TODO: implement onChange}\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onTransition(Bloc bloc, Transition transition) {\",\n      \"\\t\\tsuper.onTransition(bloc, transition);\",\n      \"\\t\\t${4:// TODO: implement onTransition}\",\n      \"\\t}\",\n      \"}\"\n    ]\n  },\n  \"Bloc State\": {\n    \"prefix\": \"blocstate\",\n    \"body\": [\n      \"class ${1:Subject}${2:Verb}${3:State} extends $1State {\",\n      \"\\tconst $1$2$3($5);\",\n      \"\",\n      \"\\t$4\",\n      \"\",\n      \"\\t@override\",\n      \"\\tList<Object> get props => [$6];\",\n      \"}\"\n    ],\n    \"description\": \"Subject + Verb (action) + State\"\n  },\n  \"Bloc Event\": {\n    \"prefix\": \"blocevent\",\n    \"body\": [\n      \"class ${1:Subject}${2:Noun}${3:Verb} extends $1Event {\",\n      \"\\tconst $1$2$3($5);\",\n      \"\",\n      \"\\t$4\",\n      \"\",\n      \"\\t@override\",\n      \"\\tList<Object> get props => [$6];\",\n      \"}\"\n    ],\n    \"description\": \"Subject + Noun (optional) + Verb (event)\"\n  },\n  \"Import package:bloc\": {\n    \"prefix\": \"importbloc\",\n    \"body\": \"import 'package:bloc/bloc.dart';\",\n    \"description\": \"import package:bloc/bloc.dart;\"\n  },\n  \"Register Event Handler\": {\n    \"prefix\": \"onevent\",\n    \"body\": [\n      \"on<${1:${TM_FILENAME_BASE/(.*)(?=_bloc)(_bloc)/${1:/pascalcase}/g}Event}>((event, emit) {\",\n      \"\\t${2:// TODO: implement event handler}\",\n      \"});\"\n    ],\n    \"description\": \"Register a new EventHandler\"\n  },\n  \"Define Event Handler\": {\n    \"prefix\": \"_onevent\",\n    \"body\": [\n      \"${1|void,Future<void>|} _on${2:Event}(\",\n      \"\\t$2 event,\",\n      \"\\tEmitter<${3:${TM_FILENAME_BASE/(.*)(?=_bloc)(_bloc)/${1:/pascalcase}/g}State}> emit,\",\n      \") ${4:async} {\",\n      \"\\t${5:// TODO: implement event handler}\",\n      \"}\"\n    ],\n    \"description\": \"Define a new EventHandler\"\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/snippets/bloc_test.json",
    "content": "{\n  \"BlocTest\": {\n    \"prefix\": \"bloctest\",\n    \"body\": [\n      \"blocTest<${1:Subject}${2|Bloc,Cubit|}, $1State>(\",\n      \"\\t'emits [${3:MyState}] when ${4:MyEvent} is added.',\",\n      \"\\tbuild: () => ${1:Subject}${2|Bloc,Cubit|}(),\",\n      \"\\tact: (bloc) => bloc.add(${4:MyEvent()}),\",\n      \"\\texpect: () => const <$1State>[${3:MyState()}],\",\n      \");\"\n    ],\n    \"description\": \"create a new blocTest\"\n  },\n  \"Import package:bloc_test\": {\n    \"prefix\": \"importbloctest\",\n    \"body\": \"import 'package:bloc_test/bloc_test.dart';\",\n    \"description\": \"import package:bloc_test/bloc_test.dart;\"\n  },\n  \"MockBloc\": {\n    \"prefix\": \"mockbloc\",\n    \"body\": [\n      \"class Mock${1:Subject}Bloc extends MockBloc<${1}Event, ${1}State> implements ${1}Bloc {}\"\n    ],\n    \"description\": \"create a mock bloc\"\n  },\n  \"_MockBloc\": {\n    \"prefix\": \"_mockbloc\",\n    \"body\": [\n      \"class _Mock${1:Subject}Bloc extends MockBloc<${1}Event, ${1}State> implements ${1}Bloc {}\"\n    ],\n    \"description\": \"create a private mock bloc\"\n  },\n  \"MockCubit\": {\n    \"prefix\": \"mockcubit\",\n    \"body\": [\n      \"class Mock${1:Subject}Cubit extends MockCubit<${1}State> implements ${1}Cubit {}\"\n    ],\n    \"description\": \"create a mock cubit\"\n  },\n  \"_MockCubit\": {\n    \"prefix\": \"_mockcubit\",\n    \"body\": [\n      \"class _Mock${1:Subject}Cubit extends MockCubit<${1}State> implements ${1}Cubit {}\"\n    ],\n    \"description\": \"create a private mock cubit\"\n  },\n  \"Fake\": {\n    \"prefix\": \"fake\",\n    \"body\": [\"class Fake${1:Subject} extends Fake implements ${1} {}\"],\n    \"description\": \"create a fake object\"\n  },\n  \"_Fake\": {\n    \"prefix\": \"_fake\",\n    \"body\": [\"class _Fake${1:Subject} extends Fake implements ${1} {}\"],\n    \"description\": \"create a private fake object\"\n  },\n  \"Mock\": {\n    \"prefix\": \"mock\",\n    \"body\": [\"class Mock${1:Subject} extends Mock implements ${1} {}\"],\n    \"description\": \"create a mock object\"\n  },\n  \"_Mock\": {\n    \"prefix\": \"_mock\",\n    \"body\": [\"class _Mock${1:Subject} extends Mock implements ${1} {}\"],\n    \"description\": \"create a private mock object\"\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/snippets/flutter_bloc.json",
    "content": "{\n  \"BlocProvider\": {\n    \"prefix\": \"blocprovider\",\n    \"body\": [\n      \"BlocProvider(\",\n      \"\\tcreate: (context) => ${1:Subject}${2|Bloc,Cubit|}(),\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ]\n  },\n  \"MultiBlocProvider\": {\n    \"prefix\": \"multiblocprovider\",\n    \"body\": [\n      \"MultiBlocProvider(\",\n      \"\\tproviders: [\",\n      \"\\t\\tBlocProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${1:Subject}${2|Bloc,Cubit|}(),\",\n      \"\\t\\t),\",\n      \"\\t\\tBlocProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${3:Subject}${4|Bloc,Cubit|}(),\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${5:Container()},\",\n      \")\"\n    ]\n  },\n  \"RepositoryProvider\": {\n    \"prefix\": \"repoprovider\",\n    \"body\": [\n      \"RepositoryProvider(\",\n      \"\\tcreate: (context) => ${1:Subject}Repository(),\",\n      \"\\tchild: ${2:Container()},\",\n      \")\"\n    ]\n  },\n  \"MultiRepositoryProvider\": {\n    \"prefix\": \"multirepoprovider\",\n    \"body\": [\n      \"MultiRepositoryProvider(\",\n      \"\\tproviders: [\",\n      \"\\t\\tRepositoryProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${1:Subject}Repository(),\",\n      \"\\t\\t),\",\n      \"\\t\\tRepositoryProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${2:Subject}Repository(),\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ]\n  },\n  \"BlocBuilder\": {\n    \"prefix\": \"blocbuilder\",\n    \"body\": [\n      \"BlocBuilder<${1:Subject}${2|Bloc,Cubit|}, $1State>(\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${3:Container()};\",\n      \"\\t},\",\n      \")\"\n    ]\n  },\n  \"BlocSelector\": {\n    \"prefix\": \"blocselector\",\n    \"body\": [\n      \"BlocSelector<${1:Subject}${2|Bloc,Cubit|}, $1State, ${3:Selected}>(\",\n      \"\\tselector: (state) {\",\n      \"\\t\\treturn ${4:state};\",\n      \"\\t},\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${5:Container()};\",\n      \"\\t},\",\n      \")\"\n    ]\n  },\n  \"BlocListener\": {\n    \"prefix\": \"bloclistener\",\n    \"body\": [\n      \"BlocListener<${1:Subject}${2|Bloc,Cubit|}, $1State>(\",\n      \"\\tlistener: (context, state) {\",\n      \"\\t\\t${3:// TODO: implement listener}\",\n      \"\\t},\",\n      \"\\tchild: ${4:Container()},\",\n      \")\"\n    ]\n  },\n  \"MultiBlocListener\": {\n    \"prefix\": \"multibloclistener\",\n    \"body\": [\n      \"MultiBlocListener(\",\n      \"\\tlisteners: [\",\n      \"\\t\\tBlocListener<${1:Subject}${2|Bloc,Cubit|}, $1State>(\",\n      \"\\t\\t\\tlistener: (context, state) {\",\n      \"\\t\\t\\t\\t${3:// TODO: implement listener}\",\n      \"\\t\\t\\t},\",\n      \"\\t\\t),\",\n      \"\\t\\tBlocListener<${4:Subject}${5|Bloc,Cubit|}, $4State>(\",\n      \"\\t\\t\\tlistener: (context, state) {\",\n      \"\\t\\t\\t\\t${6:// TODO: implement listener}\",\n      \"\\t\\t\\t},\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${7:Container()},\",\n      \")\"\n    ]\n  },\n  \"BlocConsumer\": {\n    \"prefix\": \"blocconsumer\",\n    \"body\": [\n      \"BlocConsumer<${1:Subject}${2|Bloc,Cubit|}, $1State>(\",\n      \"\\tlistener: (context, state) {\",\n      \"\\t\\t${3:// TODO: implement listener}\",\n      \"\\t},\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${4:Container()};\",\n      \"\\t},\",\n      \")\"\n    ]\n  },\n  \"BlocProvider.of()\": {\n    \"prefix\": \"blocof\",\n    \"body\": \"BlocProvider.of<${1:Subject}${2|Bloc,Cubit|}>(context)\"\n  },\n  \"RepositoryProvider.of()\": {\n    \"prefix\": \"repoof\",\n    \"body\": \"RepositoryProvider.of<${1:Subject}Repository>(context)\"\n  },\n  \"context.read()\": {\n    \"prefix\": \"read\",\n    \"body\": \"context.read<${1:Subject}${2|Bloc,Cubit,Repository|}>()\"\n  },\n  \"context.select()\": {\n    \"prefix\": \"select\",\n    \"body\": \"context.select((${1:Subject}${2|Bloc,Cubit|} ${3:bloc}) => $3$4)\"\n  },\n  \"context.watch()\": {\n    \"prefix\": \"watch\",\n    \"body\": \"context.watch<${1:Subject}${2|Bloc,Cubit,Repository|}>()\"\n  },\n  \"Import package:flutter_bloc\": {\n    \"prefix\": \"importflutterbloc\",\n    \"body\": \"import 'package:flutter_bloc/flutter_bloc.dart';\",\n    \"description\": \"import package:flutter_bloc/flutter_bloc.dart;\"\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/snippets/freezed_bloc.json",
    "content": "{\n  \"New freezed state\": {\n    \"prefix\": \"fstate\",\n    \"body\": \"const factory ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}.${1:stateName}($2) = _${1/(.*)/${1:/capitalize}/};\\n$3\"\n  },\n  \"New freezed event\": {\n    \"prefix\": \"fevent\",\n    \"body\": \"const factory ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/g}.${1:eventName}($2) = _${1/(.*)/${1:/capitalize}/};\\n$3\"\n  }  \n}\n"
  },
  {
    "path": "extensions/vscode/src/code-actions/bloc-code-action-provider.ts",
    "content": "import { window, CodeAction, CodeActionProvider, CodeActionKind } from \"vscode\";\nimport { getSelectedText } from \"../utils\";\n\nconst blocListenerRegExp = new RegExp(\"^BlocListener(\\\\<.*\\\\>)*\\\\(.*\\\\)\", \"ms\");\nconst blocProviderRegExp = new RegExp(\n  \"^BlocProvider(\\\\<.*\\\\>)*(\\\\.value)*\\\\(.*\\\\)\",\n  \"ms\"\n);\nconst repositoryProviderRegExp = new RegExp(\n  \"^RepositoryProvider(\\\\<.*\\\\>)*(\\\\.value)*\\\\(.*\\\\)\",\n  \"ms\"\n);\n\nexport class BlocCodeActionProvider implements CodeActionProvider {\n  public provideCodeActions(): CodeAction[] {\n    const editor = window.activeTextEditor;\n    if (!editor) return [];\n\n    const selectedText = editor.document.getText(getSelectedText(editor));\n    if (selectedText === \"\") return [];\n\n    const isBlocListener = blocListenerRegExp.test(selectedText);\n    const isBlocProvider = blocProviderRegExp.test(selectedText);\n    const isRepositoryProvider = repositoryProviderRegExp.test(selectedText);\n\n    return [\n      ...(isBlocListener\n        ? [\n            {\n              command: \"extension.convert-multibloclistener\",\n              title: \"Convert to MultiBlocListener\",\n            },\n          ]\n        : []),\n      ...(isBlocProvider\n        ? [\n            {\n              command: \"extension.convert-multiblocprovider\",\n              title: \"Convert to MultiBlocProvider\",\n            },\n          ]\n        : []),\n      ...(isRepositoryProvider\n        ? [\n            {\n              command: \"extension.convert-multirepositoryprovider\",\n              title: \"Convert to MultiRepositoryProvider\",\n            },\n          ]\n        : []),\n      {\n        command: \"extension.wrap-blocbuilder\",\n        title: \"Wrap with BlocBuilder\",\n      },\n      {\n        command: \"extension.wrap-blocselector\",\n        title: \"Wrap with BlocSelector\",\n      },\n      {\n        command: \"extension.wrap-bloclistener\",\n        title: \"Wrap with BlocListener\",\n      },\n      {\n        command: \"extension.wrap-blocconsumer\",\n        title: \"Wrap with BlocConsumer\",\n      },\n      {\n        command: \"extension.wrap-blocprovider\",\n        title: \"Wrap with BlocProvider\",\n      },\n      {\n        command: \"extension.wrap-repositoryprovider\",\n        title: \"Wrap with RepositoryProvider\",\n      },\n    ].map((c) => {\n      let action = new CodeAction(c.title, CodeActionKind.Refactor);\n      action.command = {\n        command: c.command,\n        title: c.title,\n      };\n      return action;\n    });\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/code-actions/index.ts",
    "content": "export * from \"./bloc-code-action-provider\";\n"
  },
  {
    "path": "extensions/vscode/src/commands/convert-to.command.ts",
    "content": "import { convertTo } from \"../utils\";\n\nconst multiBlocProviderSnippet = (widget: string, child: string) => {\n  return `MultiBlocProvider(\n    providers: [\n        ${widget},\n        BlocProvider(\n            create: (context) => \\${1:Subject}\\${2|Bloc,Cubit|}(),\n        ),\n    ],\n    ${child}\n)`;\n};\n\nconst multiBlocListenerSnippet = (widget: string, child: string) => {\n  return `MultiBlocListener(\n    listeners: [\n        ${widget},\n        BlocListener<\\${1:Subject}\\${2|Bloc,Cubit|}, \\$1State>(\n            listener: (context, state) {\n                \\${4:// TODO: implement listener}\n            },\n        ),\n    ],\n    ${child}\n)`;\n};\n\nconst multiRepositoryProviderSnippet = (widget: string, child: string) => {\n  return `MultiRepositoryProvider(\n    providers: [\n        ${widget},\n        RepositoryProvider(\n            create: (context) => \\${1:Subject}Repository(),\n        ),\n    ],\n    ${child}\n)`;\n};\n\nexport const convertToMultiBlocProvider = async () =>\n  convertTo(multiBlocProviderSnippet);\nexport const convertToMultiBlocListener = async () =>\n  convertTo(multiBlocListenerSnippet);\nexport const convertToMultiRepositoryProvider = async () =>\n  convertTo(multiRepositoryProviderSnippet);\n"
  },
  {
    "path": "extensions/vscode/src/commands/index.ts",
    "content": "export * from \"./convert-to.command\";\nexport * from \"./new-bloc.command\";\nexport * from \"./new-cubit.command\";\nexport * from \"./wrap-with.command\";\n"
  },
  {
    "path": "extensions/vscode/src/commands/new-bloc.command.ts",
    "content": "import * as _ from \"lodash\";\nimport * as changeCase from \"change-case\";\nimport * as mkdirp from \"mkdirp\";\n\nimport {\n  InputBoxOptions,\n  OpenDialogOptions,\n  Uri,\n  window,\n  workspace,\n} from \"vscode\";\nimport { existsSync, lstatSync, writeFile } from \"fs\";\nimport {\n  getBlocEventTemplate,\n  getBlocStateTemplate,\n  getBlocTemplate,\n} from \"../templates\";\nimport { getBlocType, BlocType, TemplateType } from \"../utils\";\n\nexport const newBloc = async (uri: Uri) => {\n  const blocName = await promptForBlocName();\n  if (_.isNil(blocName) || blocName.trim() === \"\") {\n    window.showErrorMessage(\"The bloc name must not be empty\");\n    return;\n  }\n\n  let targetDirectory;\n  if (_.isNil(_.get(uri, \"fsPath\")) || !lstatSync(uri.fsPath).isDirectory()) {\n    targetDirectory = await promptForTargetDirectory();\n    if (_.isNil(targetDirectory)) {\n      window.showErrorMessage(\"Please select a valid directory\");\n      return;\n    }\n  } else {\n    targetDirectory = uri.fsPath;\n  }\n\n  const blocType = await getBlocType(TemplateType.Bloc);\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  try {\n    await generateBlocCode(blocName, targetDirectory, blocType);\n    window.showInformationMessage(\n      `Successfully Generated ${pascalCaseBlocName} Bloc`\n    );\n  } catch (error) {\n    window.showErrorMessage(\n      `Error:\n        ${error instanceof Error ? error.message : JSON.stringify(error)}`\n    );\n  }\n};\n\nfunction promptForBlocName(): Thenable<string | undefined> {\n  const blocNamePromptOptions: InputBoxOptions = {\n    prompt: \"Bloc Name\",\n    placeHolder: \"counter\",\n  };\n  return window.showInputBox(blocNamePromptOptions);\n}\n\nasync function promptForTargetDirectory(): Promise<string | undefined> {\n  const options: OpenDialogOptions = {\n    canSelectMany: false,\n    openLabel: \"Select a folder to create the bloc in\",\n    canSelectFolders: true,\n  };\n\n  return window.showOpenDialog(options).then((uri) => {\n    if (_.isNil(uri) || _.isEmpty(uri)) {\n      return undefined;\n    }\n    return uri[0].fsPath;\n  });\n}\n\nasync function generateBlocCode(\n  blocName: string,\n  targetDirectory: string,\n  type: BlocType\n) {\n  const shouldCreateDirectory = workspace\n    .getConfiguration(\"bloc\")\n    .get<boolean>(\"newBlocTemplate.createDirectory\");\n  const blocDirectoryPath = shouldCreateDirectory\n    ? `${targetDirectory}/bloc`\n    : targetDirectory;\n  if (!existsSync(blocDirectoryPath)) {\n    await createDirectory(blocDirectoryPath);\n  }\n  const useSealedClasses = workspace\n    .getConfiguration(\"bloc\")\n    .get<boolean>(\"newBlocTemplate.useSealedClasses\", true);\n  await Promise.all([\n    createBlocEventTemplate(\n      blocName,\n      blocDirectoryPath,\n      type,\n      useSealedClasses\n    ),\n    createBlocStateTemplate(\n      blocName,\n      blocDirectoryPath,\n      type,\n      useSealedClasses\n    ),\n    createBlocTemplate(blocName, blocDirectoryPath, type),\n  ]);\n}\n\nfunction createDirectory(targetDirectory: string): Promise<void> {\n  return new Promise((resolve, reject) => {\n    mkdirp(targetDirectory, (error) => {\n      if (error) {\n        return reject(error);\n      }\n      resolve();\n    });\n  });\n}\n\nfunction createBlocEventTemplate(\n  blocName: string,\n  targetDirectory: string,\n  type: BlocType,\n  useSealedClasses: boolean\n) {\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const targetPath = `${targetDirectory}/${snakeCaseBlocName}_event.dart`;\n  if (existsSync(targetPath)) {\n    throw Error(`${snakeCaseBlocName}_event.dart already exists`);\n  }\n  return new Promise<void>(async (resolve, reject) => {\n    writeFile(\n      targetPath,\n      getBlocEventTemplate(blocName, type, useSealedClasses),\n      \"utf8\",\n      (error) => {\n        if (error) {\n          reject(error);\n          return;\n        }\n        resolve();\n      }\n    );\n  });\n}\n\nfunction createBlocStateTemplate(\n  blocName: string,\n  targetDirectory: string,\n  type: BlocType,\n  useSealedClasses: boolean\n) {\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const targetPath = `${targetDirectory}/${snakeCaseBlocName}_state.dart`;\n  if (existsSync(targetPath)) {\n    throw Error(`${snakeCaseBlocName}_state.dart already exists`);\n  }\n  return new Promise<void>(async (resolve, reject) => {\n    writeFile(\n      targetPath,\n      getBlocStateTemplate(blocName, type, useSealedClasses),\n      \"utf8\",\n      (error) => {\n        if (error) {\n          reject(error);\n          return;\n        }\n        resolve();\n      }\n    );\n  });\n}\n\nfunction createBlocTemplate(\n  blocName: string,\n  targetDirectory: string,\n  type: BlocType\n) {\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const targetPath = `${targetDirectory}/${snakeCaseBlocName}_bloc.dart`;\n  if (existsSync(targetPath)) {\n    throw Error(`${snakeCaseBlocName}_bloc.dart already exists`);\n  }\n  return new Promise<void>(async (resolve, reject) => {\n    writeFile(targetPath, getBlocTemplate(blocName, type), \"utf8\", (error) => {\n      if (error) {\n        reject(error);\n        return;\n      }\n      resolve();\n    });\n  });\n}\n"
  },
  {
    "path": "extensions/vscode/src/commands/new-cubit.command.ts",
    "content": "import * as _ from \"lodash\";\nimport * as changeCase from \"change-case\";\nimport * as mkdirp from \"mkdirp\";\n\nimport {\n  InputBoxOptions,\n  OpenDialogOptions,\n  Uri,\n  window,\n  workspace,\n} from \"vscode\";\nimport { existsSync, lstatSync, writeFile } from \"fs\";\nimport { getCubitStateTemplate, getCubitTemplate } from \"../templates\";\nimport { getBlocType, BlocType, TemplateType } from \"../utils\";\n\nexport const newCubit = async (uri: Uri) => {\n  const cubitName = await promptForCubitName();\n  if (_.isNil(cubitName) || cubitName.trim() === \"\") {\n    window.showErrorMessage(\"The cubit name must not be empty\");\n    return;\n  }\n\n  let targetDirectory;\n  if (_.isNil(_.get(uri, \"fsPath\")) || !lstatSync(uri.fsPath).isDirectory()) {\n    targetDirectory = await promptForTargetDirectory();\n    if (_.isNil(targetDirectory)) {\n      window.showErrorMessage(\"Please select a valid directory\");\n      return;\n    }\n  } else {\n    targetDirectory = uri.fsPath;\n  }\n\n  const blocType = await getBlocType(TemplateType.Cubit);\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  try {\n    await generateCubitCode(cubitName, targetDirectory, blocType);\n    window.showInformationMessage(\n      `Successfully Generated ${pascalCaseCubitName} Cubit`\n    );\n  } catch (error) {\n    window.showErrorMessage(\n      `Error:\n        ${error instanceof Error ? error.message : JSON.stringify(error)}`\n    );\n  }\n};\n\nfunction promptForCubitName(): Thenable<string | undefined> {\n  const cubitNamePromptOptions: InputBoxOptions = {\n    prompt: \"Cubit Name\",\n    placeHolder: \"counter\",\n  };\n  return window.showInputBox(cubitNamePromptOptions);\n}\n\nasync function promptForTargetDirectory(): Promise<string | undefined> {\n  const options: OpenDialogOptions = {\n    canSelectMany: false,\n    openLabel: \"Select a folder to create the cubit in\",\n    canSelectFolders: true,\n  };\n\n  return window.showOpenDialog(options).then((uri) => {\n    if (_.isNil(uri) || _.isEmpty(uri)) {\n      return undefined;\n    }\n    return uri[0].fsPath;\n  });\n}\n\nasync function generateCubitCode(\n  cubitName: string,\n  targetDirectory: string,\n  type: BlocType\n) {\n  const shouldCreateDirectory = workspace\n    .getConfiguration(\"bloc\")\n    .get<boolean>(\"newCubitTemplate.createDirectory\");\n  const cubitDirectoryPath = shouldCreateDirectory\n    ? `${targetDirectory}/cubit`\n    : targetDirectory;\n  if (!existsSync(cubitDirectoryPath)) {\n    await createDirectory(cubitDirectoryPath);\n  }\n\n  const useSealedClasses = workspace\n    .getConfiguration(\"bloc\")\n    .get<boolean>(\"newCubitTemplate.useSealedClasses\", true);\n  await Promise.all([\n    createCubitStateTemplate(cubitName, cubitDirectoryPath, type, useSealedClasses),\n    createCubitTemplate(cubitName, cubitDirectoryPath, type),\n  ]);\n}\n\nfunction createDirectory(targetDirectory: string): Promise<void> {\n  return new Promise((resolve, reject) => {\n    mkdirp(targetDirectory, (error) => {\n      if (error) {\n        return reject(error);\n      }\n      resolve();\n    });\n  });\n}\n\nfunction createCubitStateTemplate(\n  cubitName: string,\n  targetDirectory: string,\n  type: BlocType,\n  useSealedClasses: boolean\n) {\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  const targetPath = `${targetDirectory}/${snakeCaseCubitName}_state.dart`;\n  if (existsSync(targetPath)) {\n    throw Error(`${snakeCaseCubitName}_state.dart already exists`);\n  }\n  return new Promise<void>(async (resolve, reject) => {\n    writeFile(\n      targetPath,\n      getCubitStateTemplate(cubitName, type, useSealedClasses),\n      \"utf8\",\n      (error) => {\n        if (error) {\n          reject(error);\n          return;\n        }\n        resolve();\n      }\n    );\n  });\n}\n\nfunction createCubitTemplate(\n  cubitName: string,\n  targetDirectory: string,\n  type: BlocType\n) {\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  const targetPath = `${targetDirectory}/${snakeCaseCubitName}_cubit.dart`;\n  if (existsSync(targetPath)) {\n    throw Error(`${snakeCaseCubitName}_cubit.dart already exists`);\n  }\n  return new Promise<void>(async (resolve, reject) => {\n    writeFile(\n      targetPath,\n      getCubitTemplate(cubitName, type),\n      \"utf8\",\n      (error) => {\n        if (error) {\n          reject(error);\n          return;\n        }\n        resolve();\n      }\n    );\n  });\n}\n"
  },
  {
    "path": "extensions/vscode/src/commands/wrap-with.command.ts",
    "content": "import { wrapWith } from \"../utils\";\n\nconst blocBuilderSnippet = (widget: string) => {\n  return `BlocBuilder<\\${1:Subject}\\${2|Bloc,Cubit|}, $1State>(\n  builder: (context, state) {\n    return ${widget};\n  },\n)`;\n};\n\nconst blocSelectorSnippet = (widget: string) => {\n  return `BlocSelector<\\${1:Subject}\\${2|Bloc,Cubit|}, $1State, \\${3:SelectedState}>(\n  selector: (state) {\n    return \\${4:state};\n  },\n  builder: (context, state) {\n    return ${widget};\n  },\n)`;\n};\n\nconst blocListenerSnippet = (widget: string) => {\n  return `BlocListener<\\${1:Subject}\\${2|Bloc,Cubit|}, $1State>(\n  listener: (context, state) {\n    \\${3:// TODO: implement listener}\n  },\n  child: ${widget},\n)`;\n};\n\nconst blocProviderSnippet = (widget: string) => {\n  return `BlocProvider(\n  create: (context) => \\${1:Subject}\\${2|Bloc,Cubit|}(),\n  child: ${widget},\n)`;\n};\n\nconst blocConsumerSnippet = (widget: string) => {\n  return `BlocConsumer<\\${1:Subject}\\${2|Bloc,Cubit|}, $1State>(\n  listener: (context, state) {\n    \\${3:// TODO: implement listener}\n  },\n  builder: (context, state) {\n    return ${widget};\n  },\n)`;\n};\n\nconst repositoryProviderSnippet = (widget: string) => {\n  return `RepositoryProvider(\n  create: (context) => \\${1:Subject}Repository(),\n    child: ${widget},\n)`;\n};\n\nexport const wrapWithBlocBuilder = async () => wrapWith(blocBuilderSnippet);\nexport const wrapWithBlocSelector = async () => wrapWith(blocSelectorSnippet);\nexport const wrapWithBlocListener = async () => wrapWith(blocListenerSnippet);\nexport const wrapWithBlocConsumer = async () => wrapWith(blocConsumerSnippet);\nexport const wrapWithBlocProvider = async () => wrapWith(blocProviderSnippet);\nexport const wrapWithRepositoryProvider = async () =>\n  wrapWith(repositoryProviderSnippet);\n"
  },
  {
    "path": "extensions/vscode/src/extension.ts",
    "content": "import * as _ from \"lodash\";\n\nimport {\n  commands,\n  ExtensionContext,\n  languages,\n  window,\n  workspace,\n} from \"vscode\";\nimport { BlocCodeActionProvider } from \"./code-actions\";\nimport {\n  convertToMultiBlocListener,\n  convertToMultiBlocProvider,\n  convertToMultiRepositoryProvider,\n  newBloc,\n  newCubit,\n  wrapWithBlocBuilder,\n  wrapWithBlocConsumer,\n  wrapWithBlocListener,\n  wrapWithBlocProvider,\n  wrapWithBlocSelector,\n  wrapWithRepositoryProvider,\n} from \"./commands\";\nimport { analyzeDependencies, setShowContextMenu } from \"./utils\";\nimport { client, DART_FILE, tryStartLanguageServer } from \"./language-server\";\n\nexport function activate(context: ExtensionContext) {\n  if (\n    workspace.getConfiguration(\"bloc\").get<boolean>(\"languageServer.enabled\")\n  ) {\n    tryStartLanguageServer(context);\n  }\n\n  if (workspace.getConfiguration(\"bloc\").get<boolean>(\"checkForUpdates\")) {\n    analyzeDependencies();\n  }\n\n  setShowContextMenu();\n\n  context.subscriptions.push(\n    window.onDidChangeActiveTextEditor((_) => setShowContextMenu()),\n    workspace.onDidChangeWorkspaceFolders((_) => setShowContextMenu()),\n    workspace.onDidChangeTextDocument(async function (event) {\n      if (event.document.uri.fsPath.endsWith(\"pubspec.yaml\")) {\n        setShowContextMenu(event.document.uri);\n      }\n    }),\n    commands.registerCommand(\"extension.new-bloc\", newBloc),\n    commands.registerCommand(\"extension.new-cubit\", newCubit),\n    commands.registerCommand(\n      \"extension.convert-multibloclistener\",\n      convertToMultiBlocListener\n    ),\n    commands.registerCommand(\n      \"extension.convert-multiblocprovider\",\n      convertToMultiBlocProvider\n    ),\n    commands.registerCommand(\n      \"extension.convert-multirepositoryprovider\",\n      convertToMultiRepositoryProvider\n    ),\n    commands.registerCommand(\"extension.wrap-blocbuilder\", wrapWithBlocBuilder),\n    commands.registerCommand(\n      \"extension.wrap-blocselector\",\n      wrapWithBlocSelector\n    ),\n    commands.registerCommand(\n      \"extension.wrap-bloclistener\",\n      wrapWithBlocListener\n    ),\n    commands.registerCommand(\n      \"extension.wrap-blocconsumer\",\n      wrapWithBlocConsumer\n    ),\n    commands.registerCommand(\n      \"extension.wrap-blocprovider\",\n      wrapWithBlocProvider\n    ),\n    commands.registerCommand(\n      \"extension.wrap-repositoryprovider\",\n      wrapWithRepositoryProvider\n    ),\n    languages.registerCodeActionsProvider(\n      DART_FILE,\n      new BlocCodeActionProvider()\n    )\n  );\n}\n\nexport function deactivate(): Thenable<void> | undefined {\n  if (!client) return undefined;\n  return client.stop();\n}\n"
  },
  {
    "path": "extensions/vscode/src/language-server/index.ts",
    "content": "export * from \"./language-server\";\nexport * from \"./selectors\";\n"
  },
  {
    "path": "extensions/vscode/src/language-server/language-server.ts",
    "content": "import { ExtensionContext, ProgressLocation, window } from \"vscode\";\nimport {\n  LanguageClient,\n  LanguageClientOptions,\n  RevealOutputChannelOn,\n  ServerOptions,\n  TransportKind,\n} from \"vscode-languageclient/node\";\nimport { getBlocToolsExecutable, installBlocTools } from \"../utils\";\n\nlet client: LanguageClient;\n\nconst DART_FILE = { language: \"dart\", scheme: \"file\" };\nconst ANALYSIS_OPTIONS_FILE = {\n  pattern: \"**/analysis_options.yaml\",\n  scheme: \"file\",\n};\n\nasync function startLanguageServer(executable: string) {\n  const serverOptions: ServerOptions = {\n    command: `\"${executable}\"`,\n    args: [\"language-server\"],\n    options: {\n      env: process.env,\n      shell: true,\n    },\n    transport: TransportKind.stdio,\n  };\n\n  const clientOptions: LanguageClientOptions = {\n    revealOutputChannelOn: RevealOutputChannelOn.Info,\n    documentSelector: [DART_FILE, ANALYSIS_OPTIONS_FILE],\n  };\n\n  client = new LanguageClient(\n    \"blocAnalysisLSP\",\n    \"Bloc Analysis Server\",\n    serverOptions,\n    clientOptions\n  );\n\n  return client.start();\n}\n\nasync function startLanguageServerWithProgress(executable: string) {\n  window.withProgress(\n    {\n      location: ProgressLocation.Window,\n      title: \"Bloc Analysis Server\",\n    },\n    async () => {\n      try {\n        await startLanguageServer(executable);\n        window.setStatusBarMessage(\"✓ Bloc Analysis Server\", 3000);\n      } catch (err) {\n        window.showErrorMessage(`${err}`);\n      }\n    }\n  );\n}\n\nasync function tryStartLanguageServer(\n  context: ExtensionContext\n): Promise<void> {\n  const executable = await getBlocToolsExecutable(context);\n  if (executable) return startLanguageServerWithProgress(executable);\n\n  await window.withProgress(\n    {\n      location: ProgressLocation.Notification,\n      title: \"Installing the Bloc Language Server\",\n    },\n    async () => {\n      try {\n        await installBlocTools(context);\n        window.setStatusBarMessage(\"Bloc Language Server installed\", 3000);\n      } catch (err) {\n        window.showErrorMessage(`${err}`);\n      }\n    }\n  );\n\n  const installedExecutable = await getBlocToolsExecutable(context);\n  if (!installedExecutable) {\n    window.showErrorMessage(\"Failed to install the Bloc Language Server\");\n    return;\n  }\n\n  return startLanguageServerWithProgress(installedExecutable);\n}\n\nexport { client, tryStartLanguageServer };\n"
  },
  {
    "path": "extensions/vscode/src/language-server/selectors.ts",
    "content": "const ANALYSIS_OPTIONS_FILE = {\n  pattern: \"**/analysis_options.yaml\",\n  scheme: \"file\",\n};\nconst DART_FILE = { language: \"dart\", scheme: \"file\" };\n\nexport { ANALYSIS_OPTIONS_FILE, DART_FILE };\n"
  },
  {
    "path": "extensions/vscode/src/templates/bloc-event.template.ts",
    "content": "import * as changeCase from \"change-case\";\nimport { BlocType } from \"../utils\";\n\nexport function getBlocEventTemplate(\n  blocName: string,\n  type: BlocType,\n  useSealedClasses: boolean\n): string {\n  switch (type) {\n    case BlocType.Freezed:\n      return getFreezedBlocEvent(blocName);\n    case BlocType.Equatable:\n      return getEquatableBlocEventTemplate(blocName, useSealedClasses);\n    default:\n      return getDefaultBlocEventTemplate(blocName, useSealedClasses);\n  }\n}\n\nfunction getEquatableBlocEventTemplate(\n  blocName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n${classPrefix} class ${pascalCaseBlocName}Event extends Equatable {\n  const ${pascalCaseBlocName}Event();\n\n  @override\n  List<Object> get props => [];\n}\n`;\n}\n\nfunction getDefaultBlocEventTemplate(\n  blocName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n@immutable\n${classPrefix} class ${pascalCaseBlocName}Event {}\n`;\n}\n\nfunction getFreezedBlocEvent(blocName: string): string {\n  const pascalCaseBlocName = changeCase.pascalCase(blocName) + \"Event\";\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n@freezed\nclass ${pascalCaseBlocName} with _\\$${pascalCaseBlocName} {\n  const factory ${pascalCaseBlocName}.started() = _Started;\n}`;\n}\n"
  },
  {
    "path": "extensions/vscode/src/templates/bloc-state.template.ts",
    "content": "import * as changeCase from \"change-case\";\nimport { BlocType } from \"../utils\";\n\nexport function getBlocStateTemplate(\n  blocName: string,\n  type: BlocType,\n  useSealedClasses: boolean\n): string {\n  switch (type) {\n    case BlocType.Freezed:\n      return getFreezedBlocStateTemplate(blocName);\n    case BlocType.Equatable:\n      return getEquatableBlocStateTemplate(blocName, useSealedClasses);\n    default:\n      return getDefaultBlocStateTemplate(blocName, useSealedClasses);\n  }\n}\n\nfunction getEquatableBlocStateTemplate(\n  blocName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const subclassPrefix = useSealedClasses ? \"final \" : \"\";\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n${classPrefix} class ${pascalCaseBlocName}State extends Equatable {\n  const ${pascalCaseBlocName}State();\n  \n  @override\n  List<Object> get props => [];\n}\n\n${subclassPrefix}class ${pascalCaseBlocName}Initial extends ${pascalCaseBlocName}State {}\n`;\n}\n\nfunction getDefaultBlocStateTemplate(\n  blocName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const subclassPrefix = useSealedClasses ? \"final \" : \"\";\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n@immutable\n${classPrefix} class ${pascalCaseBlocName}State {}\n\n${subclassPrefix}class ${pascalCaseBlocName}Initial extends ${pascalCaseBlocName}State {}\n`;\n}\n\nfunction getFreezedBlocStateTemplate(blocName: string): string {\n  const pascalCaseBlocName = changeCase.pascalCase(blocName) + \"State\";\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  return `part of '${snakeCaseBlocName}_bloc.dart';\n\n@freezed\nclass ${pascalCaseBlocName} with _\\$${pascalCaseBlocName} {\n  const factory ${pascalCaseBlocName}.initial() = _Initial;\n}\n`;\n}\n"
  },
  {
    "path": "extensions/vscode/src/templates/bloc.template.ts",
    "content": "import * as changeCase from \"change-case\";\nimport { BlocType } from \"../utils\";\n\nexport function getBlocTemplate(blocName: string, type: BlocType): string {\n  switch (type) {\n    case BlocType.Freezed:\n      return getFreezedBlocTemplate(blocName);\n    case BlocType.Equatable:\n      return getEquatableBlocTemplate(blocName);\n    default:\n      return getDefaultBlocTemplate(blocName);\n  }\n}\n\nfunction getEquatableBlocTemplate(blocName: string) {\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const blocState = `${pascalCaseBlocName}State`;\n  const blocEvent = `${pascalCaseBlocName}Event`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '${snakeCaseBlocName}_event.dart';\npart '${snakeCaseBlocName}_state.dart';\n\nclass ${pascalCaseBlocName}Bloc extends Bloc<${blocEvent}, ${blocState}> {\n  ${pascalCaseBlocName}Bloc() : super(${pascalCaseBlocName}Initial()) {\n    on<${blocEvent}>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n`;\n}\n\nfunction getDefaultBlocTemplate(blocName: string) {\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const blocState = `${pascalCaseBlocName}State`;\n  const blocEvent = `${pascalCaseBlocName}Event`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart '${snakeCaseBlocName}_event.dart';\npart '${snakeCaseBlocName}_state.dart';\n\nclass ${pascalCaseBlocName}Bloc extends Bloc<${blocEvent}, ${blocState}> {\n  ${pascalCaseBlocName}Bloc() : super(${pascalCaseBlocName}Initial()) {\n    on<${blocEvent}>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n`;\n}\n\nexport function getFreezedBlocTemplate(blocName: string) {\n  const pascalCaseBlocName = changeCase.pascalCase(blocName);\n  const snakeCaseBlocName = changeCase.snakeCase(blocName);\n  const blocState = `${pascalCaseBlocName}State`;\n  const blocEvent = `${pascalCaseBlocName}Event`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '${snakeCaseBlocName}_event.dart';\npart '${snakeCaseBlocName}_state.dart';\npart '${snakeCaseBlocName}_bloc.freezed.dart';\n\nclass ${pascalCaseBlocName}Bloc extends Bloc<${blocEvent}, ${blocState}> {\n  ${pascalCaseBlocName}Bloc() : super(_Initial()) {\n    on<${blocEvent}>((event, emit) {\n      // TODO: implement event handler\n    });\n  }\n}\n`;\n}\n"
  },
  {
    "path": "extensions/vscode/src/templates/cubit-state.template.ts",
    "content": "import * as changeCase from \"change-case\";\nimport { BlocType } from \"../utils\";\n\nexport function getCubitStateTemplate(\n  cubitName: string,\n  type: BlocType,\n  useSealedClasses: boolean\n): string {\n  switch (type) {\n    case BlocType.Freezed:\n      return getFreezedCubitStateTemplate(cubitName);\n    case BlocType.Equatable:\n      return getEquatableCubitStateTemplate(cubitName, useSealedClasses);\n    default:\n      return getDefaultCubitStateTemplate(cubitName, useSealedClasses);\n  }\n}\n\nfunction getEquatableCubitStateTemplate(\n  cubitName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const subclassPrefix = useSealedClasses ? \"final \" : \"\";\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  return `part of '${snakeCaseCubitName}_cubit.dart';\n\n${classPrefix} class ${pascalCaseCubitName}State extends Equatable {\n  const ${pascalCaseCubitName}State();\n\n  @override\n  List<Object> get props => [];\n}\n\n${subclassPrefix}class ${pascalCaseCubitName}Initial extends ${pascalCaseCubitName}State {}\n`;\n}\n\nfunction getDefaultCubitStateTemplate(\n  cubitName: string,\n  useSealedClasses: boolean\n): string {\n  const classPrefix = useSealedClasses ? \"sealed\" : \"abstract\";\n  const subclassPrefix = useSealedClasses ? \"final \" : \"\";\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  return `part of '${snakeCaseCubitName}_cubit.dart';\n\n@immutable\n${classPrefix} class ${pascalCaseCubitName}State {}\n\n${subclassPrefix}class ${pascalCaseCubitName}Initial extends ${pascalCaseCubitName}State {}\n`;\n}\n\nfunction getFreezedCubitStateTemplate(cubitName: string): string {\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  return `part of '${snakeCaseCubitName}_cubit.dart';\n\n@freezed\nclass ${pascalCaseCubitName}State with _\\$${pascalCaseCubitName}State {\n  const factory ${pascalCaseCubitName}State.initial() = _Initial;\n}\n`;\n}\n"
  },
  {
    "path": "extensions/vscode/src/templates/cubit.template.ts",
    "content": "import * as changeCase from \"change-case\";\nimport { BlocType } from \"../utils\";\n\nexport function getCubitTemplate(cubitName: string, type: BlocType): string {\n  switch (type) {\n    case BlocType.Freezed:\n      return getFreezedCubitTemplate(cubitName);\n    case BlocType.Equatable:\n      return getEquatableCubitTemplate(cubitName);\n    default:\n      return getDefaultCubitTemplate(cubitName);\n  }\n}\n\nfunction getEquatableCubitTemplate(cubitName: string) {\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  const cubitState = `${pascalCaseCubitName}State`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\n\npart '${snakeCaseCubitName}_state.dart';\n\nclass ${pascalCaseCubitName}Cubit extends Cubit<${cubitState}> {\n  ${pascalCaseCubitName}Cubit() : super(${pascalCaseCubitName}Initial());\n}\n`;\n}\n\nfunction getDefaultCubitTemplate(cubitName: string) {\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  const cubitState = `${pascalCaseCubitName}State`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart '${snakeCaseCubitName}_state.dart';\n\nclass ${pascalCaseCubitName}Cubit extends Cubit<${cubitState}> {\n  ${pascalCaseCubitName}Cubit() : super(${pascalCaseCubitName}Initial());\n}\n`;\n}\n\nexport function getFreezedCubitTemplate(cubitName: string) {\n  const pascalCaseCubitName = changeCase.pascalCase(cubitName);\n  const snakeCaseCubitName = changeCase.snakeCase(cubitName);\n  const cubitState = `${pascalCaseCubitName}State`;\n  return `import 'package:bloc/bloc.dart';\nimport 'package:freezed_annotation/freezed_annotation.dart';\n\npart '${snakeCaseCubitName}_state.dart';\npart '${snakeCaseCubitName}_cubit.freezed.dart';\n\nclass ${pascalCaseCubitName}Cubit extends Cubit<${cubitState}> {\n  ${pascalCaseCubitName}Cubit() : super(${pascalCaseCubitName}State.initial());\n}\n`;\n}\n"
  },
  {
    "path": "extensions/vscode/src/templates/index.ts",
    "content": "export * from \"./bloc-event.template\";\nexport * from \"./bloc-state.template\";\nexport * from \"./bloc.template\";\nexport * from \"./cubit-state.template\";\nexport * from \"./cubit.template\";\n"
  },
  {
    "path": "extensions/vscode/src/utils/analyze-dependencies.ts",
    "content": "import * as _ from \"lodash\";\nimport * as semver from \"semver\";\nimport { window, env, Uri } from \"vscode\";\nimport { getLatestPackageVersion, getPubspecLock } from \".\";\nimport { updatePubspecDependency } from \"./update-pubspec-dependency\";\n\nconst DEFAULT_VERSION_VALUE = \"0.0.0\";\n\ninterface Dependency {\n  name: string;\n  version: string;\n  actions: Action[];\n}\n\ninterface Action {\n  name: string;\n  callback: Function;\n}\n\nconst openBlocMigrationGuide = {\n  name: \"Open Migration Guide\",\n  callback: () => {\n    env.openExternal(Uri.parse(\"https://bloclibrary.dev/migration\"));\n  },\n};\nconst openEquatableMigrationGuide = {\n  name: \"Open Migration Guide\",\n  callback: () => {\n    env.openExternal(\n      Uri.parse(\n        \"https://github.com/felangel/equatable/blob/master/doc/migration_guides/migration-0.6.0.md\"\n      )\n    );\n  },\n};\n\nconst deps = [\n  { name: \"angular_bloc\", actions: [openBlocMigrationGuide] },\n  { name: \"bloc\", actions: [openBlocMigrationGuide] },\n  { name: \"bloc_concurrency\", actions: [openBlocMigrationGuide] },\n  { name: \"equatable\", actions: [openEquatableMigrationGuide] },\n  { name: \"flutter_bloc\", actions: [openBlocMigrationGuide] },\n  { name: \"hydrated_bloc\", actions: [openBlocMigrationGuide] },\n  { name: \"replay_bloc\", actions: [openBlocMigrationGuide] },\n  { name: \"sealed_flutter_bloc\", actions: [openBlocMigrationGuide] },\n];\n\nconst devDeps = [{ name: \"bloc_test\", actions: [openBlocMigrationGuide] }];\n\nexport async function analyzeDependencies() {\n  const dependencies = await getDependencies(deps);\n  const devDependencies = await getDependencies(devDeps);\n  const pubspecLock = await getPubspecLock();\n\n  const pubspecLockDependencies = _.get(pubspecLock, \"packages\", {});\n\n  checkForUpgrades(dependencies, pubspecLockDependencies);\n  checkForUpgrades(devDependencies, pubspecLockDependencies);\n}\n\nfunction checkForUpgrades(\n  dependencies: Dependency[],\n  pubspecDependencies: object[]\n) {\n  for (let i = 0; i < dependencies.length; i++) {\n    const dependency = dependencies[i];\n    if (_.isEmpty(dependency.version)) continue;\n    if (_.has(pubspecDependencies, dependency.name)) {\n      const currentDependencyVersion = _.get(\n        pubspecDependencies,\n        dependency.name,\n      ).version;\n      \n      const hasLatestVersion = currentDependencyVersion === dependency.version;\n      if (hasLatestVersion) continue;\n      \n      showUpdateMessage(dependency, currentDependencyVersion);\n    }\n  }\n}\n\nfunction showUpdateMessage(dependency : Dependency, dependencyVersion : string) {\n  const minVersion = _.get(\n    semver.minVersion(dependencyVersion),\n    \"version\",\n    DEFAULT_VERSION_VALUE\n  );\n  \n  if (!semver.satisfies(minVersion, dependency.version)) {\n    window\n      .showWarningMessage(\n        `This workspace contains an outdated version of ${dependency.name}. Please update to ${dependency.version}.`,\n        ...dependency.actions.map((action) => action.name).concat(\"Update\")\n      )\n      .then((invokedAction) => {\n        if (invokedAction === \"Update\") {\n          return updatePubspecDependency({\n            name: dependency.name,\n            latestVersion: `^${dependency.version}`,\n            currentVersion: dependencyVersion,\n          });\n        }\n        const action = dependency.actions.find(\n          (action) => action.name === invokedAction\n        );\n        if (!_.isNil(action)) {\n          action.callback();\n        }\n      });\n  }\n}\n\nasync function getDependencies(\n  dependencies: { name: string; actions: Action[] }[]\n): Promise<Dependency[]> {\n  const futures: Promise<Dependency>[] = dependencies.map(\n    async (dependency) => {\n      return {\n        name: dependency.name,\n        actions: dependency.actions,\n        version: await getLatestPackageVersion(dependency.name),\n      };\n    }\n  );\n\n  return Promise.all(futures);\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/convert-to.ts",
    "content": "import { window, commands, SnippetString } from \"vscode\";\nimport { getSelectedText } from \"../utils\";\n\nconst childRegExp = new RegExp(\"[^S\\r\\n]*child: .*,s*\", \"ms\");\n\nexport const convertTo = async (\n  snippet: (widget: string, child: string) => string\n) => {\n  let editor = window.activeTextEditor;\n  if (!editor) return;\n  const selection = getSelectedText(editor);\n  const rawWidget = editor.document.getText(selection).replace(\"$\", \"//$\");\n  const match = rawWidget.match(childRegExp);\n  if (!match || !match.length) return;\n  const child = match[0];\n  if (!child) return;\n  const widget = rawWidget.replace(childRegExp, \"\");\n  editor.insertSnippet(new SnippetString(snippet(widget, child)), selection);\n  await commands.executeCommand(\"editor.action.formatDocument\");\n};\n"
  },
  {
    "path": "extensions/vscode/src/utils/downloader.ts",
    "content": "import axios, { AxiosRequestConfig } from \"axios\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as rimraf from \"rimraf\";\nimport { pipeline, Readable } from \"stream\";\nimport { promisify } from \"util\";\nimport { v4 as uuid } from \"uuid\";\nimport { ExtensionContext, Uri } from \"vscode\";\nimport { retry } from \"./retry\";\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst DEFAULT_RETRY_COUNT = 5;\nconst DEFAULT_RETRY_DELAY_MS = 100;\n\nconst pipelineAsync = promisify(pipeline);\nconst rmAsync = promisify(rimraf);\n\nexport async function downloadFile(\n  url: Uri,\n  filename: string,\n  context: ExtensionContext\n): Promise<Uri> {\n  if (url.scheme !== `http` && url.scheme !== `https`) {\n    throw new Error(\n      `Unsupported URI scheme in url. Supported schemes are http and https.`\n    );\n  }\n\n  const downloadsStoragePath: string = downloadsPath(context);\n  const tempFileDownloadPath: string = path.join(downloadsStoragePath, uuid());\n  const fileDownloadPath: string = path.join(downloadsStoragePath, filename);\n  await fs.promises.mkdir(downloadsStoragePath, { recursive: true });\n\n  let progress = 0;\n  let progressTimerId: any;\n  try {\n    progressTimerId = setInterval(() => {\n      if (progress > 100) clearInterval(progressTimerId);\n    }, 1500);\n\n    const downloadStream: Readable = await get(\n      url.toString(),\n      DEFAULT_TIMEOUT_MS,\n      DEFAULT_RETRY_COUNT,\n      DEFAULT_RETRY_DELAY_MS\n    );\n\n    const writeStream = fs.createWriteStream(tempFileDownloadPath);\n    await Promise.all([\n      pipelineAsync([downloadStream, writeStream]),\n      new Promise((resolve) => writeStream.on(`close`, resolve)),\n    ]);\n  } catch (error) {\n    if (progressTimerId != null) clearInterval(progressTimerId);\n    throw error;\n  }\n\n  await fs.promises.chmod(tempFileDownloadPath, 0o744); // make executable\n\n  await rmAsync(fileDownloadPath);\n\n  const renameDownloadedFile = async (): Promise<Uri> => {\n    await fs.promises.rename(tempFileDownloadPath, fileDownloadPath);\n    return Uri.file(fileDownloadPath);\n  };\n\n  return retry(\n    renameDownloadedFile,\n    renameDownloadedFile.name,\n    DEFAULT_RETRY_COUNT,\n    DEFAULT_RETRY_DELAY_MS\n  );\n}\n\nexport async function tryGetDownload(\n  filename: string,\n  context: ExtensionContext\n): Promise<Uri | undefined> {\n  try {\n    return await getDownload(filename, context);\n  } catch {\n    return undefined;\n  }\n}\n\nfunction downloadsPath(context: ExtensionContext): string {\n  return path.join(context.globalStorageUri.fsPath, `downloads`);\n}\n\nasync function listDownloads(context: ExtensionContext): Promise<Uri[]> {\n  const downloadsStoragePath = downloadsPath(context);\n  try {\n    const filePaths: string[] = await fs.promises.readdir(downloadsStoragePath);\n    return filePaths.map((filePath) =>\n      Uri.file(path.join(downloadsStoragePath, filePath))\n    );\n  } catch (error) {\n    return [];\n  }\n}\n\nasync function getDownload(\n  filename: string,\n  context: ExtensionContext\n): Promise<Uri> {\n  const filePaths = await listDownloads(context);\n  const matchingUris = filePaths.filter(\n    (uri) => uri.path.split(`/`).pop() === filename.replace(`/`, ``)\n  );\n  switch (matchingUris.length) {\n    case 1:\n      return matchingUris[0];\n    case 0:\n      throw new Error(\n        `Download not found: ${path.join(downloadsPath(context), filename)}`\n      );\n    default:\n      throw new Error(\n        `Multiple downloads found: ${filePaths.map((uri) => uri.toString())}`\n      );\n  }\n}\n\nasync function get(\n  url: string,\n  timeoutInMs: number,\n  retries: number,\n  retryDelayInMs: number\n): Promise<Readable> {\n  const body = () => getAsStream(url, timeoutInMs);\n  const onError = (error: Error) => {\n    const statusCode = (error as any)?.response?.status;\n    if (statusCode != null && 400 <= statusCode && statusCode < 500) {\n      throw error;\n    }\n  };\n  return retry(body, \"getAsStream\", retries, retryDelayInMs, onError);\n}\n\nasync function getAsStream(\n  url: string,\n  timeoutInMs: number\n): Promise<Readable> {\n  const options: AxiosRequestConfig = {\n    timeout: timeoutInMs,\n    responseType: `stream`,\n    proxy: false, // Disabling axios proxy in order to use VSCode proxy settings.\n  };\n  const response = await axios.get(url, options);\n  if (response === undefined) {\n    throw new Error(\n      `Undefined response received when downloading from '${url}'`\n    );\n  }\n  return response.data;\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/exec.ts",
    "content": "import * as cp from \"child_process\";\n\nexport interface ExecOptions {\n  cwd?: string | undefined;\n}\n\nexport const exec = (cmd: string, options?: ExecOptions) =>\n  new Promise<string>((resolve, reject) => {\n    cp.exec(cmd, { cwd: options?.cwd }, (err, output) => {\n      if (err) {\n        return reject(err);\n      }\n      return resolve(output);\n    });\n  });\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-bloc-tools-executable.ts",
    "content": "import { BLOC_TOOLS_VERSION, tryGetDownload } from \".\";\nimport { ExtensionContext } from \"vscode\";\n\nexport const getBlocToolsExecutable = async (\n  context: ExtensionContext\n): Promise<string | null> => {\n  try {\n    const executable = await tryGetDownload(\n      `bloc_${BLOC_TOOLS_VERSION}`,\n      context\n    );\n    if (!executable) return null;\n    return executable.fsPath;\n  } catch (_) {\n    return null;\n  }\n};\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-bloc-type.ts",
    "content": "import { hasDependency } from \"./has-dependency\";\nimport {\n  TemplateType,\n  getTemplateSetting,\n  TemplateSetting,\n} from \"./get-template-setting\";\n\nconst equatable = \"equatable\";\nconst freezed_annotation = \"freezed_annotation\";\n\nexport const enum BlocType {\n  Simple,\n  Equatable,\n  Freezed,\n}\n\nexport async function getBlocType(type: TemplateType): Promise<BlocType> {\n  const setting = getTemplateSetting(type);\n  switch (setting) {\n    case TemplateSetting.Freezed:\n      return BlocType.Freezed;\n    case TemplateSetting.Equatable:\n      return BlocType.Equatable;\n    case TemplateSetting.Simple:\n      return BlocType.Simple;\n    case TemplateSetting.Auto:\n    default:\n      return getDefaultDependency();\n  }\n}\n\nasync function getDefaultDependency(): Promise<BlocType> {\n  if (await hasDependency(freezed_annotation)) {\n    return BlocType.Freezed;\n  } else if (await hasDependency(equatable)) {\n    return BlocType.Equatable;\n  } else {\n    return BlocType.Simple;\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-dart-version.ts",
    "content": "import { exec } from \"./exec\";\nimport * as semver from \"semver\";\n\nexport const getDartVersion = async (): Promise<semver.SemVer | null> => {\n  try {\n    const result = await exec(\"dart --version\");\n    // Dart SDK version: 3.7.2 (stable) (Tue Mar 11 04:27:50 2025 -0700) on \"macos_arm64\"\n    const output = result.trim();\n\n    // Parse \"major.minor.patch\"\n    const regexp = new RegExp(/\\d+\\.\\d+\\.\\d+/);\n    const versionString = output.match(regexp)?.[0] ?? null;\n    return semver.parse(versionString);\n  } catch (_) {\n    return null;\n  }\n};\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-latest-package-version.ts",
    "content": "import * as _ from \"lodash\";\nimport fetch from \"node-fetch\";\n\nexport async function getLatestPackageVersion(name: string): Promise<string> {\n  try {\n    const url = `https://pub.dev/api/packages/${name}`;\n    const response = await fetch(url);\n    const body = await response.json();\n    return _.get(body, \"latest.version\", \"\");\n  } catch (_) {\n    return \"\";\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-pubspec-path.ts",
    "content": "import { workspace } from \"vscode\";\nimport * as path from \"path\";\n\nconst PUBSPEC_FILE_NAME = \"pubspec.yaml\";\nconst PUBSPEC_LOCK_FILE_NAME = \"pubspec.lock\";\n\nexport function getPubspecPath(): string | undefined {\n  return getWorkspacePath(PUBSPEC_FILE_NAME);\n}\n\nexport function getPubspecLockPath(): string | undefined {\n  return getWorkspacePath(PUBSPEC_LOCK_FILE_NAME);\n}\n\nfunction getWorkspacePath(fileName: string): string | undefined {\n  if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {\n    return path.join(\n      `${workspace.workspaceFolders[0].uri.path}`,\n      fileName\n    );\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-pubspec.ts",
    "content": "import * as yaml from \"js-yaml\";\nimport { getPubspecPath, getPubspecLockPath } from \"./get-pubspec-path\";\nimport { workspace, Uri } from \"vscode\";\n\nexport async function getPubspec(): Promise<Record<string, any> | undefined> {\n  const pubspecPath = getPubspecPath();\n  return getYAMLFileContent(pubspecPath);\n}\n\nexport async function getPubspecLock(): Promise<Record<string, any> | undefined> {\n  const pubspecLockPath = getPubspecLockPath();\n  return getYAMLFileContent(pubspecLockPath);\n}\n\nasync function getYAMLFileContent(path: string | undefined): Promise<Record<string, any> | undefined> {\n  if (path) {\n    try {\n      let content = await workspace.fs.readFile(Uri.file(path));\n      return yaml.load(content.toString());\n    } catch (_) {}\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-selected-text.ts",
    "content": "import { TextEditor, Selection, Position } from \"vscode\";\n\nconst openBracket = \"(\";\nconst closeBracket = \")\";\n\nexport const getSelectedText = (editor: TextEditor): Selection => {\n  const emptySelection = new Selection(\n    editor.document.positionAt(0),\n    editor.document.positionAt(0)\n  );\n  const language = editor.document.languageId;\n  if (language != \"dart\") return emptySelection;\n\n  const line = editor.document.lineAt(editor.selection.start);\n  const lineText = line.text;\n  const openBracketIndex = line.text.indexOf(\n    openBracket,\n    editor.selection.anchor.character\n  );\n\n  let widgetStartIndex =\n    openBracketIndex > 1\n      ? openBracketIndex - 1\n      : editor.selection.anchor.character;\n  for (widgetStartIndex; widgetStartIndex > 0; widgetStartIndex--) {\n    const currentChar = lineText.charAt(widgetStartIndex);\n    const isBeginningOfWidget =\n      currentChar === openBracket ||\n      (currentChar === \" \" &&\n        lineText.charAt(widgetStartIndex - 1) !== \",\" &&\n        lineText.substring(widgetStartIndex - 5, widgetStartIndex) != \"const\");\n    if (isBeginningOfWidget) break;\n  }\n  widgetStartIndex++;\n\n  if (openBracketIndex < 0) {\n    const commaIndex = lineText.indexOf(\",\", widgetStartIndex);\n    const bracketIndex = lineText.indexOf(closeBracket, widgetStartIndex);\n    const endIndex =\n      commaIndex >= 0\n        ? commaIndex\n        : bracketIndex >= 0\n        ? bracketIndex\n        : lineText.length;\n\n    return new Selection(\n      new Position(line.lineNumber, widgetStartIndex),\n      new Position(line.lineNumber, endIndex)\n    );\n  }\n\n  let bracketCount = 1;\n  for (let l = line.lineNumber; l < editor.document.lineCount; l++) {\n    const currentLine = editor.document.lineAt(l);\n    let c = l === line.lineNumber ? openBracketIndex + 1 : 0;\n    for (c; c < currentLine.text.length; c++) {\n      const currentChar = currentLine.text.charAt(c);\n      if (currentChar === openBracket) bracketCount++;\n      if (currentChar === closeBracket) bracketCount--;\n      if (bracketCount === 0) {\n        return new Selection(\n          new Position(line.lineNumber, widgetStartIndex),\n          new Position(l, c + 1)\n        );\n      }\n    }\n  }\n\n  return emptySelection;\n};\n"
  },
  {
    "path": "extensions/vscode/src/utils/get-template-setting.ts",
    "content": "import { workspace } from \"vscode\";\n\nexport const enum TemplateSetting {\n  Auto,\n  Equatable,\n  Freezed,\n  Simple,\n}\n\nexport const enum TemplateType {\n  Bloc,\n  Cubit,\n}\n\nexport function getTemplateSetting(type: TemplateType): TemplateSetting {\n  let config: string | undefined;\n  switch (type) {\n    case TemplateType.Bloc:\n      config = workspace.getConfiguration(\"bloc\").get(\"newBlocTemplate.type\");\n      break;\n    case TemplateType.Cubit:\n      config = workspace.getConfiguration(\"bloc\").get(\"newCubitTemplate.type\");\n      break;\n    default:\n      return TemplateSetting.Auto;\n  }\n\n  switch (config) {\n    case \"freezed\":\n      return TemplateSetting.Freezed;\n    case \"equatable\":\n      return TemplateSetting.Equatable;\n    case \"simple\":\n      return TemplateSetting.Simple;\n    case \"auto\":\n    default:\n      return TemplateSetting.Auto;\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/has-dependency.ts",
    "content": "import * as _ from \"lodash\";\n\nimport { getPubspec } from \".\";\n\nexport async function hasDependency(dependency: string) {\n  const pubspec = await getPubspec();\n  const dependencies = _.get(pubspec, \"dependencies\", {});\n  return _.has(dependencies, dependency);\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/index.ts",
    "content": "export * from \"./analyze-dependencies\";\nexport * from \"./convert-to\";\nexport * from \"./downloader\";\nexport * from \"./exec\";\nexport * from \"./get-bloc-tools-executable\";\nexport * from \"./get-bloc-type\";\nexport * from \"./get-dart-version\";\nexport * from \"./get-latest-package-version\";\nexport * from \"./get-pubspec\";\nexport * from \"./get-pubspec-path\";\nexport * from \"./get-selected-text\";\nexport * from \"./get-template-setting\";\nexport * from \"./has-dependency\";\nexport * from \"./install-bloc-tools\";\nexport * from \"./retry\";\nexport * from \"./set-show-context-menu\";\nexport * from \"./update-pubspec-dependency\";\nexport * from \"./wrap-with\";\n"
  },
  {
    "path": "extensions/vscode/src/utils/install-bloc-tools.ts",
    "content": "import { arch, type } from \"node:os\";\nimport { ExtensionContext, Uri } from \"vscode\";\nimport { downloadFile } from \".\";\n\nexport const BLOC_TOOLS_VERSION = \"0.1.0-dev.21\";\nexport const installBlocTools = async (\n  context: ExtensionContext\n): Promise<boolean> => {\n  try {\n    const os = getOS();\n    if (os === OperatingSystem.unknown) return false;\n    const arch = getArch();\n    if (arch == Architecture.unknown) return false;\n    await downloadFile(\n      Uri.parse(\n        `https://github.com/felangel/bloc/releases/download/bloc_tools-v${BLOC_TOOLS_VERSION}/bloc_${os}_${arch}`\n      ),\n      `bloc_${BLOC_TOOLS_VERSION}`,\n      context\n    );\n    return true;\n  } catch (_) {\n    return false;\n  }\n};\n\nfunction getOS(): OperatingSystem {\n  const hostOS = type();\n  switch (hostOS) {\n    case \"Linux\":\n      return OperatingSystem.linux;\n    case \"Darwin\":\n      return OperatingSystem.macos;\n    case \"Windows_NT\":\n      return OperatingSystem.windows;\n  }\n  return OperatingSystem.unknown;\n}\n\nfunction getArch(): Architecture {\n  const hostArch = arch();\n  switch (hostArch) {\n    case \"arm64\":\n      return Architecture.arm64;\n    case \"x64\":\n      return Architecture.x64;\n  }\n  return Architecture.unknown;\n}\n\nenum OperatingSystem {\n  linux = \"linux\",\n  macos = \"macos\",\n  windows = \"windows\",\n  unknown = \"-\",\n}\n\nenum Architecture {\n  arm64 = \"arm64\",\n  x64 = \"x64\",\n  unknown = \"-\",\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/retry.ts",
    "content": "export async function retry<T>(\n  callback: () => Promise<T>,\n  label: string,\n  retryCount: number,\n  delay: number,\n  onError?: (error: Error) => void\n): Promise<T> {\n  try {\n    return await callback();\n  } catch (error) {\n    if (error instanceof Error) {\n      if (retryCount === 0)\n        throw new Error(\n          `Maximum retry count exceeded.'${label}' failed with error: ${\n            error.message\n          }. ${JSON.stringify(error)}`\n        );\n      if (onError != null) onError(error);\n    }\n    await new Promise((resolve): void => {\n      setTimeout(resolve, delay);\n    });\n    return retry(callback, label, retryCount - 1, delay * 2, onError);\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/set-show-context-menu.ts",
    "content": "import * as yaml from \"js-yaml\";\nimport * as _ from \"lodash\";\n\nimport { commands, Uri, workspace } from \"vscode\";\n\nexport async function setShowContextMenu(\n  pubspec?: Uri | undefined,\n): Promise<void> {\n  async function pubspecIncludesBloc(pubspec: Uri): Promise<boolean> {\n    try {\n      const content = await workspace.fs.readFile(pubspec);\n      const yamlContent = yaml.load(content.toString());\n      const dependencies = _.get(yamlContent, \"dependencies\", {});\n      return [\n        \"angular_bloc\",\n        \"bloc\",\n        \"flutter_bloc\",\n        \"hydrated_bloc\",\n        \"replay_bloc\",\n      ].some((d) => dependencies.hasOwnProperty(d));\n    } catch (_) {}\n    return false;\n  }\n\n  async function workspaceIncludesBloc(): Promise<boolean> {\n    try {\n      const pubspecs = await workspace.findFiles(\"**/**/pubspec.yaml\");\n      for (const pubspec of pubspecs) {\n        if (await pubspecIncludesBloc(pubspec)) {\n          return true;\n        }\n      }\n    } catch (_) {}\n    return false;\n  }\n\n  commands.executeCommand(\n    \"setContext\",\n    \"bloc.showContextMenu\",\n    pubspec\n      ? await pubspecIncludesBloc(pubspec)\n      : await workspaceIncludesBloc(),\n  );\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/update-pubspec-dependency.ts",
    "content": "import * as _ from \"lodash\";\nimport * as fs from \"fs\";\nimport { workspace } from \"vscode\";\nimport { getPubspecPath } from \"./get-pubspec-path\";\n\nexport function updatePubspecDependency(dependency: {\n  name: string;\n  latestVersion: string;\n  currentVersion: string;\n}) {\n  if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {\n    const pubspecPath = getPubspecPath();\n    if (pubspecPath) {\n      try {\n        fs.writeFileSync(\n          pubspecPath,\n          fs\n            .readFileSync(pubspecPath, \"utf8\")\n            .replace(\n              `${dependency.name}: ${dependency.currentVersion}`,\n              `${dependency.name}: ${dependency.latestVersion}`\n            )\n        );\n      } catch (_) {}\n    }\n  }\n}\n"
  },
  {
    "path": "extensions/vscode/src/utils/wrap-with.ts",
    "content": "import { commands, SnippetString, window } from \"vscode\";\nimport { getSelectedText } from \"../utils\";\n\nconst interpolatedVarRegExp = /[$]/g;\nconst escapedCharacterRegExp = /[\\\\]/g;\n\nexport const wrapWith = async (snippet: (widget: string) => string) => {\n  let editor = window.activeTextEditor;\n  if (!editor) return;\n  const selection = getSelectedText(editor);\n  const widget = editor.document\n    .getText(selection)\n    .replace(escapedCharacterRegExp, \"\\\\\\\\\")\n    .replace(interpolatedVarRegExp, \"\\\\$\");\n  editor.insertSnippet(new SnippetString(snippet(widget)), selection);\n  await commands.executeCommand(\"editor.action.formatDocument\");\n};\n"
  },
  {
    "path": "extensions/vscode/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es6\",\n    \"outDir\": \"dist\",\n    \"lib\": [\"es6\", \"DOM\"],\n    \"sourceMap\": true,\n    \"rootDir\": \"src\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true\n  },\n  \"exclude\": [\"node_modules\", \".vscode-test\"]\n}\n"
  },
  {
    "path": "extensions/vscode/tslint.json",
    "content": "{\n  \"rules\": {\n    \"no-string-throw\": true,\n    \"no-unused-expression\": true,\n    \"no-duplicate-variable\": true,\n    \"curly\": true,\n    \"class-name\": true,\n    \"semicolon\": [true, \"always\"],\n    \"triple-equals\": true\n  },\n  \"defaultSeverity\": \"warning\"\n}\n"
  },
  {
    "path": "extensions/vscode/webpack.config.js",
    "content": "'use strict';\n\nconst path = require('path');\n\n/**@type {import('webpack').Configuration}*/\nconst config = {\n  target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/\n\n  entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/\n  output: {\n    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/\n    path: path.resolve(__dirname, 'dist'),\n    filename: 'extension.js',\n    libraryTarget: 'commonjs2',\n    devtoolModuleFilenameTemplate: '../[resource-path]'\n  },\n  devtool: 'source-map',\n  externals: {\n    vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/\n  },\n  resolve: {\n    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader\n    extensions: ['.ts', '.js']\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        exclude: /node_modules/,\n        use: [\n          {\n            loader: 'ts-loader'\n          }\n        ]\n      }\n    ]\n  }\n};\nmodule.exports = config;\n"
  },
  {
    "path": "extensions/zed/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\ndebug\ntarget\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# MSVC Windows builds of rustc generate these, which store debugging information\n*.pdb\n\n# Generated by cargo mutants\n# Contains mutation testing data\n**/mutants.out*/\n\n# RustRover\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": "extensions/zed/CHANGELOG.md",
    "content": "# 0.1.0\n\n- feat: initial extension release\n  - includes snippets and language server\n"
  },
  {
    "path": "extensions/zed/Cargo.toml",
    "content": "[package]\nname = \"bloc-zed\"\nversion = \"0.1.0\"\nedition = \"2024\"\nlicense = \"MIT\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nzed_extension_api = \"0.7.0\"\n"
  },
  {
    "path": "extensions/zed/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "extensions/zed/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc.png\" height=\"100\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n</p>\n\n---\n\n## Overview\n\n[Zed](https://zed.dev) support for the [Bloc Library](https://bloclibrary.dev) providing snippets and the Bloc language server for [Dart](https://dart.dev/) and [Flutter](https://flutter.dev/) apps.\n\n## Installation\n\nInstall from the Zed Extensions panel by searching for \"Bloc\", or install as a dev extension for local development:\n\n1. Open Zed\n2. Open the command palette and run `zed: install dev extension`\n3. Select the `extensions/zed` directory\n\n## Language Server\n\nThe Bloc language server provides custom diagnostic reporting for bloc-related lint rules. See [the official documentation](https://bloclibrary.dev/lint) for more information about configuring the linter and supported lint rules.\n\nThe language server binary (`bloc_tools`) is automatically downloaded from [GitHub releases](https://github.com/felangel/bloc/releases).\n\n## Snippets\n\n### Bloc\n\n| Shortcut            | Description                                   |\n| ------------------- | --------------------------------------------- |\n| `importbloc`        | Imports `package:bloc`                        |\n| `importflutterbloc` | Imports `package:flutter_bloc`                |\n| `importbloctest`    | Imports `package:bloc_test`                   |\n| `bloc`              | Creates a Bloc class                          |\n| `cubit`             | Creates a Cubit class                         |\n| `onevent`           | Register a new `EventHandler`                 |\n| `_onevent`          | Define a new `EventHandler`                   |\n| `blocobserver`      | Creates a `BlocObserver` class                |\n| `blocprovider`      | Creates a `BlocProvider` widget               |\n| `multiblocprovider` | Creates a `MultiBlocProvider` widget          |\n| `repoprovider`      | Creates a `RepositoryProvider` widget         |\n| `multirepoprovider` | Creates a `MultiRepositoryProvider` widget    |\n| `blocbuilder`       | Creates a `BlocBuilder` widget                |\n| `blocselector`      | Creates a `BlocSelector` widget               |\n| `bloclistener`      | Creates a `BlocListener` widget               |\n| `multibloclistener` | Creates a `MultiBlocListener` widget          |\n| `blocconsumer`      | Creates a `BlocConsumer` widget               |\n| `blocof`            | Shortcut for `BlocProvider.of()`              |\n| `repoof`            | Shortcut for `RepositoryProvider.of()`        |\n| `read`              | Shortcut for `context.read()`                 |\n| `watch`             | Shortcut for `context.watch()`                |\n| `select`            | Shortcut for `context.select()`               |\n| `blocstate`         | Creates a state class                         |\n| `blocevent`         | Creates an event class                        |\n| `bloctest`          | Creates a `blocTest`                          |\n| `mockbloc`          | Creates a class extending `MockBloc`          |\n| `_mockbloc`         | Creates a private class extending `MockBloc`  |\n| `mockcubit`         | Creates a class extending `MockCubit`         |\n| `_mockcubit`        | Creates a private class extending `MockCubit` |\n| `fake`              | Creates a class extending `Fake`              |\n| `_fake`             | Creates a private class extending `Fake`      |\n| `mock`              | Creates a class extending `Mock`              |\n| `_mock`             | Creates a private class extending `Mock`      |\n\n### Freezed Bloc\n\n| Shortcut | Description             |\n| -------- | ----------------------- |\n| `fstate` | Creates a freezed state |\n| `fevent` | Creates a freezed event |\n"
  },
  {
    "path": "extensions/zed/extension.toml",
    "content": "id = \"bloc\"\nname = \"Bloc\"\ndescription = \"Support for the Bloc state management library for Dart and Flutter. Provides snippets and the Bloc language server.\"\nversion = \"0.1.0\"\nschema_version = 1\nauthors = [\"Felix Angelov <felangelov@gmail.com>\", \"Johannes Naylor <me@jonaylor.com>\"]\nrepository = \"https://github.com/felangel/bloc\"\n\nsnippets = [\"snippets/dart.json\"]\n\n[language_servers.bloc]\nname = \"Bloc Language Server\"\nlanguages = [\"Dart\"]\n"
  },
  {
    "path": "extensions/zed/snippets/dart.json",
    "content": "{\n  \"Bloc\": {\n    \"prefix\": \"bloc\",\n    \"body\": [\n      \"class ${1:Subject}Bloc extends Bloc<$1Event, $1State> {\",\n      \"\\t${1:Subject}Bloc() : super(${1:Subject}Initial()) {\",\n      \"\\t\\ton<$1Event>((event, emit) {\",\n      \"\\t\\t\\t$0\",\n      \"\\t\\t});\",\n      \"\\t}\",\n      \"}\"\n    ],\n    \"description\": \"Creates a Bloc class\"\n  },\n  \"Cubit\": {\n    \"prefix\": \"cubit\",\n    \"body\": [\n      \"class ${1:Subject}Cubit extends Cubit<$1State> {\",\n      \"\\t${1:Subject}Cubit() : super(${1:Subject}Initial());\",\n      \"}\"\n    ],\n    \"description\": \"Creates a Cubit class\"\n  },\n  \"BlocObserver\": {\n    \"prefix\": \"blocobserver\",\n    \"body\": [\n      \"import 'package:bloc/bloc.dart';\",\n      \"\",\n      \"class ${1:My}BlocObserver extends BlocObserver {\",\n      \"\\t@override\",\n      \"\\tvoid onEvent(Bloc bloc, Object? event) {\",\n      \"\\t\\tsuper.onEvent(bloc, event);\",\n      \"\\t\\t$0\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onError(BlocBase bloc, Object error, StackTrace stackTrace) {\",\n      \"\\t\\tsuper.onError(bloc, error, stackTrace);\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onChange(BlocBase bloc, Change change) {\",\n      \"\\t\\tsuper.onChange(bloc, change);\",\n      \"\\t}\",\n      \"\",\n      \"\\t@override\",\n      \"\\tvoid onTransition(Bloc bloc, Transition transition) {\",\n      \"\\t\\tsuper.onTransition(bloc, transition);\",\n      \"\\t}\",\n      \"}\"\n    ],\n    \"description\": \"Creates a BlocObserver class\"\n  },\n  \"Bloc State\": {\n    \"prefix\": \"blocstate\",\n    \"body\": [\n      \"class ${1:Subject}${2:Verb}${3:State} extends $1State {\",\n      \"\\tconst $1$2$3($5);\",\n      \"\",\n      \"\\t$4\",\n      \"\",\n      \"\\t@override\",\n      \"\\tList<Object> get props => [$6];\",\n      \"}\"\n    ],\n    \"description\": \"Creates a Bloc state class (Subject + Verb + State)\"\n  },\n  \"Bloc Event\": {\n    \"prefix\": \"blocevent\",\n    \"body\": [\n      \"class ${1:Subject}${2:Noun}${3:Verb} extends $1Event {\",\n      \"\\tconst $1$2$3($5);\",\n      \"\",\n      \"\\t$4\",\n      \"\",\n      \"\\t@override\",\n      \"\\tList<Object> get props => [$6];\",\n      \"}\"\n    ],\n    \"description\": \"Creates a Bloc event class (Subject + Noun + Verb)\"\n  },\n  \"Import package:bloc\": {\n    \"prefix\": \"importbloc\",\n    \"body\": \"import 'package:bloc/bloc.dart';\",\n    \"description\": \"Import package:bloc/bloc.dart\"\n  },\n  \"Register Event Handler\": {\n    \"prefix\": \"onevent\",\n    \"body\": [\n      \"on<${1:Subject}Event>((event, emit) {\",\n      \"\\t$0\",\n      \"});\"\n    ],\n    \"description\": \"Register a new EventHandler\"\n  },\n  \"Define Event Handler\": {\n    \"prefix\": \"_onevent\",\n    \"body\": [\n      \"${1:void} _on${2:Event}(\",\n      \"\\t$2 event,\",\n      \"\\tEmitter<${3:Subject}State> emit,\",\n      \") {\",\n      \"\\t$0\",\n      \"}\"\n    ],\n    \"description\": \"Define a new EventHandler\"\n  },\n  \"BlocTest\": {\n    \"prefix\": \"bloctest\",\n    \"body\": [\n      \"blocTest<${1:Subject}${2:Bloc}, $1State>(\",\n      \"\\t'emits [${3:MyState}] when ${4:MyEvent} is added.',\",\n      \"\\tbuild: () => $1$2(),\",\n      \"\\tact: (bloc) => bloc.add($4()),\",\n      \"\\texpect: () => const <$1State>[$3()],\",\n      \");\"\n    ],\n    \"description\": \"Creates a blocTest\"\n  },\n  \"Import package:bloc_test\": {\n    \"prefix\": \"importbloctest\",\n    \"body\": \"import 'package:bloc_test/bloc_test.dart';\",\n    \"description\": \"Import package:bloc_test/bloc_test.dart\"\n  },\n  \"MockBloc\": {\n    \"prefix\": \"mockbloc\",\n    \"body\": \"class Mock${1:Subject}Bloc extends MockBloc<${1}Event, ${1}State> implements ${1}Bloc {}\",\n    \"description\": \"Creates a MockBloc class\"\n  },\n  \"_MockBloc\": {\n    \"prefix\": \"_mockbloc\",\n    \"body\": \"class _Mock${1:Subject}Bloc extends MockBloc<${1}Event, ${1}State> implements ${1}Bloc {}\",\n    \"description\": \"Creates a private MockBloc class\"\n  },\n  \"MockCubit\": {\n    \"prefix\": \"mockcubit\",\n    \"body\": \"class Mock${1:Subject}Cubit extends MockCubit<${1}State> implements ${1}Cubit {}\",\n    \"description\": \"Creates a MockCubit class\"\n  },\n  \"_MockCubit\": {\n    \"prefix\": \"_mockcubit\",\n    \"body\": \"class _Mock${1:Subject}Cubit extends MockCubit<${1}State> implements ${1}Cubit {}\",\n    \"description\": \"Creates a private MockCubit class\"\n  },\n  \"Fake\": {\n    \"prefix\": \"fake\",\n    \"body\": \"class Fake${1:Subject} extends Fake implements ${1} {}\",\n    \"description\": \"Creates a Fake class\"\n  },\n  \"_Fake\": {\n    \"prefix\": \"_fake\",\n    \"body\": \"class _Fake${1:Subject} extends Fake implements ${1} {}\",\n    \"description\": \"Creates a private Fake class\"\n  },\n  \"Mock\": {\n    \"prefix\": \"mock\",\n    \"body\": \"class Mock${1:Subject} extends Mock implements ${1} {}\",\n    \"description\": \"Creates a Mock class\"\n  },\n  \"_Mock\": {\n    \"prefix\": \"_mock\",\n    \"body\": \"class _Mock${1:Subject} extends Mock implements ${1} {}\",\n    \"description\": \"Creates a private Mock class\"\n  },\n  \"BlocProvider\": {\n    \"prefix\": \"blocprovider\",\n    \"body\": [\n      \"BlocProvider(\",\n      \"\\tcreate: (context) => ${1:Subject}${2:Bloc}(),\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a BlocProvider widget\"\n  },\n  \"MultiBlocProvider\": {\n    \"prefix\": \"multiblocprovider\",\n    \"body\": [\n      \"MultiBlocProvider(\",\n      \"\\tproviders: [\",\n      \"\\t\\tBlocProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${1:Subject}${2:Bloc}(),\",\n      \"\\t\\t),\",\n      \"\\t\\tBlocProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${3:Subject}${4:Bloc}(),\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${5:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a MultiBlocProvider widget\"\n  },\n  \"RepositoryProvider\": {\n    \"prefix\": \"repoprovider\",\n    \"body\": [\n      \"RepositoryProvider(\",\n      \"\\tcreate: (context) => ${1:Subject}Repository(),\",\n      \"\\tchild: ${2:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a RepositoryProvider widget\"\n  },\n  \"MultiRepositoryProvider\": {\n    \"prefix\": \"multirepoprovider\",\n    \"body\": [\n      \"MultiRepositoryProvider(\",\n      \"\\tproviders: [\",\n      \"\\t\\tRepositoryProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${1:Subject}Repository(),\",\n      \"\\t\\t),\",\n      \"\\t\\tRepositoryProvider(\",\n      \"\\t\\t\\tcreate: (context) => ${2:Subject}Repository(),\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a MultiRepositoryProvider widget\"\n  },\n  \"BlocBuilder\": {\n    \"prefix\": \"blocbuilder\",\n    \"body\": [\n      \"BlocBuilder<${1:Subject}${2:Bloc}, $1State>(\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${3:Container()};\",\n      \"\\t},\",\n      \")\"\n    ],\n    \"description\": \"Creates a BlocBuilder widget\"\n  },\n  \"BlocSelector\": {\n    \"prefix\": \"blocselector\",\n    \"body\": [\n      \"BlocSelector<${1:Subject}${2:Bloc}, $1State, ${3:Selected}>(\",\n      \"\\tselector: (state) {\",\n      \"\\t\\treturn ${4:state};\",\n      \"\\t},\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${5:Container()};\",\n      \"\\t},\",\n      \")\"\n    ],\n    \"description\": \"Creates a BlocSelector widget\"\n  },\n  \"BlocListener\": {\n    \"prefix\": \"bloclistener\",\n    \"body\": [\n      \"BlocListener<${1:Subject}${2:Bloc}, $1State>(\",\n      \"\\tlistener: (context, state) {\",\n      \"\\t\\t$0\",\n      \"\\t},\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a BlocListener widget\"\n  },\n  \"MultiBlocListener\": {\n    \"prefix\": \"multibloclistener\",\n    \"body\": [\n      \"MultiBlocListener(\",\n      \"\\tlisteners: [\",\n      \"\\t\\tBlocListener<${1:Subject}${2:Bloc}, $1State>(\",\n      \"\\t\\t\\tlistener: (context, state) {\",\n      \"\\t\\t\\t\\t$0\",\n      \"\\t\\t\\t},\",\n      \"\\t\\t),\",\n      \"\\t],\",\n      \"\\tchild: ${3:Container()},\",\n      \")\"\n    ],\n    \"description\": \"Creates a MultiBlocListener widget\"\n  },\n  \"BlocConsumer\": {\n    \"prefix\": \"blocconsumer\",\n    \"body\": [\n      \"BlocConsumer<${1:Subject}${2:Bloc}, $1State>(\",\n      \"\\tlistener: (context, state) {\",\n      \"\\t\\t$0\",\n      \"\\t},\",\n      \"\\tbuilder: (context, state) {\",\n      \"\\t\\treturn ${3:Container()};\",\n      \"\\t},\",\n      \")\"\n    ],\n    \"description\": \"Creates a BlocConsumer widget\"\n  },\n  \"BlocProvider.of()\": {\n    \"prefix\": \"blocof\",\n    \"body\": \"BlocProvider.of<${1:Subject}${2:Bloc}>(context)\",\n    \"description\": \"Shortcut for BlocProvider.of()\"\n  },\n  \"RepositoryProvider.of()\": {\n    \"prefix\": \"repoof\",\n    \"body\": \"RepositoryProvider.of<${1:Subject}Repository>(context)\",\n    \"description\": \"Shortcut for RepositoryProvider.of()\"\n  },\n  \"context.read()\": {\n    \"prefix\": \"read\",\n    \"body\": \"context.read<${1:Subject}${2:Bloc}>()\",\n    \"description\": \"Shortcut for context.read()\"\n  },\n  \"context.select()\": {\n    \"prefix\": \"select\",\n    \"body\": \"context.select((${1:Subject}${2:Bloc} ${3:bloc}) => $3$0)\",\n    \"description\": \"Shortcut for context.select()\"\n  },\n  \"context.watch()\": {\n    \"prefix\": \"watch\",\n    \"body\": \"context.watch<${1:Subject}${2:Bloc}>()\",\n    \"description\": \"Shortcut for context.watch()\"\n  },\n  \"Import package:flutter_bloc\": {\n    \"prefix\": \"importflutterbloc\",\n    \"body\": \"import 'package:flutter_bloc/flutter_bloc.dart';\",\n    \"description\": \"Import package:flutter_bloc/flutter_bloc.dart\"\n  },\n  \"New freezed state\": {\n    \"prefix\": \"fstate\",\n    \"body\": \"const factory ${1:ClassName}.${2:stateName}($3) = _${4:StateName};\\n$0\",\n    \"description\": \"Creates a freezed state\"\n  },\n  \"New freezed event\": {\n    \"prefix\": \"fevent\",\n    \"body\": \"const factory ${1:ClassName}.${2:eventName}($3) = _${4:EventName};\\n$0\",\n    \"description\": \"Creates a freezed event\"\n  }\n}\n"
  },
  {
    "path": "extensions/zed/src/lib.rs",
    "content": "use std::fs;\nuse zed_extension_api::{self as zed, LanguageServerId, Result, settings::LspSettings};\n\nconst BLOC_TOOLS_REPO: &str = \"felangel/bloc\";\nconst BLOC_TOOLS_RELEASE_TAG: &str = \"bloc_tools-v0.1.0-dev.22\";\n\nstruct BlocExtension {\n    cached_binary_path: Option<String>,\n}\n\nimpl BlocExtension {\n    fn language_server_binary_path(\n        &mut self,\n        language_server_id: &LanguageServerId,\n        worktree: &zed::Worktree,\n    ) -> Result<String> {\n        let binary_settings = LspSettings::for_worktree(\"bloc\", worktree)\n            .ok()\n            .and_then(|s| s.binary);\n\n        if let Some(path) = binary_settings.as_ref().and_then(|s| s.path.clone()) {\n            return Ok(path);\n        }\n\n        if let Some(path) = &self.cached_binary_path {\n            if fs::metadata(path).map_or(false, |m| m.is_file()) {\n                return Ok(path.clone());\n            }\n        }\n\n        zed::set_language_server_installation_status(\n            language_server_id,\n            &zed::LanguageServerInstallationStatus::CheckingForUpdate,\n        );\n\n        let release = match zed::github_release_by_tag_name(BLOC_TOOLS_REPO, BLOC_TOOLS_RELEASE_TAG)\n        {\n            Ok(release) => release,\n            Err(e) => {\n                let url = format!(\n                    \"https://api.github.com/repos/{BLOC_TOOLS_REPO}/releases/tags/{BLOC_TOOLS_RELEASE_TAG}\"\n                );\n                return Err(format!(\"Failed to fetch release from {url}: {e}\"));\n            }\n        };\n\n        let (os, arch) = zed::current_platform();\n\n        let asset_name = format!(\n            \"bloc_{os}_{arch}\",\n            os = match os {\n                zed::Os::Mac => \"macos\",\n                zed::Os::Linux => \"linux\",\n                zed::Os::Windows => \"windows\",\n            },\n            arch = match arch {\n                zed::Architecture::Aarch64 => \"arm64\",\n                zed::Architecture::X8664 => \"x64\",\n                zed::Architecture::X86 => \"x64\",\n            },\n        );\n\n        let asset = release\n            .assets\n            .iter()\n            .find(|a| a.name == asset_name)\n            .ok_or_else(|| {\n                format!(\n                    \"No compatible binary found for this platform (looked for '{}')\",\n                    asset_name\n                )\n            })?;\n\n        let version_dir = format!(\"bloc_tools-{}\", release.version);\n        let binary_path = format!(\"{version_dir}/{asset_name}\");\n\n        if !fs::metadata(&binary_path).map_or(false, |m| m.is_file()) {\n            zed::set_language_server_installation_status(\n                language_server_id,\n                &zed::LanguageServerInstallationStatus::Downloading,\n            );\n\n            fs::create_dir_all(&version_dir)\n                .map_err(|e| format!(\"Failed to create directory '{version_dir}': {e}\"))?;\n\n            zed::download_file(\n                &asset.download_url,\n                &binary_path,\n                zed::DownloadedFileType::Uncompressed,\n            )\n            .map_err(|e| format!(\"Failed to download bloc_tools: {e}\"))?;\n\n            zed::make_file_executable(&binary_path)?;\n\n            // Clean up old versions\n            if let Ok(entries) = fs::read_dir(\".\") {\n                for entry in entries.flatten() {\n                    if let Some(name) = entry.file_name().to_str() {\n                        if name.starts_with(\"bloc_tools-\") && name != version_dir {\n                            fs::remove_dir_all(entry.path()).ok();\n                        }\n                    }\n                }\n            }\n        }\n\n        self.cached_binary_path = Some(binary_path.clone());\n        Ok(binary_path)\n    }\n}\n\nimpl zed::Extension for BlocExtension {\n    fn new() -> Self {\n        BlocExtension {\n            cached_binary_path: None,\n        }\n    }\n\n    fn language_server_command(\n        &mut self,\n        language_server_id: &LanguageServerId,\n        worktree: &zed::Worktree,\n    ) -> Result<zed::Command> {\n        let binary_path = self.language_server_binary_path(language_server_id, worktree)?;\n\n        let args = LspSettings::for_worktree(\"bloc\", worktree)\n            .ok()\n            .and_then(|s| s.binary)\n            .and_then(|b| b.arguments)\n            .unwrap_or_else(|| vec![\"language-server\".to_string()]);\n\n        Ok(zed::Command {\n            command: binary_path,\n            args,\n            env: Default::default(),\n        })\n    }\n}\n\nzed::register_extension!(BlocExtension);\n"
  },
  {
    "path": "packages/angular_bloc/CHANGELOG.md",
    "content": "# 10.0.0-dev.5\n\n- chore(deps): upgrade to `package:bloc v9.0.0`\n- chore: update sponsors\n\n# 10.0.0-dev.4\n\n- chore: update sponsors table in `README`\n- chore: add `funding` to `pubspec.yaml`\n\n# 10.0.0-dev.3\n\n- chore: update copyright year\n- chore: update sponsors\n\n# 10.0.0-dev.2\n\n- chore(deps): upgrade to `package:ngdart v8.0.0-dev.4`\n- chore: add `topics` to `pubspec.yaml`\n- chore(deps): upgrade to `package:mocktail v1.0.0`\n- chore: update sponsors\n\n# 10.0.0-dev.1\n\n- docs: upgrade to Dart 3\n- refactor: standardize analysis_options\n- refactor: update sdk constraints and fix analysis warnings\n\n# 10.0.0-dev.0\n\n- **BREAKING**: refactor: upgrade `ngdart` to v8 pre-release\n\n# 9.0.0\n\n- **BREAKING**: feat: upgrade to `ngdart v7.0.0`\n- refactor: remove deprecated `invariant_booleans` lint rule\n- docs: update example to follow naming conventions\n- chore: add screenshots to `pubspec.yaml`\n\n# 8.0.0\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0`\n\n# 8.0.0-dev.3\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`\n\n# 8.0.0-dev.2\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`\n\n# 8.0.0-dev.1\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`\n\n# 7.1.0\n\n- feat: upgrade to `bloc ^7.2.0`\n\n# 7.0.0\n\n- **BREAKING**: refactor: upgrade to `angular ^7.0.0`\n- **BREAKING**: refactor: upgrade to `bloc ^7.0.0`\n- **BREAKING**: refactor upgrade to null safety (`dart >= 2.12.0`)\n\n# 6.0.1\n\n- Fix CHANGELOG formatting\n\n# 6.0.0\n\n- Update to `angular ^6.0.1`\n- Update to `bloc ^6.1.0`\n- Export `package:bloc`\n\n# 6.0.0-dev.8\n\n- Update to `bloc ^6.0.0`\n\n# 6.0.0-dev.7\n\n- Update to `bloc ^6.0.0-dev.1`\n- Remove external dependency on package:angular_cubit\n- Inline documentation updates\n- README updates\n- Example application updates\n\n# 6.0.0-dev.6\n\n- Update to `bloc ^5.0.1`\n- Update to `angular_cubit ^0.1.0-dev.4`\n\n# 6.0.0-dev.5\n\n- Update to `bloc ^5.0.0`\n- Update to `angular_cubit ^0.1.0-dev.1`\n\n# 6.0.0-dev.4\n\n- Update to `bloc ^5.0.0-dev.11`\n- Updates to documentation\n\n# 6.0.0-dev.3\n\n- Update to `bloc ^5.0.0-dev.10`\n\n# 6.0.0-dev.2\n\n- Update to `bloc ^5.0.0-dev.7`\n\n# 6.0.0-dev.1\n\n- Update to `bloc ^5.0.0-dev.6`\n- Update to extend [angular_cubit](https://pub.dev/packages/angular_cubit)\n- Update documentation and static assets.\n\n# 5.0.0-dev.1\n\n- Update to `angular_dart: ^6.0.0-alpha+1`\n\n# 4.0.0\n\n- Update to `bloc: ^4.0.0`\n\n# 4.0.0-dev.4\n\n- Update to `bloc: ^4.0.0-dev.4`\n\n# 4.0.0-dev.3\n\n- Update to `bloc: ^4.0.0-dev.3`\n\n# 4.0.0-dev.2\n\n- Update to `bloc: ^4.0.0-dev.2`\n\n# 4.0.0-dev.1\n\n- Update to `bloc: ^4.0.0-dev.1`\n\n# 3.0.0\n\n- Update to `bloc: ^3.0.0` and Minor Updates to Documentation\n\n# 3.0.0-dev.1\n\n- Update to `bloc: ^3.0.0-dev.1`\n\n# 2.0.0\n\n- Update to `bloc: ^2.0.0` and Documentation Updates\n- Adhere to [effective dart](https://dart.dev/guides/language/effective-dart) ([#561](https://github.com/felangel/bloc/issues/561))\n\n# 1.0.0\n\n- Update to `bloc: ^1.0.0` and Minor Updates to Documentation\n\n# 0.11.0\n\n- Update to `bloc: ^0.16.0` and Minor Updates to Documentation\n\n# 0.10.0\n\n- Update to `bloc: ^0.15.0` and Minor Updates to Documentation\n\n# 0.9.0\n\n- Update to `bloc: ^0.14.0` and Minor Updates to Documentation\n\n# 0.8.0\n\n- Update to `bloc: ^0.13.0` and Minor Updates to Documentation\n\n# 0.7.0\n\n- Update to `bloc: ^0.12.0` and Minor Updates to Documentation\n\n# 0.6.0\n\n- Update to `bloc: ^0.11.0` and Minor Updates to Documentation\n\n# 0.5.0\n\n- Update to `bloc: ^0.10.0` and Minor Updates to Documentation\n\n# 0.4.4\n\n- Additional Minor Updates to Documentation\n\n# 0.4.3\n\n- Update to `bloc:^0.9.3` and Minor Updates to Documentation\n\n# 0.4.2\n\n- Additional Minor Updates to Documentation\n\n# 0.4.1\n\n- Minor Updates to Documentation\n\n# 0.4.0\n\n- Update to `bloc: ^0.9.0`\n\n# 0.3.3\n\n- Additional Minor Updates to Documentation\n\n# 0.3.2\n\n- Additional Minor Updates to Documentation\n\n# 0.3.1\n\n- Minor Updates to Documentation\n\n# 0.3.0\n\n- Update to `bloc: ^0.8.0`\n\n# 0.2.5\n\n- Additional Minor Updates to Documentation\n\n# 0.2.4\n\n- Additional Minor Updates to Documentation\n\n# 0.2.3\n\n- Updates to Documentation and Examples\n\n# 0.2.2\n\n- Additional Minor Updates to Documentation\n\n# 0.2.1\n\n- Minor Updates to Documentation\n\n# 0.2.0\n\n- Update to `bloc: ^0.7.0`\n\n# 0.1.2\n\n- Minor Updates to Documentation\n\n# 0.1.1\n\n- Minor Updates to Documentation\n\n# 0.1.0\n\n- Initial Version of the library.\n  - Includes the ability to connect presentation layer to `Bloc` by using the `BlocPipe` Component.\n"
  },
  {
    "path": "packages/angular_bloc/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "packages/angular_bloc/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/angular_bloc.png\" height=\"100\" alt=\"Angular Bloc Package\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/angular_bloc\"><img src=\"https://img.shields.io/pub/v/angular_bloc.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nA Dart package that helps implement the BLoC pattern in [AngularDart](https://pub.dev/packages/ngdart). Built to work with [package:bloc](https://pub.dev/packages/bloc).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Angular Components\n\n**BlocPipe** is an Angular pipe which helps bind `Bloc` state changes to the presentation layer. `BlocPipe` handles rendering the html element in response to new states. `BlocPipe` is very similar to `AsyncPipe` but is designed specifically for blocs.\n\n## Cubit Usage\n\nLets take a look at how to use `BlocPipe` to hook up a `CounterPage` html template to a `CounterCubit`.\n\n### `counter_cubit.dart`\n\n```dart\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state — 1);\n}\n```\n\n### `counter_page_component.dart`\n\n```dart\nimport 'package:angular/angular.dart';\nimport 'package:angular_bloc/angular_bloc.dart';\n\nimport './counter_cubit.dart';\n\n@Component(\n  selector: 'counter-page',\n  templateUrl: 'counter_page_component.html',\n  pipes: [BlocPipe],\n)\nclass CounterPageComponent implements OnInit, OnDestroy {\n  late final CounterCubit counterCubit;\n\n  @override\n  void ngOnInit() {\n    counterCubit = CounterCubit();\n  }\n\n  @override\n  void ngOnDestroy() {\n    counterCubit.close();\n  }\n}\n```\n\n### `counter_page_component.html`\n\n```html\n<div>\n  <h1>Counter App</h1>\n  <h2>Current Count: {{ $pipe.bloc(counterCubit) }}</h2>\n  <button (click)=\"counterCubit.increment()\">➕</button>\n  <button (click)=\"counterCubit.decrement()\">➖</button>\n</div>\n```\n\n## Bloc Usage\n\nLets take a look at how to use `BlocPipe` to hook up a `CounterPage` html template to a `CounterBloc`.\n\n### `counter_bloc.dart`\n\n```dart\nimport 'package:bloc/bloc.dart';\n\nsealed class CounterEvent {}\nfinal class CounterIncrementPressed extends CounterEvent {}\nfinal class CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n```\n\n### `counter_page_component.dart`\n\n```dart\nimport 'package:angular/angular.dart';\nimport 'package:angular_bloc/angular_bloc.dart';\n\nimport './counter_bloc.dart';\n\n@Component(\n  selector: 'counter-page',\n  templateUrl: 'counter_page_component.html',\n  pipes: [BlocPipe],\n)\nclass CounterPageComponent implements OnInit, OnDestroy {\n  late final CounterBloc counterBloc;\n\n  @override\n  void ngOnInit() {\n    counterBloc = CounterBloc();\n  }\n\n  @override\n  void ngOnDestroy() {\n    counterBloc.close();\n  }\n\n  void increment() => counterBloc.add(CounterIncrementPressed());\n\n  void decrement() => counterBloc.add(CounterDecrementPressed());\n}\n```\n\n### `counter_page_component.html`\n\n```html\n<div>\n  <h1>Counter App</h1>\n  <h2>Current Count: {{ $pipe.bloc(counterBloc) }}</h2>\n  <button (click)=\"increment()\">+</button>\n  <button (click)=\"decrement()\">-</button>\n</div>\n```\n\nAt this point we have successfully separated our presentational layer from our business logic layer!\n\n## Dart Versions\n\n- Dart 2: >= 2.12.0\n\n## Examples\n\n- [Counter](https://github.com/felangel/bloc/tree/master/examples/angular_counter) - a complete example of how to create a `CounterBloc` and hook it up to an AngularDart app.\n- [Github Search](https://github.com/felangel/bloc/tree/master/examples/github_search/angular_github_search) - an example of how to create a Github Search Application using the `bloc` and `angular_bloc` packages.\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/angular_bloc/analysis_options.yaml",
    "content": "include: \n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml"
  },
  {
    "path": "packages/angular_bloc/dart_test.yaml",
    "content": "# As of test: ^0.12.32, we can use a common/base dart_test.yaml, which this is.\n#\n# See documentation:\n# * https://github.com/dart-lang/test/blob/master/doc/configuration.md\n# * https://github.com/dart-lang/test/blob/master/doc/configuration.md#include\npresets:\n  # When run with -P travis, we have different settings/options.\n  #\n  # 1: We don't use Chrome --headless:\n  # 2: We use --reporter expanded\n  # 3: We skip anything tagged \"fails-on-travis\".\n  travis:\n    override_platforms:\n      chrome:\n        settings:\n          # Disable some security options, since this is only on Travis.\n          # https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md\n          # https://docs.travis-ci.com/user/chrome#Sandboxing\n          arguments: --no-sandbox\n          # TODO(https://github.com/dart-lang/test/issues/772)\n          headless: false\n\n    # Don't run any tests that are tagged [\"fails-on-travis\"].\n    exclude_tags: \"fails-on-travis\"\n\n    # https://github.com/dart-lang/test/blob/master/doc/configuration.md#reporter\n    reporter: expanded\n\n# This repository has only web tests.\nplatforms:\n  - chrome"
  },
  {
    "path": "packages/angular_bloc/example/example.dart",
    "content": "// This example is one-pager for the [angular_bloc](https://github.com/felangel/bloc/tree/master/examples/angular_counter) example.\n\nimport 'package:angular_bloc/angular_bloc.dart';\nimport 'package:ngdart/angular.dart';\n\n@Component(\n  selector: 'my-app',\n  template: '<counter-page></counter-page>',\n  directives: [CounterPageComponent],\n)\nclass AppComponent {}\n\nabstract class CounterEvent {}\n\nclass CounterIncrementPressed extends CounterEvent {}\n\nclass CounterDecrementPressed extends CounterEvent {}\n\n// ignore: prefer_file_naming_conventions\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    // ignore: avoid_print\n    print(transition);\n  }\n}\n\nconst String template =\n    r'''<div><h1>Counter App</h1><h2>Current Count: {{ $pipe.bloc(counterBloc) }}</h2><button (click)=\"increment()\">+</button><button (click)=\"decrement()\">-</button></div>''';\n\n@Component(\n  selector: 'counter-page',\n  pipes: [BlocPipe],\n  template: template,\n)\nclass CounterPageComponent implements OnInit, OnDestroy {\n  late final CounterBloc counterBloc;\n\n  @override\n  void ngOnInit() {\n    counterBloc = CounterBloc();\n  }\n\n  @override\n  void ngOnDestroy() {\n    counterBloc.close();\n  }\n\n  void increment() => counterBloc.add(CounterIncrementPressed());\n\n  void decrement() => counterBloc.add(CounterDecrementPressed());\n}\n"
  },
  {
    "path": "packages/angular_bloc/lib/angular_bloc.dart",
    "content": "/// Angular components that make it easy to implement the BLoC design pattern.\n/// Built to be used with the [bloc state management package](https://pub.dev/packages/bloc).\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary angular_dart;\n\nexport 'package:bloc/bloc.dart';\nexport './src/pipes/pipes.dart';\n"
  },
  {
    "path": "packages/angular_bloc/lib/src/pipes/bloc_pipe.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:ngdart/angular.dart' show ChangeDetectorRef, OnDestroy, Pipe;\n\n/// {@template bloc_pipe}\n/// A `pipe` which helps bind [BlocBase] ([Bloc] and [Cubit])\n/// state changes to the presentation layer.\n///\n/// [BlocPipe] handles rendering the html element in response to new states.\n/// [BlocPipe] is very similar to `AsyncPipe` but is designed\n/// specifically for blocs.\n///\n/// ```html\n/// <p>Current Count: {{ $pipe.bloc(counterBloc) }}</p>\n/// ```\n///\n/// See also:\n///\n/// * [Bloc] for more information about how to make and use blocs.\n/// * [Cubit] for more information about how to make and use cubits.\n///\n/// {@endtemplate}\n@Pipe('bloc', pure: false)\nclass BlocPipe implements OnDestroy {\n  /// {@macro bloc_pipe}\n  BlocPipe(this._ref);\n\n  final ChangeDetectorRef _ref;\n  BlocBase<dynamic>? _bloc;\n  Object? _latestValue;\n  StreamSubscription<dynamic>? _subscription;\n\n  @override\n  void ngOnDestroy() {\n    if (_subscription != null) {\n      _dispose();\n    }\n  }\n\n  /// Angular invokes the [transform] method with the value of a binding as the\n  /// first argument, and any parameters as the second argument in list form.\n  dynamic transform(BlocBase<dynamic>? bloc) {\n    if (_bloc == null) {\n      if (bloc != null) {\n        _subscribe(bloc);\n      }\n    } else if (!_maybeStreamIdentical(bloc, _bloc)) {\n      _dispose();\n      return transform(bloc);\n    }\n    if (bloc == null) {\n      return null;\n    }\n    return _latestValue ?? bloc.state;\n  }\n\n  void _subscribe(BlocBase<dynamic> bloc) {\n    _bloc = bloc;\n    _subscription = bloc.stream.listen(\n      (dynamic value) => _updateLatestValue(bloc, value),\n    );\n  }\n\n  void _updateLatestValue(dynamic async, Object? value) {\n    if (identical(async, _bloc)) {\n      _latestValue = value;\n      _ref.markForCheck();\n    }\n  }\n\n  void _dispose() {\n    _subscription?.cancel();\n    _latestValue = null;\n    _subscription = null;\n    _bloc = null;\n  }\n\n  // StreamController.stream getter always returns new Stream instance,\n  // operator== check is also needed. See\n  // https://github.com/dart-lang/angular2/issues/260\n  static bool _maybeStreamIdentical(dynamic a, dynamic b) {\n    if (!identical(a, b)) {\n      return a is Stream && b is Stream && a == b;\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "packages/angular_bloc/lib/src/pipes/pipes.dart",
    "content": "export './bloc_pipe.dart' show BlocPipe;\n"
  },
  {
    "path": "packages/angular_bloc/pubspec.yaml",
    "content": "name: angular_bloc\ndescription: Angular Components that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.\nversion: 10.0.0-dev.5\nrepository: https://github.com/felangel/bloc/tree/master/packages/angular_bloc\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://bloclibrary.dev\ndocumentation: https://bloclibrary.dev/getting-started\ntopics: [bloc, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.19.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  ngdart: ^8.0.0-dev.4\n\ndev_dependencies:\n  bloc_lint: ^0.3.0\n  build_runner: ^2.0.0\n  build_test: ^2.0.0\n  build_web_compilers: ^4.0.0\n  meta: ^1.8.0\n  mocktail: ^1.0.0\n  ngtest: ^5.0.0-dev.3\n  test: ^1.16.0\n\nscreenshots:\n  - description: The angular bloc package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/angular_bloc/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/angular_bloc/test/bloc_pipe_test.dart",
    "content": "@TestOn('browser')\nlibrary angular_bloc_test;\n\nimport 'dart:async';\n\nimport 'package:angular_bloc/angular_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:ngdart/angular.dart' show ChangeDetectorRef;\nimport 'package:test/test.dart';\n\nclass MockChangeDetectorRef extends Mock implements ChangeDetectorRef {}\n\nabstract class CounterEvent {}\n\nclass Increment extends CounterEvent {}\n\nclass Decrement extends CounterEvent {}\n\n// ignore: prefer_file_naming_conventions\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n    on<Decrement>((event, emit) => emit(state - 1));\n  }\n}\n\nvoid main() {\n  group('Stream', () {\n    late Bloc<dynamic, dynamic> bloc;\n    late BlocPipe pipe;\n    late ChangeDetectorRef ref;\n\n    setUp(() {\n      bloc = CounterBloc();\n      ref = MockChangeDetectorRef();\n      pipe = BlocPipe(ref);\n    });\n\n    group('transform', () {\n      test('should return initialState when subscribing to an bloc', () {\n        expect(pipe.transform(bloc), 0);\n      });\n\n      test('should return the latest available value', () async {\n        pipe.transform(bloc);\n        bloc.add(Increment());\n        Timer.run(\n          expectAsync0(() {\n            final dynamic res = pipe.transform(bloc);\n            expect(res, 1);\n          }),\n        );\n      });\n\n      test(\n          'should return same value when nothing has changed '\n          'since the last call', () async {\n        pipe.transform(bloc);\n        bloc.add(Increment());\n        Timer.run(\n          expectAsync0(() {\n            pipe.transform(bloc);\n            expect(pipe.transform(bloc), 1);\n          }),\n        );\n      });\n\n      test(\n          'should dispose of the existing subscription when '\n          'subscribing to a new bloc', () async {\n        pipe.transform(bloc);\n        final newBloc = CounterBloc();\n        expect(pipe.transform(newBloc), 0);\n        // this should not affect the pipe\n        bloc.add(Increment());\n        Timer.run(\n          expectAsync0(() {\n            expect(pipe.transform(newBloc), 0);\n          }),\n        );\n      });\n\n      test('should not dispose of existing subscription when Streams are equal',\n          () async {\n        // See https://github.com/dart-lang/angular2/issues/260\n        final bloc = CounterBloc();\n        expect(pipe.transform(bloc), 0);\n        bloc.add(Increment());\n        Timer.run(\n          expectAsync0(() {\n            expect(pipe.transform(bloc), 1);\n          }),\n        );\n      });\n\n      test('should request a change detection check upon receiving a new value',\n          () async {\n        pipe.transform(bloc);\n        bloc.add(Increment());\n        Timer(\n          const Duration(milliseconds: 10),\n          expectAsync0(() {\n            verify(() => ref.markForCheck()).called(1);\n          }),\n        );\n      });\n    });\n\n    group('ngOnDestroy', () {\n      test('should do nothing when no subscription and not throw exception',\n          () {\n        pipe.ngOnDestroy();\n      });\n\n      test('should dispose of the existing subscription', () async {\n        pipe\n          ..transform(bloc)\n          ..ngOnDestroy();\n        bloc.add(Increment());\n        Timer.run(\n          expectAsync0(() {\n            expect(pipe.transform(bloc), 1);\n          }),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc/CHANGELOG.md",
    "content": "# 9.2.0\n\n- feat: add `MultiBlocObserver` ([#4714](https://github.com/felangel/bloc/pull/4714))\n- docs: minor `README.md` improvements ([#4657](https://github.com/felangel/bloc/pull/4657))\n\n# 9.1.0\n\n- docs: add `onDone` to `README` and `example` ([#4641](https://github.com/felangel/bloc/pull/4641))\n- feat: add `onDone` callback ([#4633](https://github.com/felangel/bloc/pull/4633))\n- refactor: add `pkg:bloc_lint` ([#4620](https://github.com/felangel/bloc/pull/4620))\n\n# 9.0.1\n\n- refactor: analysis options updates ([#4616](https://github.com/felangel/bloc/pull/4616))\n- docs: update build status badge ([#4502](https://github.com/felangel/bloc/pull/4502))\n- docs: update sponsors ([#4418](https://github.com/felangel/bloc/pull/4418))\n- docs: update minimum Dart SDK version in `README.md`\n\n# 9.0.0\n\n- **BREAKING** refactor!: introduce `EmittableStateStreamableSource` ([#4311](https://github.com/felangel/bloc/pull/4311))\n  - `BlocBase<State>` implements `EmittableStateStreamableSource<State>`\n- **BREAKING** refactor!: remove deprecated `BlocOverrides` ([#4137](https://github.com/felangel/bloc/pull/4137))\n- refactor: use `Object.hashAll` internally ([#4310](https://github.com/felangel/bloc/pull/4310))\n  - bumps minimum Dart SDK to 2.14\n- chore: update sponsors\n\n# 8.1.4\n\n- docs: improve diagrams\n- chore: update copyright year\n- chore: update sponsors\n\n# 8.1.3\n\n- chore: update sponsors ([#4054](https://github.com/felangel/bloc/pull/4054))\n- chore: fix `require_trailing_commas` ([#3977](https://github.com/felangel/bloc/pull/3977))\n- chore(deps): upgrade to `package:mocktail v1.0.0` ([#3919](https://github.com/felangel/bloc/pull/3919))\n- chore: add `topics` to `pubspec.yaml` ([#3914](https://github.com/felangel/bloc/pull/3914))\n\n# 8.1.2\n\n- docs: upgrade README snippets to Dart 3 ([#3826](https://github.com/felangel/bloc/pull/3826))\n- refactor: standardize analysis options and resolve warnings ([#3826](https://github.com/felangel/bloc/pull/3826))\n- docs: remove superfluous word from inline docs ([#3734](https://github.com/felangel/bloc/pull/3734))\n\n# 8.1.1\n\n- chore: add screenshots to `pubspec.yaml` ([#3708](https://github.com/felangel/bloc/pull/3708))\n- refactor: `const` constructor support for `BlocObserver` ([#3704](https://github.com/felangel/bloc/pull/3704))\n- refactor: upgrade to Dart 2.19 ([#3699](https://github.com/felangel/bloc/pull/3699))\n  - remove deprecated `invariant_booleans` lint rule\n\n# 8.1.0\n\n- feat: reintroduce `Bloc.observer` and `Bloc.transformer` ([#3469](https://github.com/felangel/bloc/pull/3469))\n  - deprecate: `BlocOverrides`\n- fix: remove unnecessary `async` from `Emitter.onEach` ([#3392](https://github.com/felangel/bloc/pull/3392))\n- chore: upgrade to `mocktail ^0.3.0` ([#3477](https://github.com/felangel/bloc/pull/3477))\n\n# 8.0.3\n\n- refactor: resolve analysis warnings ([#3189](https://github.com/felangel/bloc/pull/3189))\n- docs: fix inline doc comment ([#3167](https://github.com/felangel/bloc/pull/3167))\n- docs: update GetStream utm tags ([#3136](https://github.com/felangel/bloc/pull/3136))\n- docs: update VGV sponsors logo ([#3125](https://github.com/felangel/bloc/pull/3125))\n\n# 8.0.2\n\n- fix: make `onChange` and `addError` protected ([#3071](https://github.com/felangel/bloc/pull/3071))\n- refactor: use `late` keyword for internal state controller ([#3100](https://github.com/felangel/bloc/pull/3100))\n- refactor: add `isClosed` to `Closable` ([#3066](https://github.com/felangel/bloc/pull/3066))\n- refactor: add core interfaces ([#3012](https://github.com/felangel/bloc/pull/3012))\n- refactor: internal reorganization ([#3011](https://github.com/felangel/bloc/pull/3011))\n- docs: update example to follow naming conventions ([#3029](https://github.com/felangel/bloc/pull/3029))\n\n# 8.0.1\n\n- fix: allow `emit` usage within tests ([#2982](https://github.com/felangel/bloc/pull/2982))\n\n# 8.0.0\n\n- **BREAKING**: feat: introduce `BlocOverrides` API ([#2932](https://github.com/felangel/bloc/pull/2932))\n  - `Bloc.observer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.blocObserver`\n  - `Bloc.transformer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.eventTransformer`\n- **BREAKING**: refactor: make `BlocObserver` an abstract class\n- **BREAKING**: feat: `add` throws `StateError` when bloc is closed ([#2912](https://github.com/felangel/bloc/pull/2912))\n- **BREAKING**: feat: `emit` throws `StateError` when bloc is closed ([#2913](https://github.com/felangel/bloc/pull/2913))\n- **BREAKING**: feat: improve error handling/reporting\n  - `BlocUnhandledErrorException` is removed\n  - Uncaught exceptions are always reported to `onError` and rethrown\n  - `addError` reports error to `onError` but does not propagate as an uncaught exception\n- **BREAKING**: feat: restrict scope of `emit` in `Bloc` and `Cubit`\n  - In `Cubit`, `emit` is `protected` so it can only be used within the `Cubit` instance.\n  - In `Bloc`, `emit` is `internal` so it cannot be used outside of the internal package implementation.\n- **BREAKING**: refactor: remove deprecated `TransitionFunction`\n- **BREAKING**: refactor: remove deprecated `transformEvents`\n- **BREAKING**: refactor: remove deprecated `mapEventToState`\n- **BREAKING**: refactor: remove deprecated `transformTransitions`\n- **BREAKING**: refactor: remove deprecated `listen` on `BlocBase`\n- feat: throw `StateError` if an event is added without a registered event handler\n\n# 8.0.0-dev.5\n\n- **BREAKING**: feat: introduce `BlocOverrides` API ([#2932](https://github.com/felangel/bloc/pull/2932))\n  - `Bloc.observer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.blocObserver`\n  - `Bloc.transformer` removed in favor of `BlocOverrides.runZoned` and `BlocOverrides.current.eventTransformer`\n- **BREAKING**: refactor: make `BlocObserver` an abstract class\n- **BREAKING**: feat: `add` throws `StateError` when bloc is closed ([#2912](https://github.com/felangel/bloc/pull/2912))\n- **BREAKING**: feat: `emit` throws `StateError` when bloc is closed ([#2913](https://github.com/felangel/bloc/pull/2913))\n\n# 8.0.0-dev.4\n\n- **BREAKING**: feat: improve error handling/reporting\n  - `BlocUnhandledErrorException` is removed\n  - Uncaught exceptions are always reported to `onError` and rethrown\n  - `addError` reports error to `onError` but does not propagate as an uncaught exception\n\n# 8.0.0-dev.3\n\n- **BREAKING**: feat: restrict scope of `emit` in `Bloc` and `Cubit`\n  - In `Cubit`, `emit` is `protected` so it can only be used within the `Cubit` instance.\n  - In `Bloc`, `emit` is `internal` so it cannot be used outside of the internal package implementation.\n\n# 8.0.0-dev.2\n\n- **BREAKING**: refactor: remove deprecated `listen` on `BlocBase`\n\n# 8.0.0-dev.1\n\n- **BREAKING**: refactor: remove deprecated `TransitionFunction`\n- **BREAKING**: refactor: remove deprecated `transformEvents`\n- **BREAKING**: refactor: remove deprecated `mapEventToState`\n- **BREAKING**: refactor: remove deprecated `transformTransitions`\n- feat: throw `StateError` if an event is added without a registered event handler\n\n# 7.2.1\n\n- fix: `on<E extends Event>` should have an `EventTransformer<E>` instead of `EventTransformer<Event>`\n\n# 7.2.0\n\n- feat: introduce `on<Event>` API to register event handlers\n  - by default events are processed concurrently\n- feat: introduce `Bloc.transformer` API to configure the default `EventTransformer`\n- feat: introduce `Emitter<State>` to trigger state changes\n  - `call` to trigger a state change (alignment with `Cubit`)\n  - `forEach` as an analogue for `await for`\n  - `onEach` to simplify subscription management\n  - `isDone` to abort expensive async operations\n- feat: throw `StateError` if `mapEventToState` is used in conjunction with `on<Event>`\n- feat: throw `StateError` if duplicate event handlers are registered\n- feat: throw `AssertionError` when `emit` is called in a completed `EventHandler`\n- feat: throw `AssertionError` when `emit.onEach` and `emit.forEach` are unawaited\n- **DEPRECATE**: fix: `mapEventToState` deprecated in favor of `on<Event>`\n- **DEPRECATE**: fix: `transformEvents` deprecated in favor of `EventTransformer`\n  - use a built in `EventTransformer` or define your own\n- **DEPRECATE**: fix: `transformTransitions` deprecated\n  - override `Stream<State> get stream` to modify the outbound stream\n\n# 7.2.0-dev.3\n\n- **BREAKING**: refactor: require `emit.forEach` `onData` to be synchronous\n- refactor: minor internal optimizations in `on<Event>` implementation\n\n# 7.2.0-dev.2\n\n- **BREAKING**: refactor!: make `onData` callback in `emit.onEach` and `emit.forEach` named\n- **BREAKING**: feat!: rename `emit.isCanceled` to `emit.isDone` to encapsulate completion and cancelation\n- feat: introduce optional `onError` in `emit.onEach` and `emit.forEach`\n- feat: throw `AssertionError` when `emit` is called in a completed `EventHandler`\n- feat: throw `AssertionError` when `emit.onEach` and `emit.forEach` are unawaited\n- fix: `emit.onEach` and `emit.forEach` error propagation when stream emits an error\n\n# 7.2.0-dev.1\n\n- feat: introduce `on<Event>` API to register event handlers\n  - by default events are processed concurrently\n- feat: introduce `Bloc.transformer` API to configure the default `EventTransformer`\n- feat: introduce `Emitter<State>` to trigger state changes\n  - `call` to trigger a state change (alignment with `Cubit`)\n  - `forEach` as an analogue for `await for`\n  - `onEach` to simplify subscription management\n  - `isCanceled` to abort expensive async operations\n- feat: throw `StateError` if `mapEventToState` is used in conjunction with `on<Event>`\n- feat: throw `StateError` if duplicate event handlers are registered\n- **DEPRECATE**: fix: `mapEventToState` deprecated in favor of `on<Event>`\n- **DEPRECATE**: fix: `transformEvents` deprecated in favor of `EventTransformer`\n  - use a built in `EventTransformer` or define your own\n- **DEPRECATE**: fix: `transformTransitions` deprecated\n  - override `Stream<State> get stream` to modify the outbound stream\n\n# 7.1.0\n\n- feat: expose `isClosed` getter on `BlocBase`\n- refactor: simplify internal event controller initialization\n- docs: update `onChange` description in README\n- docs: update GetStream sponsorship urls\n\n# 7.0.0\n\n- **BREAKING**: refactor: `Bloc` and `Cubit` extend `BlocBase`\n  - refactor: `void onError(Cubit cubit, Object error, StackTrace stackTrace)` -> `void onError(BlocBase bloc, Object error, StackTrace stackTrace)`\n  - refactor: `void onCreate(Cubit cubit)` -> `void onCreate(BlocBase bloc)`\n  - refactor: `void onClose(Cubit cubit)` -> `void onClose(BlocBase bloc)`\n- **BREAKING**: refactor: `Bloc` and `Cubit` do not extend `Stream` and implement `Sink`\n  - refactor: use `bloc.stream` or `cubit.stream` to access `Stream<State>`\n    - `myBloc.map(...)` -> `myBloc.stream.map(...)`\n  - refactor: deprecate `bloc.listen` in favor of `bloc.stream.listen`\n- **BREAKING**: refactor: `CubitUnhandledErrorException` -> `BlocUnhandledErrorException`\n- **BREAKING**: opt into null safety\n  - feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- fix: `transformEvents` multiple subscriptions issue\n- test: improve testing for advanced `transformEvents` behavior\n- chore: bump to `meta: ^1.3.0`\n\n# 7.0.0-nullsafety.4\n\n- **BREAKING**: refactor: `Bloc` and `Cubit` extend `BlocBase`\n  - refactor: `void onError(Bloc bloc, Object error, StackTrace stackTrace)` -> `void onError(BlocBase bloc, Object error, StackTrace stackTrace)`\n  - refactor: `void onCreate(Bloc bloc)` -> `void onCreate(BlocBase bloc)`\n  - refactor: `void onClose(Bloc bloc)` -> `void onClose(BlocBase bloc)`\n- **BREAKING**: refactor: `Bloc` and `Cubit` do not extend `Stream` and implement `Sink`\n  - refactor: use `bloc.stream` or `cubit.stream` to access `Stream<State>`\n    - `myBloc.map(...)` -> `myBloc.stream.map(...)`\n  - refactor: deprecate `bloc.listen` in favor of `bloc.stream.listen`\n- **BREAKING**: revert: refactor: `Change` and `onChange` removed in favor of `Transition` and `onTransition`\n\n# 7.0.0-nullsafety.3\n\n- fix: `transformEvents` multiple subscriptions issue\n- test: improve testing for advanced `transformEvents` behavior\n\n# 7.0.0-nullsafety.2\n\n- chore: bump to `meta: ^1.3.0`\n\n# 7.0.0-nullsafety.1\n\n- **BREAKING**: refactor: `Cubit` extends `Bloc`\n  - refactor: `Change` and `onChange` removed in favor of `Transition` and `onTransition`\n  - refactor: `void onError(Cubit cubit, Object error, StackTrace stackTrace)` -> `void onError(Bloc bloc, Object error, StackTrace stackTrace)`\n  - refactor: `void onCreate(Cubit cubit)` -> `void onCreate(Bloc bloc)`\n  - refactor: `void onClose(Cubit cubit)` -> `void onClose(Bloc bloc)`\n  - refactor: `CubitUnhandledErrorException` -> `BlocUnhandledErrorException`\n\n# 7.0.0-nullsafety.0\n\n- **BREAKING**: opt into null safety\n- feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n\n# 6.1.3\n\n- fix: `transformEvents` multiple subscriptions issue due to `v6.1.2`\n\n# 6.1.2\n\n- fix: bloc memory leak due to internal event stream being a broadcast stream\n\n# 6.1.1\n\n- fix: `close` should always emit done\n\n# 6.1.0\n\n- feat: add `onCreate` and `onClose` to `BlocObserver`\n\n# 6.0.3\n\n- docs: README updates to include flow diagrams for `Bloc` and `Cubit`.\n\n# 6.0.2\n\n- refactor: cubit internal memory and performance optimizations\n\n# 6.0.1\n\n- docs: minor documentation fixes and improvements\n\n# 6.0.0\n\n- **BREAKING**: do not emit current state on subscription\n- **BREAKING**: `onError` in `BlocObserver` takes a `Cubit` as first parameter\n- **BREAKING**: allow blocs and cubits to emit the initial state\n- feat: include `cubit` and remove external dependency on [package:cubit](https://pub.dev/packages/cubit)\n  - exports class `Cubit`\n  - exports class `Change` (`Transition` for `Cubit`)\n- feat: `onChange` added to `BlocObserver`\n- refactor: apply additional lint rules\n- fix: add `@visibleForTesting` to `emit` on class `Cubit`\n- docs: fix inline documentation references\n\n# 6.0.0-dev.2\n\n- fix: add `@visibleForTesting` to `emit` on class `Cubit`\n\n# 6.0.0-dev.1\n\n- **BREAKING**: do not emit current state on subscription\n- **BREAKING**: `onError` in `BlocObserver` takes a `Cubit` as first parameter\n- **BREAKING**: allow blocs and cubits to emit the initial state\n- feat: include `cubit` and remove external dependency on [package:cubit](https://pub.dev/packages/cubit)\n  - exports class `Cubit`\n  - exports class `Change` (`Transition` for `Cubit`)\n- feat: `onChange` added to `BlocObserver`\n- refactor: apply additional lint rules\n- docs: fix inline documentation references\n\n# 5.0.1\n\n- fix: upgrade to `cubit ^0.1.2`\n- docs: minor documentation updates\n\n# 5.0.0\n\n- **BREAKING**: remove `initialState` override in favor of providing the initial state via super ([#1304](https://github.com/felangel/bloc/issues/1304)).\n- **BREAKING**: Remove `BlocSupervisor` and rename `BlocDelegate` to `BlocObserver`.\n- feat: support `null` states ([#1312](https://github.com/felangel/bloc/issues/1312)).\n- refactor: bloc to extend [cubit](https://pub.dev/packages/cubit) rather than `Stream`.\n- feat: ignore newly added events after bloc is closed ([#1236](https://github.com/felangel/bloc/issues/1236)).\n- feat: add `addError` to conform to `EventSink` interface.\n- feat: mark `onError`, `onTransition`, `onEvent` as `protected`.\n- docs: documentation improvements\n- docs: logo updates\n\n# 5.0.0-dev.11\n\n- feat: add `addError` to conform to `EventSink` interface.\n- feat: mark `onError`, `onTransition`, `onEvent` as `protected`.\n\n# 5.0.0-dev.10\n\n- docs: additional minor improvement to bloc logo alignment\n\n# 5.0.0-dev.9\n\n- docs: minor improvement to bloc logo alignment\n\n# 5.0.0-dev.8\n\n- **BREAKING**: Remove `BlocSupervisor` and rename `BlocDelegate` to `BlocObserver`.\n\n# 5.0.0-dev.7\n\n- Ignore newly added events after bloc is closed ([#1236](https://github.com/felangel/bloc/issues/1236)).\n\n# 5.0.0-dev.6\n\n- Additional documentation optimizations.\n\n# 5.0.0-dev.5\n\n- Optimize documentation assets for smaller viewports.\n\n# 5.0.0-dev.4\n\n- Update to [cubit](https://pub.dev/packages/cubit) `^0.0.13`\n- Update documentation and static assets.\n\n# 5.0.0-dev.3\n\n- Update documentation and static assets.\n\n# 5.0.0-dev.2\n\n- **BREAKING**: update `initialState` to be a required positional parameter ([related issue](https://github.com/dart-lang/sdk/issues/42438)).\n\n# 5.0.0-dev.1\n\n- **BREAKING**: remove `initialState` override in favor of providing the initial state via super ([#1304](https://github.com/felangel/bloc/issues/1304)).\n- feat: support `null` states ([#1312](https://github.com/felangel/bloc/issues/1312)).\n- refactor: bloc to extend [cubit](https://pub.dev/packages/cubit) rather than `Stream`.\n\n# 4.0.0\n\n- Remove `rxdart` dependency ([#821](https://github.com/felangel/bloc/pull/821))\n- Replace `transformStates` with `transformTransitions` ([#840](https://github.com/felangel/bloc/pull/840))\n- Fix null `stackTrace` in `onError` ([#963](https://github.com/felangel/bloc/pull/963))\n- Fix remove duplicate terminating state\n- Add `mustCallSuper` to `onEvent`, `onTransition`, and `onError`\n- Surface Unhandled Bloc Errors in Debug Mode\n- Internal testing improvements\n\n# 4.0.0-dev.4\n\n- Surface Unhandled Bloc Errors in Debug Mode\n- Internal testing improvements\n\n# 4.0.0-dev.3\n\n- Add `mustCallSuper` to `onEvent`, `onTransition`, and `onError`\n\n# 4.0.0-dev.2\n\n- Fix remove duplicate terminating state\n\n# 4.0.0-dev.1\n\n- Remove `rxdart` dependency ([#821](https://github.com/felangel/bloc/pull/821))\n- Replace `transformStates` with `transformTransitions` ([#840](https://github.com/felangel/bloc/pull/840))\n- Fix null `stackTrace` in `onError` ([#963](https://github.com/felangel/bloc/pull/963))\n\n# 3.0.0\n\n- Upgrade to `rxdart ^0.23.0`\n- Upgrade to `Dart >= 2.6.0`\n\n# 3.0.0-dev.1\n\n- Upgrade to `rxdart ^0.23.0`\n\n# 2.0.0\n\n- Allow blocs to finish processing pending events on `close` ([#639](https://github.com/felangel/bloc/issues/639))\n- Documentation Updates\n\n# 1.0.1\n\n- Bugfix: Exceptions thrown in `onTransition` are passed to `onError` and should not break bloc functionality ([#641](https://github.com/felangel/bloc/issues/641))\n- Adhere to [effective dart](https://dart.dev/guides/language/effective-dart) ([#561](https://github.com/felangel/bloc/issues/561))\n- Documentation and Example Updates\n\n# 1.0.0\n\n- `dispatch` and `dispose` removed\n- Documentation Updates\n\n# 0.16.1\n\n- Minor Documentation Updates\n\n# 0.16.0\n\n- Bloc extends `Stream<State>` ([#558](https://github.com/felangel/bloc/issues/558))\n  - `bloc.state.listen` -> `bloc.listen`\n  - `bloc.currentState` -> `bloc.state`\n- Bloc implements `Sink<Event>` ([#558](https://github.com/felangel/bloc/issues/558))\n  - `dispatch` deprecated in favor of `add`\n  - `dispose` deprecated in favor of `close`\n- Documentation and Example Updates\n\n# 0.15.0\n\n- Removed Bloc `event` Stream ([#326](https://github.com/felangel/bloc/issues/326))\n- Renamed `transform` to `transformEvents`\n- Added `transformStates` ([#382](https://github.com/felangel/bloc/issues/382))\n\n# 0.14.4\n\nAdditional Dependency and Documentation Updates.\n\n# 0.14.3\n\nDependency and Documentation Updates.\n\n# 0.14.2\n\n- Deprecated Bloc `event` Stream ([#326](https://github.com/felangel/bloc/issues/326))\n- Documentation Updates\n\n# 0.14.1\n\nInternal `BlocDelegate` update and Documentation Updates.\n\n# 0.14.0\n\n`BlocDelegate` initialization improvements and Documentation Updates.\n\n- `BlocSupervisor().delegate = ...` is now `BlocSupervisor.delegate = ...` ([#304](https://github.com/felangel/bloc/issues/304)).\n\n# 0.13.0\n\n`Bloc` and `BlocDelegate` Improvements, new Features, and Documentation Updates.\n\n- Improved `dispose` to ignore pending events ([#257](https://github.com/felangel/bloc/issues/257)).\n- Exposed `event` stream on `Bloc` similar to `state` stream to expose a `Stream` of `dispatched` events ([#259](https://github.com/felangel/bloc/issues/259)).\n- Update to use `rxdart` version `^0.22.0` ([#265](https://github.com/felangel/bloc/issues/265)).\n- `BlocDelegate` methods include a reference to the `Bloc` instance ([#259](https://github.com/felangel/bloc/issues/259)).\n- Added `onEvent` to `Bloc` and `BlocDelegate` ([#259](https://github.com/felangel/bloc/issues/259)).\n\n# 0.12.0\n\nUpdated `transform` to enable advanced event filtering and processing and Documentation Updates.\n\n# 0.11.2\n\nAdded `BlocDelegate` `onError` and `onTransition` mustCallSuper and Documentation Updates\n\n# 0.11.1\n\nAdded `dispose` mustCallSuper and Documentation Updates\n\n# 0.11.0\n\nUpdate `mapEventToState` to remove unnecessary argument for `currentState`\n\n- `Stream<S> mapEventToState(S currentState, E event)` -> `Stream<S> mapEventToState(E event)`\n- Documentation Updates\n- Example Updates\n\n# 0.10.0\n\nUpdated to `rxdart ^0.21.0` and Documentation Updates\n\n# 0.9.5\n\nMinor Enhancements to Code Style and Documentation.\n\n# 0.9.4\n\nCalls to `dispatch` after `dispose` has been called trigger `onError` in the `Bloc` and `BlocDelegate`.\n\n# 0.9.3\n\nRestrict `rxdart` to `\">=0.18.1 <0.21.0\"` due to breaking changes.\n\n# 0.9.2\n\nAdditional Minor Updates to Documentation\n\n# 0.9.1\n\nMinor Updates to Documentation\n\n# 0.9.0\n\n`Bloc` and `BlocDelegate` Error Handling\n\n- Added `onError` to `Bloc` for local error handling.\n- Added `onError` to `BlocDelegate` for global error handling.\n\n# 0.8.4\n\nBlocs handle exceptions thrown in `mapEventToState` and documentation updates.\n\n# 0.8.3\n\nMinor Internal Improvements and Documentation Updates\n\n# 0.8.2\n\nAdditional Minor Updates to Documentation\n\n# 0.8.1\n\nMinor Updates to Documentation\n\n# 0.8.0\n\nBlocs ignore duplicate states\n\n# 0.7.8\n\nAdditional Minor Updates to Documentation\n\n# 0.7.7\n\nAdditional Minor Updates to Documentation\n\n# 0.7.6\n\nMinor Updates to Documentation\n\n# 0.7.5\n\nExposed `currentState` in `Bloc`\n\n- Updates to Documentation.\n\n# 0.7.4\n\nUpdated `mapEventToState` parameter name\n\n- `Stream<S> mapEventToState(S state, E event)` -> `Stream<S> mapEventToState(S currentState, E event)`\n- Updates to Documentation.\n- Updates to Example.\n\n# 0.7.3\n\nMinor Updates to Documentation\n\n# 0.7.2\n\n`Transition` Fix\n\n- `Bloc` with `mapEventToState` which returns multiple states per event will now correctly report the `Transitions`.\n\n# 0.7.1\n\nImprovements to `Bloc` usage in pure Dart applications.\n\n- `Bloc` state is seeded with `initialState` automatically\n\n# 0.7.0\n\nAdded `BlocSupervisor` and `BlocDelegate`.\n\n- `BlocSupervisor` notifies `BlocDelegate` of `Transitions`\n- `BlocDelegate` exposes `onTransition` which is invoked for all `Bloc` `Transitions`.\n\n# 0.6.0\n\n`Transitions` and `initialState` updates.\n\n- Added `Transition`s and `onTransition`\n- Made `initialState` required\n\n# 0.5.2\n\nAdditional minor Updates to Documentation.\n\n# 0.5.1\n\nMinor Updates to Documentation\n\n# 0.5.0\n\nMoved Flutter Widgets to flutter_bloc package\n\n# 0.4.2\n\nAdditional minor Updates to Documentation.\n\n# 0.4.1\n\nMinor Updates to Documentation.\n\n# 0.4.0\n\nAdded `BlocProvider`.\n\n- `BlocProvider.of(context)`\n- Updates to Documentation.\n- Updates to Example.\n\n# 0.3.0\n\nUpdated `mapEventToState` to take current state as an argument.\n\n- `Stream<S> mapEventToState(E event)` -> `Stream<S> mapEventToState(S state, E event)`\n- Updates to Documentation.\n- Updates to Example.\n\n# 0.2.5\n\nAdditional Minor Updates to Documentation.\n\n# 0.2.4\n\nAdditional Minor Updates to Documentation.\n\n# 0.2.3\n\nAdditional Minor Updates to Documentation.\n\n# 0.2.2\n\nAdditional Minor Updates to Documentation.\n\n# 0.2.1\n\nMinor Updates to Documentation.\n\n# 0.2.0\n\nAdded Support for Stream Transformation\n\n- Includes `Stream<E> transform(Stream<E> events)`\n- Updates to Documentation\n\n# 0.1.2\n\nAdditional Minor Updates to Documentation.\n\n# 0.1.1\n\nMinor Updates to Documentation.\n\n# 0.1.0\n\nInitial Version of the library.\n\n- Includes the ability to create a custom Bloc by extending `Bloc` class.\n- Includes the ability to connect presentation layer to `Bloc` by using the `BlocBuilder` Widget.\n"
  },
  {
    "path": "packages/bloc/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "packages/bloc/README.md",
    "content": "<p align=\"right\">\n<a href=\"https://flutter.dev/docs/development/packages-and-plugins/favorites\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/pub/flutter_favorite.png\" width=\"100\" alt=\"build\"></a>\n</p>\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc.png\" height=\"100\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/bloc\"><img src=\"https://img.shields.io/pub/v/bloc.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nA predictable state management library that helps implement the BLoC (Business Logic Component) design pattern.\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\nThis package is built to work with:\n\n- [flutter_bloc](https://pub.dev/packages/flutter_bloc)\n- [angular_bloc](https://pub.dev/packages/angular_bloc)\n- [bloc_concurrency](https://pub.dev/packages/bloc_concurrency)\n- [bloc_test](https://pub.dev/packages/bloc_test)\n- [hydrated_bloc](https://pub.dev/packages/hydrated_bloc)\n- [replay_bloc](https://pub.dev/packages/replay_bloc)\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Overview\n\nThe goal of this package is to make it easy to implement the `BLoC` Design Pattern (Business Logic Component).\n\nThis design pattern helps to separate _presentation_ from _business logic_. Following the BLoC pattern facilitates testability and reusability. This package abstracts reactive aspects of the pattern allowing developers to focus on writing the business logic.\n\n### Cubit\n\n![Cubit Architecture](https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams/cubit_architecture_full.png)\n\nA `Cubit` is a class which extends `BlocBase` and can be extended to manage any type of state. `Cubit` requires an initial state which will be the state before `emit` has been called. The current state of a `cubit` can be accessed via the `state` getter and the state of the `cubit` can be updated by calling `emit` with a new `state`.\n\n![Cubit Flow](https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams//cubit_flow.png)\n\nState changes in cubit begin with predefined function calls which can use the `emit` method to output new states. `onChange` is called right before a state change occurs and contains the current and next state.\n\n#### Creating a Cubit\n\n```dart\n/// A `CounterCubit` which manages an `int` as its state.\nclass CounterCubit extends Cubit<int> {\n  /// The initial state of the `CounterCubit` is 0.\n  CounterCubit() : super(0);\n\n  /// When increment is called, the current state\n  /// of the cubit is accessed via `state` and\n  /// a new `state` is emitted via `emit`.\n  void increment() => emit(state + 1);\n}\n```\n\n#### Using a Cubit\n\n```dart\nvoid main() {\n  /// Create a `CounterCubit` instance.\n  final cubit = CounterCubit();\n\n  /// Access the state of the `cubit` via `state`.\n  print(cubit.state); // 0\n\n  /// Interact with the `cubit` to trigger `state` changes.\n  cubit.increment();\n\n  /// Access the new `state`.\n  print(cubit.state); // 1\n\n  /// Close the `cubit` when it is no longer needed.\n  cubit.close();\n}\n```\n\n#### Observing a Cubit\n\n`onChange` can be overridden to observe state changes for a single `cubit`.\n\n`onError` can be overridden to observe errors for a single `cubit`.\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    print('$error, $stackTrace');\n    super.onError(error, stackTrace);\n  }\n}\n```\n\n`BlocObserver` can be used to observe all `cubits`.\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {\n    super.onCreate(bloc);\n    print('onCreate -- ${bloc.runtimeType}');\n  }\n\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('onChange -- ${bloc.runtimeType}, $change');\n  }\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {\n    print('onError -- ${bloc.runtimeType}, $error');\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onClose(BlocBase bloc) {\n    super.onClose(bloc);\n    print('onClose -- ${bloc.runtimeType}');\n  }\n}\n```\n\n```dart\nvoid main() {\n  Bloc.observer = MyBlocObserver();\n  // Use cubits...\n}\n```\n\nTo register multiple `BlocObserver` instances, use `MultiBlocObserver`:\n\n```dart\nBloc.observer = MultiBlocObserver(\n  observers: [\n    MyLoggingObserver(),\n    MyErrorObserver(),\n    MyPerformanceObserver(),\n  ],\n);\n```\n\n### Bloc\n\n![Bloc Architecture](https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams/bloc_architecture_full.png)\n\nA `Bloc` is a more advanced class which relies on `events` to trigger `state` changes rather than functions. `Bloc` also extends `BlocBase` which means it has a similar public API as `Cubit`. However, rather than calling a `function` on a `Bloc` and directly emitting a new `state`, `Blocs` receive `events` and convert the incoming `events` into outgoing `states`.\n\n![Bloc Flow](https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams/bloc_flow.png)\n\nState changes in bloc begin when events are added which trigger `onEvent`. The events are then funnelled through an `EventTransformer`. By default, each event is processed concurrently but a custom `EventTransformer` can be provided to manipulate the incoming event stream. All registered `EventHandlers` for that event type are then invoked with the incoming event. Each `EventHandler` is responsible for emitting zero or more states in response to the event. Lastly, `onTransition` is called just before the state is updated and contains the current state, event, and next state.\n\n#### Creating a Bloc\n\n```dart\n/// The events which `CounterBloc` will react to.\nsealed class CounterEvent {}\n\n/// Notifies bloc to increment state.\nfinal class CounterIncrementPressed extends CounterEvent {}\n\n/// A `CounterBloc` which handles converting `CounterEvent`s into `int`s.\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  /// The initial state of the `CounterBloc` is 0.\n  CounterBloc() : super(0) {\n    /// When a `CounterIncrementPressed` event is added,\n    /// the current `state` of the bloc is accessed via the `state` property\n    /// and a new state is emitted via `emit`.\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n}\n```\n\n#### Using a Bloc\n\n```dart\nFuture<void> main() async {\n  /// Create a `CounterBloc` instance.\n  final bloc = CounterBloc();\n\n  /// Access the state of the `bloc` via `state`.\n  print(bloc.state); // 0\n\n  /// Interact with the `bloc` to trigger `state` changes.\n  bloc.add(CounterIncrementPressed());\n\n  /// Wait for next iteration of the event-loop\n  /// to ensure event has been processed.\n  await Future.delayed(Duration.zero);\n\n  /// Access the new `state`.\n  print(bloc.state); // 1\n\n  /// Close the `bloc` when it is no longer needed.\n  await bloc.close();\n}\n```\n\n#### Observing a Bloc\n\nSince all `Blocs` extend `BlocBase` just like `Cubit`, `onChange` and `onError` can be overridden in a `Bloc` as well.\n\nIn addition, `Blocs` can also override `onEvent`, `onTransition`, and `onDone`.\n\n`onEvent` is called any time a new `event` is added to the `Bloc`.\n\n`onTransition` is similar to `onChange`, however, it contains the `event` which triggered the state change in addition to the `currentState` and `nextState`.\n\n`onDone` is called whenever the event handler for a given `event` has completed.\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n\n  @override\n  void onEvent(CounterEvent event) {\n    super.onEvent(event);\n    print(event);\n  }\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    print(change);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    print(transition);\n  }\n\n  @override\n  void onDone(CounterEvent event, [Object? error, StackTrace? stackTrace]) {\n    super.onDone(event, error, stackTrace);\n    print('$event, $error');\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    print('$error, $stackTrace');\n    super.onError(error, stackTrace);\n  }\n}\n```\n\n`BlocObserver` can be used to observe all `blocs` as well.\n\n```dart\nclass MyBlocObserver extends BlocObserver {\n  @override\n  void onCreate(BlocBase bloc) {\n    super.onCreate(bloc);\n    print('onCreate -- ${bloc.runtimeType}');\n  }\n\n  @override\n  void onEvent(Bloc bloc, Object? event) {\n    super.onEvent(bloc, event);\n    print('onEvent -- ${bloc.runtimeType}, $event');\n  }\n\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print('onChange -- ${bloc.runtimeType}, $change');\n  }\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {\n    super.onTransition(bloc, transition);\n    print('onTransition -- ${bloc.runtimeType}, $transition');\n  }\n\n  @override\n  void onDone(Bloc bloc, Object? event, [Object? error, StackTrace? stackTrace]) {\n    super.onDone(bloc, event, error, stackTrace);\n    print('onDone -- ${bloc.runtimeType}, $event, $error');\n  }\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {\n    print('onError -- ${bloc.runtimeType}, $error');\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onClose(BlocBase bloc) {\n    super.onClose(bloc);\n    print('onClose -- ${bloc.runtimeType}');\n  }\n}\n```\n\n```dart\nvoid main() {\n  Bloc.observer = MyBlocObserver();\n  // Use blocs...\n}\n```\n\nJust as with `Cubit`, to register multiple `BlocObserver` instances use `MultiBlocObserver`:\n\n```dart\nBloc.observer = MultiBlocObserver(\n  observers: [\n    MyLoggingObserver(),\n    MyErrorObserver(),\n    MyPerformanceObserver(),\n  ],\n);\n```\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Examples\n\n- [Counter](https://github.com/felangel/bloc/tree/master/packages/bloc/example) - an example of how to create a `CounterBloc` in a pure Dart app.\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/bloc/analysis_options.yaml",
    "content": "include: \n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml"
  },
  {
    "path": "packages/bloc/example/main.dart",
    "content": "// ignore_for_file: avoid_print\n\nimport 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nclass SimpleBlocObserver extends BlocObserver {\n  const SimpleBlocObserver();\n\n  @override\n  void onCreate(BlocBase<dynamic> bloc) {\n    super.onCreate(bloc);\n    print('onCreate -- bloc: ${bloc.runtimeType}');\n  }\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    super.onEvent(bloc, event);\n    print('onEvent -- bloc: ${bloc.runtimeType}, event: $event');\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    print('onChange -- bloc: ${bloc.runtimeType}, change: $change');\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    print('onTransition -- bloc: ${bloc.runtimeType}, transition: $transition');\n  }\n\n  @override\n  void onDone(\n    Bloc<dynamic, dynamic> bloc,\n    Object? event, [\n    Object? error,\n    StackTrace? stackTrace,\n  ]) {\n    super.onDone(bloc, event, error, stackTrace);\n    print('onDone -- bloc: ${bloc.runtimeType}, event: $event, error: $error');\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    print('onError -- bloc: ${bloc.runtimeType}, error: $error');\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onClose(BlocBase<dynamic> bloc) {\n    super.onClose(bloc);\n    print('onClose -- bloc: ${bloc.runtimeType}');\n  }\n}\n\nvoid main() {\n  Bloc.observer = const SimpleBlocObserver();\n  cubitMain();\n  blocMain();\n}\n\nvoid cubitMain() {\n  print('----------CUBIT----------');\n\n  /// Create a `CounterCubit` instance.\n  final cubit = CounterCubit();\n\n  /// Access the state of the `cubit` via `state`.\n  print(cubit.state); // 0\n\n  /// Interact with the `cubit` to trigger `state` changes.\n  cubit.increment();\n\n  /// Access the new `state`.\n  print(cubit.state); // 1\n\n  /// Close the `cubit` when it is no longer needed.\n  cubit.close();\n}\n\nFuture<void> blocMain() async {\n  print('----------BLOC----------');\n\n  /// Create a `CounterBloc` instance.\n  final bloc = CounterBloc();\n\n  /// Access the state of the `bloc` via `state`.\n  print(bloc.state);\n\n  /// Interact with the `bloc` to trigger `state` changes.\n  bloc.add(CounterIncrementPressed());\n\n  /// Wait for next iteration of the event-loop\n  /// to ensure event has been processed.\n  await Future<void>.delayed(Duration.zero);\n\n  /// Access the new `state`.\n  print(bloc.state);\n\n  /// Close the `bloc` when it is no longer needed.\n  await bloc.close();\n}\n\n/// A `CounterCubit` which manages an `int` as its state.\nclass CounterCubit extends Cubit<int> {\n  /// The initial state of the `CounterCubit` is 0.\n  CounterCubit() : super(0);\n\n  /// When increment is called, the current state\n  /// of the cubit is accessed via `state` and\n  /// a new `state` is emitted via `emit`.\n  void increment() => emit(state + 1);\n}\n\n/// The events which `CounterBloc` will react to.\nabstract class CounterEvent {}\n\n/// Notifies bloc to increment state.\nclass CounterIncrementPressed extends CounterEvent {}\n\n/// A `CounterBloc` which handles converting `CounterEvent`s into `int`s.\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  /// The initial state of the `CounterBloc` is 0.\n  CounterBloc() : super(0) {\n    /// When a `CounterIncrementPressed` event is added,\n    /// the current `state` of the bloc is accessed via the `state` property\n    /// and a new state is emitted via `emit`.\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n}\n"
  },
  {
    "path": "packages/bloc/lib/bloc.dart",
    "content": "/// A predictable state management library for [Dart](https://dart.dev).\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary bloc;\n\nexport 'src/bloc.dart';\nexport 'src/bloc_observer.dart';\nexport 'src/change.dart';\nexport 'src/cubit.dart';\nexport 'src/transition.dart';\n"
  },
  {
    "path": "packages/bloc/lib/src/bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart 'bloc_base.dart';\npart 'emitter.dart';\n\n/// An [ErrorSink] that supports adding events.\n///\n/// Multiple events can be reported to the sink via `add`.\nabstract class BlocEventSink<Event extends Object?> implements ErrorSink {\n  /// Adds an [event] to the sink.\n  ///\n  /// Must not be called on a closed sink.\n  void add(Event event);\n}\n\n/// An event handler is responsible for reacting to an incoming [Event]\n/// and can emit zero or more states via the [Emitter].\ntypedef EventHandler<Event, State> = FutureOr<void> Function(\n  Event event,\n  Emitter<State> emit,\n);\n\n/// Signature for a function which converts an incoming event\n/// into an outbound stream of events.\n/// Used when defining custom [EventTransformer]s.\ntypedef EventMapper<Event> = Stream<Event> Function(Event event);\n\n/// Used to change how events are processed.\n/// By default events are processed concurrently.\ntypedef EventTransformer<Event> = Stream<Event> Function(\n  Stream<Event> events,\n  EventMapper<Event> mapper,\n);\n\n/// {@template bloc}\n/// Takes a `Stream` of `Events` as input\n/// and transforms them into a `Stream` of `States` as output.\n/// {@endtemplate}\nabstract class Bloc<Event, State> extends BlocBase<State>\n    implements BlocEventSink<Event> {\n  /// {@macro bloc}\n  Bloc(State initialState) : super(initialState);\n\n  /// The current [BlocObserver] instance.\n  static BlocObserver observer = const _DefaultBlocObserver();\n\n  /// The default [EventTransformer] used for all event handlers.\n  /// By default all events are processed concurrently.\n  ///\n  /// If a custom transformer is specified for a particular event handler,\n  /// it will take precendence over the global transformer.\n  ///\n  /// See also:\n  ///\n  /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an\n  /// opinionated set of event transformers.\n  ///\n  static EventTransformer<dynamic> transformer = (events, mapper) {\n    return events\n        .map(mapper)\n        .transform<dynamic>(const _FlatMapStreamTransformer<dynamic>());\n  };\n\n  final _eventController = StreamController<Event>.broadcast();\n  final _subscriptions = <StreamSubscription<dynamic>>[];\n  final _handlers = <_Handler>[];\n  final _emitters = <_Emitter<dynamic>>[];\n  final _eventTransformer = Bloc.transformer;\n\n  /// Notifies the [Bloc] of a new [event] which triggers\n  /// all corresponding [EventHandler] instances.\n  ///\n  /// * A [StateError] will be thrown if there is no event handler\n  /// registered for the incoming [event].\n  ///\n  /// * A [StateError] will be thrown if the bloc is closed and the\n  /// [event] will not be processed.\n  @override\n  void add(Event event) {\n    // ignore: prefer_asserts_with_message\n    assert(() {\n      final handlerExists = _handlers.any((handler) => handler.isType(event));\n      if (!handlerExists) {\n        final eventType = event.runtimeType;\n        throw StateError(\n          '''add($eventType) was called without a registered event handler.\\n'''\n          '''Make sure to register a handler via on<$eventType>((event, emit) {...})''',\n        );\n      }\n      return true;\n    }());\n    try {\n      onEvent(event);\n      _eventController.add(event);\n    } catch (error, stackTrace) {\n      onError(error, stackTrace);\n      rethrow;\n    }\n  }\n\n  /// Called whenever an [event] is [add]ed to the [Bloc].\n  /// A great spot to add logging/analytics at the individual [Bloc] level.\n  ///\n  /// **Note: `super.onEvent` should always be called first.**\n  /// ```dart\n  /// @override\n  /// void onEvent(Event event) {\n  ///   // Always call super.onEvent with the current event\n  ///   super.onEvent(event);\n  ///\n  ///   // Custom onEvent logic goes here\n  /// }\n  /// ```\n  ///\n  /// See also:\n  ///\n  /// * [BlocObserver.onEvent] for observing events globally.\n  ///\n  @protected\n  @mustCallSuper\n  void onEvent(Event event) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onEvent(this, event);\n  }\n\n  /// {@template emit}\n  /// **[emit] is only for internal use and should never be called directly\n  /// outside of tests. The [Emitter] instance provided to each [EventHandler]\n  /// should be used instead.**\n  ///\n  /// ```dart\n  /// class MyBloc extends Bloc<MyEvent, MyState> {\n  ///   MyBloc() : super(MyInitialState()) {\n  ///     on<MyEvent>((event, emit) {\n  ///       // use `emit` to update the state.\n  ///       emit(MyOtherState());\n  ///     });\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// Updates the state of the bloc to the provided [state].\n  /// A bloc's state should only be updated by `emitting` a new `state`\n  /// from an [EventHandler] in response to an incoming event.\n  /// {@endtemplate}\n  @visibleForTesting\n  @override\n  void emit(State state) => super.emit(state);\n\n  /// Register event handler for an event of type `E`.\n  /// There should only ever be one event handler per event type `E`.\n  ///\n  /// ```dart\n  /// abstract class CounterEvent {}\n  /// class CounterIncrementPressed extends CounterEvent {}\n  ///\n  /// class CounterBloc extends Bloc<CounterEvent, int> {\n  ///   CounterBloc() : super(0) {\n  ///     on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  ///   }\n  /// }\n  /// ```\n  ///\n  /// * A [StateError] will be thrown if there are multiple event handlers\n  /// registered for the same type `E`.\n  ///\n  /// By default, events will be processed concurrently.\n  ///\n  /// See also:\n  ///\n  /// * [EventTransformer] to customize how events are processed.\n  /// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an\n  /// opinionated set of event transformers.\n  ///\n  void on<E extends Event>(\n    EventHandler<E, State> handler, {\n    EventTransformer<E>? transformer,\n  }) {\n    // ignore: prefer_asserts_with_message\n    assert(() {\n      final handlerExists = _handlers.any((handler) => handler.type == E);\n      if (handlerExists) {\n        throw StateError(\n          'on<$E> was called multiple times. '\n          'There should only be a single event handler per event type.',\n        );\n      }\n      _handlers.add(_Handler(isType: (dynamic e) => e is E, type: E));\n      return true;\n    }());\n\n    final subscription = (transformer ?? _eventTransformer)(\n      _eventController.stream.where((event) => event is E).cast<E>(),\n      (dynamic event) {\n        void onEmit(State state) {\n          if (isClosed) return;\n          if (this.state == state && _emitted) return;\n          onTransition(\n            Transition(\n              currentState: this.state,\n              event: event as E,\n              nextState: state,\n            ),\n          );\n          emit(state);\n        }\n\n        final emitter = _Emitter(onEmit);\n        final controller = StreamController<E>.broadcast(\n          sync: true,\n          onCancel: emitter.cancel,\n        );\n\n        Future<void> handleEvent() async {\n          void tearDown() {\n            emitter.complete();\n            _emitters.remove(emitter);\n            if (!controller.isClosed) controller.close();\n          }\n\n          try {\n            _emitters.add(emitter);\n            await handler(event as E, emitter);\n            onDone(event);\n          } catch (error, stackTrace) {\n            onError(error, stackTrace);\n            onDone(event as E, error, stackTrace);\n            rethrow;\n          } finally {\n            tearDown();\n          }\n        }\n\n        handleEvent();\n        return controller.stream;\n      },\n    ).listen(null);\n    _subscriptions.add(subscription);\n  }\n\n  /// Called whenever a [transition] occurs with the given [transition].\n  /// A [transition] occurs when a new `event` is added\n  /// and a new state is `emitted` from a corresponding [EventHandler].\n  ///\n  /// [onTransition] is called before a [Bloc]'s [state] has been updated.\n  /// A great spot to add logging/analytics at the individual [Bloc] level.\n  ///\n  /// **Note: `super.onTransition` should always be called first.**\n  /// ```dart\n  /// @override\n  /// void onTransition(Transition<Event, State> transition) {\n  ///   // Always call super.onTransition with the current transition\n  ///   super.onTransition(transition);\n  ///\n  ///   // Custom onTransition logic goes here\n  /// }\n  /// ```\n  ///\n  /// See also:\n  ///\n  /// * [BlocObserver.onTransition] for observing transitions globally.\n  ///\n  @protected\n  @mustCallSuper\n  void onTransition(Transition<Event, State> transition) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onTransition(this, transition);\n  }\n\n  /// Called whenever an [event] handler for a specific [Bloc] has completed.\n  /// This may include an [error] and [stackTrace] if an uncaught exception\n  /// occurred within the event handler.\n  ///\n  /// [onDone] is called right after the event handler has completed.\n  /// A great spot to add logging/analytics at the individual [Bloc] level.\n  ///\n  /// **Note: `super.onDone` should always be called first.**\n  /// ```dart\n  /// @override\n  /// void onDone(Event event, [Object? error, StackTrace? stackTrace]) {\n  ///   // Always call super.onDone with the respective event.\n  ///   super.onDone(event, error, stackTrace);\n  ///\n  ///   // Custom onDone logic goes here\n  /// }\n  /// ```\n  ///\n  /// See also:\n  ///\n  /// * [BlocObserver.onDone] for observing event handler completions globally.\n  ///\n  @protected\n  @mustCallSuper\n  void onDone(Event event, [Object? error, StackTrace? stackTrace]) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onDone(this, event, error, stackTrace);\n  }\n\n  /// Closes the `event` and `state` `Streams`.\n  /// This method should be called when a [Bloc] is no longer needed.\n  /// Once [close] is called, `events` that are [add]ed will not be\n  /// processed.\n  /// In addition, if [close] is called while `events` are still being\n  /// processed, the [Bloc] will finish processing the pending `events`.\n  @mustCallSuper\n  @override\n  Future<void> close() async {\n    await _eventController.close();\n    for (final emitter in _emitters) {\n      emitter.cancel();\n    }\n    await Future.wait<void>(_emitters.map((e) => e.future));\n    await Future.wait<void>(_subscriptions.map((s) => s.cancel()));\n    return super.close();\n  }\n}\n\nclass _Handler {\n  const _Handler({required this.isType, required this.type});\n  final bool Function(dynamic value) isType;\n  final Type type;\n}\n\nclass _DefaultBlocObserver extends BlocObserver {\n  const _DefaultBlocObserver();\n}\n\nclass _FlatMapStreamTransformer<T> extends StreamTransformerBase<Stream<T>, T> {\n  const _FlatMapStreamTransformer();\n\n  @override\n  Stream<T> bind(Stream<Stream<T>> stream) {\n    final controller = StreamController<T>.broadcast(sync: true);\n\n    controller.onListen = () {\n      final subscriptions = <StreamSubscription<dynamic>>[];\n\n      final outerSubscription = stream.listen(\n        (inner) {\n          final subscription = inner.listen(\n            controller.add,\n            onError: controller.addError,\n          );\n\n          subscription.onDone(() {\n            subscriptions.remove(subscription);\n            if (subscriptions.isEmpty) controller.close();\n          });\n\n          subscriptions.add(subscription);\n        },\n        onError: controller.addError,\n      );\n\n      outerSubscription.onDone(() {\n        subscriptions.remove(outerSubscription);\n        if (subscriptions.isEmpty) controller.close();\n      });\n\n      subscriptions.add(outerSubscription);\n\n      controller.onCancel = () {\n        if (subscriptions.isEmpty) return null;\n        final cancels = [for (final s in subscriptions) s.cancel()];\n        return Future.wait(cancels).then((_) {});\n      };\n    };\n\n    return controller.stream;\n  }\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/bloc_base.dart",
    "content": "part of 'bloc.dart';\n\n/// An object that provides access to a stream of states over time.\nabstract class Streamable<State extends Object?> {\n  /// The current [stream] of states.\n  Stream<State> get stream;\n}\n\n/// A [Streamable] that provides synchronous access to the current [state].\nabstract class StateStreamable<State> implements Streamable<State> {\n  /// The current [state].\n  State get state;\n}\n\n/// A [StateStreamable] that must be closed when no longer in use.\nabstract class StateStreamableSource<State>\n    implements StateStreamable<State>, Closable {}\n\n/// An object that must be closed when no longer in use.\nabstract class Closable {\n  /// Closes the current instance.\n  /// The returned future completes when the instance has been closed.\n  FutureOr<void> close();\n\n  /// Whether the object is closed.\n  ///\n  /// An object is considered closed once [close] is called.\n  bool get isClosed;\n}\n\n/// An object that can emit new states.\n// ignore: one_member_abstracts\nabstract class Emittable<State extends Object?> {\n  /// Emits a new [state].\n  void emit(State state);\n}\n\n/// A generic destination for errors.\n///\n/// Multiple errors can be reported to the sink via `addError`.\nabstract class ErrorSink implements Closable {\n  /// Adds an [error] to the sink with an optional [stackTrace].\n  ///\n  /// Must not be called on a closed sink.\n  void addError(Object error, [StackTrace? stackTrace]);\n}\n\n/// A [StateStreamableSource] that can emit new states.\nabstract class EmittableStateStreamableSource<State>\n    implements StateStreamableSource<State>, Emittable<State> {}\n\n/// {@template bloc_base}\n/// An interface for the core functionality implemented by\n/// both [Bloc] and [Cubit].\n/// {@endtemplate}\nabstract class BlocBase<State>\n    implements EmittableStateStreamableSource<State>, ErrorSink {\n  /// {@macro bloc_base}\n  BlocBase(this._state) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onCreate(this);\n  }\n\n  final _blocObserver = Bloc.observer;\n\n  late final _stateController = StreamController<State>.broadcast();\n\n  State _state;\n\n  bool _emitted = false;\n\n  @override\n  State get state => _state;\n\n  @override\n  Stream<State> get stream => _stateController.stream;\n\n  /// Whether the bloc is closed.\n  ///\n  /// A bloc is considered closed once [close] is called.\n  /// Subsequent state changes cannot occur within a closed bloc.\n  @override\n  bool get isClosed => _stateController.isClosed;\n\n  /// Updates the [state] to the provided [state].\n  /// [emit] does nothing if the [state] being emitted\n  /// is equal to the current [state].\n  ///\n  /// To allow for the possibility of notifying listeners of the initial state,\n  /// emitting a state which is equal to the initial state is allowed as long\n  /// as it is the first thing emitted by the instance.\n  ///\n  /// * Throws a [StateError] if the bloc is closed.\n  @protected\n  @visibleForTesting\n  @override\n  void emit(State state) {\n    try {\n      if (isClosed) {\n        throw StateError('Cannot emit new states after calling close');\n      }\n      if (state == _state && _emitted) return;\n      onChange(Change<State>(currentState: this.state, nextState: state));\n      _state = state;\n      _stateController.add(_state);\n      _emitted = true;\n    } catch (error, stackTrace) {\n      onError(error, stackTrace);\n      rethrow;\n    }\n  }\n\n  /// Called whenever a [change] occurs with the given [change].\n  /// A [change] occurs when a new `state` is emitted.\n  /// [onChange] is called before the `state` of the `cubit` is updated.\n  /// [onChange] is a great spot to add logging/analytics for a specific `cubit`.\n  ///\n  /// **Note: `super.onChange` should always be called first.**\n  /// ```dart\n  /// @override\n  /// void onChange(Change change) {\n  ///   // Always call super.onChange with the current change\n  ///   super.onChange(change);\n  ///\n  ///   // Custom onChange logic goes here\n  /// }\n  /// ```\n  ///\n  /// See also:\n  ///\n  /// * [BlocObserver] for observing [Cubit] behavior globally.\n  ///\n  @protected\n  @mustCallSuper\n  void onChange(Change<State> change) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onChange(this, change);\n  }\n\n  /// Reports an [error] which triggers [onError] with an optional [StackTrace].\n  @protected\n  @mustCallSuper\n  @override\n  void addError(Object error, [StackTrace? stackTrace]) {\n    onError(error, stackTrace ?? StackTrace.current);\n  }\n\n  /// Called whenever an [error] occurs and notifies [BlocObserver.onError].\n  ///\n  /// **Note: `super.onError` should always be called last.**\n  ///\n  /// ```dart\n  /// @override\n  /// void onError(Object error, StackTrace stackTrace) {\n  ///   // Custom onError logic goes here\n  ///\n  ///   // Always call super.onError with the current error and stackTrace\n  ///   super.onError(error, stackTrace);\n  /// }\n  /// ```\n  @protected\n  @mustCallSuper\n  void onError(Object error, StackTrace stackTrace) {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onError(this, error, stackTrace);\n  }\n\n  /// Closes the instance.\n  /// This method should be called when the instance is no longer needed.\n  /// Once [close] is called, the instance can no longer be used.\n  @mustCallSuper\n  @override\n  Future<void> close() async {\n    // ignore: invalid_use_of_protected_member\n    _blocObserver.onClose(this);\n    await _stateController.close();\n  }\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/bloc_observer.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\n/// {@template bloc_observer}\n/// An interface for observing the behavior of [Bloc] instances.\n/// {@endtemplate}\nabstract class BlocObserver {\n  /// {@macro bloc_observer}\n  const BlocObserver();\n\n  /// Called whenever a [Bloc] is instantiated.\n  /// In many cases, a cubit may be lazily instantiated and\n  /// [onCreate] can be used to observe exactly when the cubit\n  /// instance is created.\n  @protected\n  @mustCallSuper\n  void onCreate(BlocBase<dynamic> bloc) {}\n\n  /// Called whenever an [event] is `added` to any [bloc] with the given [bloc]\n  /// and [event].\n  @protected\n  @mustCallSuper\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {}\n\n  /// Called whenever a [Change] occurs in any [bloc]\n  /// A [change] occurs when a new state is emitted.\n  /// [onChange] is called before a bloc's state has been updated.\n  @protected\n  @mustCallSuper\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {}\n\n  /// Called whenever a transition occurs in any [bloc] with the given [bloc]\n  /// and [transition].\n  /// A [transition] occurs when a new `event` is added\n  /// and a new state is `emitted` from a corresponding [EventHandler].\n  /// [onTransition] is called before a [bloc]'s state has been updated.\n  @protected\n  @mustCallSuper\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {}\n\n  /// Called whenever an [error] is thrown in any [Bloc] or [Cubit].\n  /// The [stackTrace] argument may be [StackTrace.empty] if an error\n  /// was received without a stack trace.\n  @protected\n  @mustCallSuper\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {}\n\n  /// Called whenever an [event] handler for a specific [bloc] has completed.\n  /// This may include an [error] and [stackTrace] if an uncaught exception\n  /// occurred within the event handler.\n  @protected\n  @mustCallSuper\n  void onDone(\n    Bloc<dynamic, dynamic> bloc,\n    Object? event, [\n    Object? error,\n    StackTrace? stackTrace,\n  ]) {}\n\n  /// Called whenever a [Bloc] is closed.\n  /// [onClose] is called just before the [Bloc] is closed\n  /// and indicates that the particular instance will no longer\n  /// emit new states.\n  @protected\n  @mustCallSuper\n  void onClose(BlocBase<dynamic> bloc) {}\n}\n\n/// {@template multi_bloc_observer}\n/// A [BlocObserver] which supports registering multiple [BlocObserver]\n/// instances. This is useful when maintaining multiple [BlocObserver] instances\n/// for different functions e.g. `LoggingBlocObserver`,\n/// `ErrorReportingBlocObserver`.\n///\n/// ```dart\n/// Bloc.observer = MultiBlocObserver(\n///   observers: [\n///     LoggingObserver(),\n///     ErrorObserver(),\n///     PerformanceObserver(),\n///   ],\n/// );\n/// ```\n/// {@endtemplate}\nclass MultiBlocObserver implements BlocObserver {\n  /// {@macro multi_bloc_observer}\n  const MultiBlocObserver({required this.observers});\n\n  /// The list of [BlocObserver] instances that will be registered. Observers\n  /// are notified of all lifecycle events in the same order that they are\n  /// specified.\n  final List<BlocObserver> observers;\n\n  @override\n  void onCreate(BlocBase<dynamic> bloc) {\n    for (final observer in observers) {\n      observer.onCreate(bloc);\n    }\n  }\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    for (final observer in observers) {\n      observer.onEvent(bloc, event);\n    }\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    for (final observer in observers) {\n      observer.onChange(bloc, change);\n    }\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    for (final observer in observers) {\n      observer.onTransition(bloc, transition);\n    }\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    for (final observer in observers) {\n      observer.onError(bloc, error, stackTrace);\n    }\n  }\n\n  @override\n  void onDone(\n    Bloc<dynamic, dynamic> bloc,\n    Object? event, [\n    Object? error,\n    StackTrace? stackTrace,\n  ]) {\n    for (final observer in observers) {\n      observer.onDone(bloc, event, error, stackTrace);\n    }\n  }\n\n  @override\n  void onClose(BlocBase<dynamic> bloc) {\n    for (final observer in observers) {\n      observer.onClose(bloc);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/change.dart",
    "content": "import 'package:meta/meta.dart';\n\n/// {@template change}\n/// A [Change] represents the change from one [State] to another.\n/// A [Change] consists of the [currentState] and [nextState].\n/// {@endtemplate}\n@immutable\nclass Change<State> {\n  /// {@macro change}\n  const Change({required this.currentState, required this.nextState});\n\n  /// The current [State] at the time of the [Change].\n  final State currentState;\n\n  /// The next [State] at the time of the [Change].\n  final State nextState;\n\n  @override\n  bool operator ==(Object other) =>\n      identical(this, other) ||\n      other is Change<State> &&\n          runtimeType == other.runtimeType &&\n          currentState == other.currentState &&\n          nextState == other.nextState;\n\n  @override\n  int get hashCode => Object.hashAll([currentState, nextState]);\n\n  @override\n  String toString() {\n    return 'Change { currentState: $currentState, nextState: $nextState }';\n  }\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\n/// {@template cubit}\n/// A [Cubit] is similar to [Bloc] but has no notion of events\n/// and relies on methods to [emit] new states.\n///\n/// Every [Cubit] requires an initial state which will be the\n/// state of the [Cubit] before [emit] has been called.\n///\n/// The current state of a [Cubit] can be accessed via the [state] getter.\n///\n/// ```dart\n/// class CounterCubit extends Cubit<int> {\n///   CounterCubit() : super(0);\n///\n///   void increment() => emit(state + 1);\n/// }\n/// ```\n///\n/// {@endtemplate}\nabstract class Cubit<State> extends BlocBase<State> {\n  /// {@macro cubit}\n  Cubit(State initialState) : super(initialState);\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/emitter.dart",
    "content": "part of 'bloc.dart';\n\n/// {@template emitter}\n/// An [Emitter] is a class which is capable of emitting new states.\n///\n/// See also:\n///\n/// * [EventHandler] which has access to an [Emitter].\n///\n/// {@endtemplate}\nabstract class Emitter<State> {\n  /// Subscribes to the provided [stream] and invokes the [onData] callback\n  /// when the [stream] emits new data.\n  ///\n  /// [onEach] completes when the event handler is cancelled or when\n  /// the provided [stream] has ended.\n  ///\n  /// If [onError] is omitted, any errors on this [stream]\n  /// are considered unhandled, and will be thrown by [onEach].\n  /// As a result, the internal subscription to the [stream] will be canceled.\n  ///\n  /// If [onError] is provided, any errors on this [stream] will be passed on to\n  /// [onError] and will not result in unhandled exceptions or cancelations to\n  /// the internal stream subscription.\n  ///\n  /// **Note**: The stack trace argument may be [StackTrace.empty]\n  /// if the [stream] received an error without a stack trace.\n  Future<void> onEach<T>(\n    Stream<T> stream, {\n    required void Function(T data) onData,\n    void Function(Object error, StackTrace stackTrace)? onError,\n  });\n\n  /// Subscribes to the provided [stream] and invokes the [onData] callback\n  /// when the [stream] emits new data and the result of [onData] is emitted.\n  ///\n  /// [forEach] completes when the event handler is cancelled or when\n  /// the provided [stream] has ended.\n  ///\n  /// If [onError] is omitted, any errors on this [stream]\n  /// are considered unhandled, and will be thrown by [forEach].\n  /// As a result, the internal subscription to the [stream] will be canceled.\n  ///\n  /// If [onError] is provided, any errors on this [stream] will be passed on to\n  /// [onError] and will not result in unhandled exceptions or cancelations to\n  /// the internal stream subscription.\n  ///\n  /// **Note**: The stack trace argument may be [StackTrace.empty]\n  /// if the [stream] received an error without a stack trace.\n  Future<void> forEach<T>(\n    Stream<T> stream, {\n    required State Function(T data) onData,\n    State Function(Object error, StackTrace stackTrace)? onError,\n  });\n\n  /// Whether the [EventHandler] associated with this [Emitter]\n  /// has been completed or canceled.\n  bool get isDone;\n\n  /// Emits the provided [state].\n  void call(State state);\n}\n\nclass _Emitter<State> implements Emitter<State> {\n  _Emitter(this._emit);\n\n  final void Function(State state) _emit;\n  final _completer = Completer<void>();\n  final _disposables = <FutureOr<void> Function()>[];\n\n  var _isCanceled = false;\n  var _isCompleted = false;\n\n  @override\n  Future<void> onEach<T>(\n    Stream<T> stream, {\n    required void Function(T data) onData,\n    void Function(Object error, StackTrace stackTrace)? onError,\n  }) {\n    final completer = Completer<void>();\n    final subscription = stream.listen(\n      onData,\n      onDone: completer.complete,\n      onError: onError ?? completer.completeError,\n      cancelOnError: onError == null,\n    );\n    _disposables.add(subscription.cancel);\n    return Future.any([future, completer.future]).whenComplete(() {\n      subscription.cancel();\n      _disposables.remove(subscription.cancel);\n    });\n  }\n\n  @override\n  Future<void> forEach<T>(\n    Stream<T> stream, {\n    required State Function(T data) onData,\n    State Function(Object error, StackTrace stackTrace)? onError,\n  }) {\n    return onEach<T>(\n      stream,\n      onData: (data) => call(onData(data)),\n      onError: onError != null\n          ? (Object error, StackTrace stackTrace) {\n              call(onError(error, stackTrace));\n            }\n          : null,\n    );\n  }\n\n  @override\n  void call(State state) {\n    assert(\n      !_isCompleted,\n      '''\n\n\nemit was called after an event handler completed normally.\nThis is usually due to an unawaited future in an event handler.\nPlease make sure to await all asynchronous operations with event handlers\nand use emit.isDone after asynchronous operations before calling emit() to\nensure the event handler has not completed.\n\n  **BAD**\n  on<Event>((event, emit) {\n    future.whenComplete(() => emit(...));\n  });\n\n  **GOOD**\n  on<Event>((event, emit) async {\n    await future.whenComplete(() => emit(...));\n  });\n''',\n    );\n    if (!_isCanceled) _emit(state);\n  }\n\n  @override\n  bool get isDone => _isCanceled || _isCompleted;\n\n  void cancel() {\n    if (isDone) return;\n    _isCanceled = true;\n    _close();\n  }\n\n  void complete() {\n    if (isDone) return;\n    assert(\n      _disposables.isEmpty,\n      '''\n\n\nAn event handler completed but left pending subscriptions behind.\nThis is most likely due to an unawaited emit.forEach or emit.onEach. \nPlease make sure to await all asynchronous operations within event handlers.\n\n  **BAD**\n  on<Event>((event, emit) {\n    emit.forEach(...);\n  });  \n  \n  **GOOD**\n  on<Event>((event, emit) async {\n    await emit.forEach(...);\n  });\n\n  **GOOD**\n  on<Event>((event, emit) {\n    return emit.forEach(...);\n  });\n\n  **GOOD**\n  on<Event>((event, emit) => emit.forEach(...));\n\n''',\n    );\n    _isCompleted = true;\n    _close();\n  }\n\n  void _close() {\n    for (final disposable in _disposables) {\n      disposable.call();\n    }\n    _disposables.clear();\n    if (!_completer.isCompleted) _completer.complete();\n  }\n\n  Future<void> get future => _completer.future;\n}\n"
  },
  {
    "path": "packages/bloc/lib/src/transition.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\n/// {@template transition}\n/// A [Transition] is the change from one state to another.\n/// Consists of the [currentState], an [event], and the [nextState].\n/// {@endtemplate}\n@immutable\nclass Transition<Event, State> extends Change<State> {\n  /// {@macro transition}\n  const Transition({\n    required State currentState,\n    required this.event,\n    required State nextState,\n  }) : super(currentState: currentState, nextState: nextState);\n\n  /// The [Event] which triggered the current [Transition].\n  final Event event;\n\n  @override\n  bool operator ==(Object other) =>\n      identical(this, other) ||\n      other is Transition<Event, State> &&\n          runtimeType == other.runtimeType &&\n          currentState == other.currentState &&\n          event == other.event &&\n          nextState == other.nextState;\n\n  @override\n  int get hashCode => Object.hashAll([currentState, event, nextState]);\n\n  @override\n  String toString() {\n    return '''Transition { currentState: $currentState, event: $event, nextState: $nextState }''';\n  }\n}\n"
  },
  {
    "path": "packages/bloc/pubspec.yaml",
    "content": "name: bloc\ndescription: A predictable state management library that helps implement the BLoC (Business Logic Component) design pattern.\nversion: 9.2.0\nrepository: https://github.com/felangel/bloc/tree/master/packages/bloc\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://github.com/felangel/bloc\ndocumentation: https://bloclibrary.dev\ntopics: [bloc, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  meta: ^1.3.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  mocktail: ^1.0.0\n  stream_transform: ^2.0.0\n  test: ^1.18.2\n\nscreenshots:\n  - description: The bloc package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/bloc/test/bloc_event_transformer_test.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport 'package:test/test.dart';\n\n@immutable\nabstract class CounterEvent {}\n\nclass Increment extends CounterEvent {\n  @override\n  bool operator ==(Object value) {\n    if (identical(this, value)) return true;\n    return value is Increment;\n  }\n\n  @override\n  int get hashCode => 0;\n}\n\nconst delay = Duration(milliseconds: 30);\n\nFuture<void> wait() => Future.delayed(delay);\nFuture<void> tick() => Future.delayed(Duration.zero);\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc({EventTransformer<Increment>? incrementTransformer}) : super(0) {\n    on<Increment>(\n      (event, emit) {\n        onCalls.add(event);\n        return Future<void>.delayed(delay, () {\n          if (emit.isDone) return;\n          onEmitCalls.add(event);\n          emit(state + 1);\n        });\n      },\n      transformer: incrementTransformer,\n    );\n  }\n\n  final onCalls = <CounterEvent>[];\n  final onEmitCalls = <CounterEvent>[];\n}\n\nvoid main() {\n  late EventTransformer<dynamic> transformer;\n\n  setUp(() {\n    transformer = Bloc.transformer;\n  });\n\n  tearDown(() {\n    Bloc.transformer = transformer;\n  });\n\n  test('processes events concurrently by default', () async {\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n\n  test(\n      'when processing events concurrently '\n      'all subscriptions are canceled on close', () async {\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(bloc.onEmitCalls, isEmpty);\n\n    expect(states, isEmpty);\n  });\n\n  test(\n      'processes events sequentially when '\n      'transformer is overridden.', () async {\n    EventTransformer<Increment> incrementTransformer() {\n      return (events, mapper) => events.asyncExpand(mapper);\n    }\n\n    final states = <int>[];\n    final bloc = CounterBloc(incrementTransformer: incrementTransformer())\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment()]),\n    );\n    expect(states, equals([1]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n\n  test(\n      'processes events sequentially when '\n      'Bloc.transformer is overridden.', () async {\n    Bloc.transformer = (events, mapper) => events.asyncExpand<dynamic>(mapper);\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment()]),\n    );\n    expect(states, equals([1]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([\n        Increment(),\n        Increment(),\n        Increment(),\n      ]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n}\n"
  },
  {
    "path": "packages/bloc/test/bloc_event_transformer_test_legacy.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport 'package:test/test.dart';\n\n@immutable\nabstract class CounterEvent {}\n\nclass Increment extends CounterEvent {\n  @override\n  bool operator ==(Object value) {\n    if (identical(this, value)) return true;\n    return value is Increment;\n  }\n\n  @override\n  int get hashCode => 0;\n}\n\nconst delay = Duration(milliseconds: 30);\n\nFuture<void> wait() => Future.delayed(delay);\nFuture<void> tick() => Future.delayed(Duration.zero);\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc({EventTransformer<Increment>? incrementTransformer}) : super(0) {\n    on<Increment>(\n      (event, emit) {\n        onCalls.add(event);\n        return Future<void>.delayed(delay, () {\n          if (emit.isDone) return;\n          onEmitCalls.add(event);\n          emit(state + 1);\n        });\n      },\n      transformer: incrementTransformer,\n    );\n  }\n\n  final onCalls = <CounterEvent>[];\n  final onEmitCalls = <CounterEvent>[];\n}\n\nvoid main() {\n  test('processes events concurrently by default', () async {\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n\n  test(\n      'when processing events concurrently '\n      'all subscriptions are canceled on close', () async {\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(bloc.onEmitCalls, isEmpty);\n\n    expect(states, isEmpty);\n  });\n\n  test(\n      'processes events sequentially when '\n      'transformer is overridden.', () async {\n    EventTransformer<Increment> incrementTransformer() {\n      return (events, mapper) => events.asyncExpand(mapper);\n    }\n\n    final states = <int>[];\n    final bloc = CounterBloc(incrementTransformer: incrementTransformer())\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment()]),\n    );\n    expect(states, equals([1]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n\n  test(\n      'processes events sequentially when '\n      'Bloc.transformer is overridden.', () async {\n    final defaultTransformer = Bloc.transformer;\n    addTearDown(() => Bloc.transformer = defaultTransformer);\n\n    Bloc.transformer = (events, mapper) => events.asyncExpand<dynamic>(mapper);\n\n    final states = <int>[];\n    final bloc = CounterBloc()\n      ..stream.listen(states.add)\n      ..add(Increment())\n      ..add(Increment())\n      ..add(Increment());\n\n    await tick();\n\n    expect(bloc.onCalls, equals([Increment()]));\n\n    await wait();\n\n    expect(bloc.onEmitCalls, equals([Increment()]));\n    expect(states, equals([1]));\n\n    await tick();\n\n    expect(bloc.onCalls, equals([Increment(), Increment()]));\n\n    await wait();\n\n    expect(bloc.onEmitCalls, equals([Increment(), Increment()]));\n\n    expect(states, equals([1, 2]));\n\n    await tick();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    await wait();\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n\n    await bloc.close();\n\n    expect(\n      bloc.onCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(\n      bloc.onEmitCalls,\n      equals([Increment(), Increment(), Increment()]),\n    );\n\n    expect(states, equals([1, 2, 3]));\n  });\n}\n"
  },
  {
    "path": "packages/bloc/test/bloc_observer_test.dart",
    "content": "// ignore_for_file: invalid_use_of_protected_member\nimport 'package:bloc/bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'blocs/blocs.dart';\n\nclass _MockBlocObserver extends Mock implements BlocObserver {}\n\nclass DefaultBlocObserver extends BlocObserver {\n  const DefaultBlocObserver();\n}\n\nvoid main() {\n  final bloc = CounterBloc();\n  final error = Exception();\n  const stackTrace = StackTrace.empty;\n  const event = CounterEvent.increment;\n  const change = Change(currentState: 0, nextState: 1);\n  const transition = Transition(\n    currentState: 0,\n    event: CounterEvent.increment,\n    nextState: 1,\n  );\n\n  group(BlocObserver, () {\n    group('onCreate', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onCreate(bloc);\n      });\n    });\n\n    group('onEvent', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onEvent(bloc, event);\n      });\n    });\n\n    group('onChange', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onChange(bloc, change);\n      });\n    });\n\n    group('onTransition', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onTransition(bloc, transition);\n      });\n    });\n\n    group('onDone', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onDone(bloc, event);\n      });\n    });\n\n    group('onError', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onError(bloc, error, stackTrace);\n      });\n    });\n\n    group('onClose', () {\n      test('does nothing by default', () {\n        const DefaultBlocObserver().onClose(bloc);\n      });\n    });\n  });\n\n  group(MultiBlocObserver, () {\n    late MultiBlocObserver observer;\n    late List<BlocObserver> observers;\n\n    setUp(() {\n      observers = [_MockBlocObserver(), _MockBlocObserver()];\n      observer = MultiBlocObserver(observers: observers);\n    });\n\n    group('onCreate', () {\n      test('notifies all registered observers', () {\n        observer.onCreate(bloc);\n        for (final observer in observers) {\n          verify(() => observer.onCreate(bloc)).called(1);\n        }\n      });\n    });\n\n    group('onEvent', () {\n      test('notifies all registered observers', () {\n        observer.onEvent(bloc, event);\n        for (final observer in observers) {\n          verify(() => observer.onEvent(bloc, event)).called(1);\n        }\n      });\n    });\n\n    group('onChange', () {\n      test('notifies all registered observers', () {\n        observer.onChange(bloc, change);\n        for (final observer in observers) {\n          verify(() => observer.onChange(bloc, change)).called(1);\n        }\n      });\n    });\n\n    group('onTransition', () {\n      test('notifies all registered observers', () {\n        observer.onTransition(bloc, transition);\n        for (final observer in observers) {\n          verify(() => observer.onTransition(bloc, transition)).called(1);\n        }\n      });\n    });\n\n    group('onDone', () {\n      test('notifies all registered observers', () {\n        observer.onDone(bloc, event);\n        for (final observer in observers) {\n          verify(() => observer.onDone(bloc, event)).called(1);\n        }\n      });\n    });\n\n    group('onError', () {\n      test('notifies all registered observers', () {\n        observer.onError(bloc, error, stackTrace);\n        for (final observer in observers) {\n          verify(() => observer.onError(bloc, error, stackTrace)).called(1);\n        }\n      });\n    });\n\n    group('onClose', () {\n      test('notifies all registered observers', () {\n        observer.onClose(bloc);\n        for (final observer in observers) {\n          verify(() => observer.onClose(bloc)).called(1);\n        }\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc/test/bloc_on_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:test/test.dart';\n\nabstract class TestEvent {}\n\nclass TestEventA extends TestEvent {}\n\nclass TestEventAA extends TestEventA {}\n\nclass TestEventB extends TestEvent {}\n\nclass TestEventBA extends TestEventB {}\n\nclass TestState {}\n\ntypedef OnEvent<E, S> = void Function(E event, Emitter<S> emit);\n\nvoid defaultOnEvent<E, S>(E event, Emitter<S> emit) {}\n\nclass TestBloc extends Bloc<TestEvent, TestState> {\n  TestBloc({\n    this.onTestEvent,\n    this.onTestEventA,\n    this.onTestEventB,\n    this.onTestEventAA,\n    this.onTestEventBA,\n  }) : super(TestState()) {\n    on<TestEventA>(onTestEventA ?? defaultOnEvent);\n    on<TestEventB>(onTestEventB ?? defaultOnEvent);\n    on<TestEventAA>(onTestEventAA ?? defaultOnEvent);\n    on<TestEventBA>(onTestEventBA ?? defaultOnEvent);\n    on<TestEvent>(onTestEvent ?? defaultOnEvent);\n  }\n\n  final OnEvent<TestEvent, TestState>? onTestEvent;\n  final OnEvent<TestEventA, TestState>? onTestEventA;\n  final OnEvent<TestEventAA, TestState>? onTestEventAA;\n  final OnEvent<TestEventB, TestState>? onTestEventB;\n  final OnEvent<TestEventBA, TestState>? onTestEventBA;\n}\n\nclass DuplicateHandlerBloc extends Bloc<TestEvent, TestState> {\n  DuplicateHandlerBloc() : super(TestState()) {\n    on<TestEvent>(defaultOnEvent);\n    on<TestEvent>(defaultOnEvent);\n  }\n}\n\nclass MissingHandlerBloc extends Bloc<TestEvent, TestState> {\n  MissingHandlerBloc() : super(TestState());\n}\n\nvoid main() {\n  group('on<Event>', () {\n    test('throws StateError when handler is registered more than once', () {\n      const expectedMessage = 'on<TestEvent> was called multiple times. '\n          'There should only be a single event handler per event type.';\n      final expected = throwsA(\n        isA<StateError>().having((e) => e.message, 'message', expectedMessage),\n      );\n      expect(() => DuplicateHandlerBloc(), expected);\n    });\n\n    test('throws StateError when handler is missing', () {\n      const expectedMessage =\n          '''add(TestEventA) was called without a registered event handler.\\n'''\n          '''Make sure to register a handler via on<TestEventA>((event, emit) {...})''';\n      final expected = throwsA(\n        isA<StateError>().having((e) => e.message, 'message', expectedMessage),\n      );\n      expect(() => MissingHandlerBloc().add(TestEventA()), expected);\n    });\n\n    test('invokes all on<T> when event E is added where E is T', () async {\n      var onEventCallCount = 0;\n      var onACallCount = 0;\n      var onBCallCount = 0;\n      var onAACallCount = 0;\n      var onBACallCount = 0;\n\n      final bloc = TestBloc(\n        onTestEvent: (_, __) => onEventCallCount++,\n        onTestEventA: (_, __) => onACallCount++,\n        onTestEventB: (_, __) => onBCallCount++,\n        onTestEventAA: (_, __) => onAACallCount++,\n        onTestEventBA: (_, __) => onBACallCount++,\n      )..add(TestEventA());\n\n      await Future<void>.delayed(Duration.zero);\n\n      expect(onEventCallCount, equals(1));\n      expect(onACallCount, equals(1));\n      expect(onBCallCount, equals(0));\n      expect(onAACallCount, equals(0));\n      expect(onBACallCount, equals(0));\n\n      bloc.add(TestEventAA());\n\n      await Future<void>.delayed(Duration.zero);\n\n      expect(onEventCallCount, equals(2));\n      expect(onACallCount, equals(2));\n      expect(onBCallCount, equals(0));\n      expect(onAACallCount, equals(1));\n      expect(onBACallCount, equals(0));\n\n      bloc.add(TestEventB());\n\n      await Future<void>.delayed(Duration.zero);\n\n      expect(onEventCallCount, equals(3));\n      expect(onACallCount, equals(2));\n      expect(onBCallCount, equals(1));\n      expect(onAACallCount, equals(1));\n      expect(onBACallCount, equals(0));\n\n      bloc.add(TestEventBA());\n\n      await Future<void>.delayed(Duration.zero);\n\n      expect(onEventCallCount, equals(4));\n      expect(onACallCount, equals(2));\n      expect(onBCallCount, equals(2));\n      expect(onAACallCount, equals(1));\n      expect(onBACallCount, equals(1));\n\n      await bloc.close();\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc/test/bloc_test.dart",
    "content": "// ignore_for_file: invalid_use_of_protected_member\nimport 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'blocs/blocs.dart';\n\nFuture<void> tick() => Future<void>.delayed(Duration.zero);\n\nclass MockBlocObserver extends Mock implements BlocObserver {}\n\nclass FakeBlocBase<S> extends Fake implements BlocBase<S> {}\n\nvoid main() {\n  group('Bloc Tests', () {\n    group('Simple Bloc', () {\n      late SimpleBloc simpleBloc;\n      late MockBlocObserver observer;\n\n      setUp(() {\n        simpleBloc = SimpleBloc();\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('triggers onCreate on observer when instantiated', () {\n        final bloc = SimpleBloc();\n        verify(() => observer.onCreate(bloc)).called(1);\n      });\n\n      test('triggers onClose on observer when closed', () async {\n        final bloc = SimpleBloc();\n        await bloc.close();\n        verify(() => observer.onClose(bloc)).called(1);\n      });\n\n      test('close does not emit new states over the state stream', () async {\n        final expectedStates = [emitsDone];\n\n        unawaited(expectLater(simpleBloc.stream, emitsInOrder(expectedStates)));\n\n        await simpleBloc.close();\n      });\n\n      test('state returns correct value initially', () {\n        expect(simpleBloc.state, '');\n      });\n\n      test('should map single event to correct state', () {\n        final expectedStates = ['data', emitsDone];\n        final simpleBloc = SimpleBloc();\n\n        expectLater(\n          simpleBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          verifyInOrder([\n            () => observer.onCreate(simpleBloc),\n            () => observer.onEvent(simpleBloc, 'event'),\n            () => observer.onTransition(\n                  simpleBloc,\n                  const Transition<dynamic, String>(\n                    currentState: '',\n                    event: 'event',\n                    nextState: 'data',\n                  ),\n                ),\n            () => observer.onChange(\n                  simpleBloc,\n                  const Change<String>(currentState: '', nextState: 'data'),\n                ),\n            () => observer.onDone(simpleBloc, 'event'),\n            () => observer.onClose(simpleBloc),\n          ]);\n\n          expect(simpleBloc.state, 'data');\n        });\n\n        simpleBloc\n          ..add('event')\n          ..close();\n      });\n\n      test('should map multiple events to correct states', () {\n        final expectedStates = ['data', emitsDone];\n        final simpleBloc = SimpleBloc();\n\n        expectLater(\n          simpleBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          verifyInOrder([\n            () => observer.onCreate(simpleBloc),\n            () => observer.onEvent(simpleBloc, 'event1'),\n            () => observer.onEvent(simpleBloc, 'event2'),\n            () => observer.onEvent(simpleBloc, 'event3'),\n            () => observer.onTransition(\n                  simpleBloc,\n                  const Transition<dynamic, String>(\n                    currentState: '',\n                    event: 'event1',\n                    nextState: 'data',\n                  ),\n                ),\n            () => observer.onChange(\n                  simpleBloc,\n                  const Change<String>(currentState: '', nextState: 'data'),\n                ),\n            () => observer.onDone(simpleBloc, 'event1'),\n            () => observer.onClose(simpleBloc),\n          ]);\n\n          expect(simpleBloc.state, 'data');\n        });\n\n        simpleBloc\n          ..add('event1')\n          ..add('event2')\n          ..add('event3')\n          ..close();\n      });\n\n      test('is a broadcast stream', () {\n        final expectedStates = ['data', emitsDone];\n\n        expect(simpleBloc.stream.isBroadcast, isTrue);\n        expectLater(simpleBloc.stream, emitsInOrder(expectedStates));\n        expectLater(simpleBloc.stream, emitsInOrder(expectedStates));\n\n        simpleBloc\n          ..add('event')\n          ..close();\n      });\n\n      test('multiple subscribers receive the latest state', () {\n        const expectedStates = <String>['data'];\n\n        expectLater(simpleBloc.stream, emitsInOrder(expectedStates));\n        expectLater(simpleBloc.stream, emitsInOrder(expectedStates));\n        expectLater(simpleBloc.stream, emitsInOrder(expectedStates));\n\n        simpleBloc.add('event');\n      });\n    });\n\n    group('Complex Bloc', () {\n      late ComplexBloc complexBloc;\n      late MockBlocObserver observer;\n\n      setUp(() {\n        complexBloc = ComplexBloc();\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('close does not emit new states over the state stream', () async {\n        final expectedStates = [emitsDone];\n\n        unawaited(\n          expectLater(complexBloc.stream, emitsInOrder(expectedStates)),\n        );\n\n        await complexBloc.close();\n      });\n\n      test('state returns correct value initially', () {\n        expect(complexBloc.state, ComplexStateA());\n      });\n\n      test('should map single event to correct state', () {\n        final expectedStates = [ComplexStateB()];\n        final complexBloc = ComplexBloc();\n\n        expectLater(\n          complexBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          verifyInOrder([\n            () => observer.onCreate(complexBloc),\n            () => observer.onEvent(complexBloc, ComplexEventB()),\n            () => observer.onTransition(\n                  complexBloc,\n                  Transition<ComplexEvent, ComplexState>(\n                    currentState: ComplexStateA(),\n                    event: ComplexEventB(),\n                    nextState: ComplexStateB(),\n                  ),\n                ),\n            () => observer.onChange(\n                  complexBloc,\n                  Change<ComplexState>(\n                    currentState: ComplexStateA(),\n                    nextState: ComplexStateB(),\n                  ),\n                ),\n            () => observer.onDone(complexBloc, ComplexEventB()),\n            () => observer.onClose(complexBloc),\n          ]);\n\n          expect(complexBloc.state, ComplexStateB());\n        });\n\n        complexBloc\n          ..add(ComplexEventB())\n          ..close();\n      });\n\n      test('should map multiple events to correct states', () async {\n        final expectedStates = [\n          ComplexStateB(),\n          ComplexStateD(),\n          ComplexStateA(),\n          ComplexStateC(),\n        ];\n\n        unawaited(\n          expectLater(complexBloc.stream, emitsInOrder(expectedStates)),\n        );\n\n        complexBloc.add(ComplexEventA());\n        await Future<void>.delayed(const Duration(milliseconds: 20));\n        complexBloc.add(ComplexEventB());\n        await Future<void>.delayed(const Duration(milliseconds: 20));\n        complexBloc.add(ComplexEventC());\n        await Future<void>.delayed(const Duration(milliseconds: 20));\n        complexBloc.add(ComplexEventD());\n        await Future<void>.delayed(const Duration(milliseconds: 200));\n        complexBloc\n          ..add(ComplexEventC())\n          ..add(ComplexEventA());\n        await Future<void>.delayed(const Duration(milliseconds: 120));\n        complexBloc.add(ComplexEventC());\n      });\n\n      test('is a broadcast stream', () {\n        final expectedStates = [ComplexStateB()];\n\n        expect(complexBloc.stream.isBroadcast, isTrue);\n        expectLater(complexBloc.stream, emitsInOrder(expectedStates));\n        expectLater(complexBloc.stream, emitsInOrder(expectedStates));\n\n        complexBloc.add(ComplexEventB());\n      });\n\n      test('multiple subscribers receive the latest state', () {\n        final expected = <ComplexState>[ComplexStateB()];\n\n        expectLater(complexBloc.stream, emitsInOrder(expected));\n        expectLater(complexBloc.stream, emitsInOrder(expected));\n        expectLater(complexBloc.stream, emitsInOrder(expected));\n\n        complexBloc.add(ComplexEventB());\n      });\n    });\n\n    group('CounterBloc', () {\n      late CounterBloc counterBloc;\n      late MockBlocObserver observer;\n      late List<String> transitions;\n      late List<CounterEvent> events;\n\n      setUp(() {\n        events = [];\n        transitions = [];\n        counterBloc = CounterBloc(\n          onEventCallback: events.add,\n          onTransitionCallback: (transition) {\n            transitions.add(transition.toString());\n          },\n        );\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('state returns correct value initially', () {\n        expect(counterBloc.state, 0);\n        expect(events.isEmpty, true);\n        expect(transitions.isEmpty, true);\n      });\n\n      test('single Increment event updates state to 1', () {\n        final expectedStates = [1, emitsDone];\n        final expectedTransitions = [\n          '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }''',\n        ];\n        final counterBloc = CounterBloc(\n          onEventCallback: events.add,\n          onTransitionCallback: (transition) {\n            transitions.add(transition.toString());\n          },\n        );\n\n        expectLater(\n          counterBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          expectLater(transitions, expectedTransitions);\n\n          verifyInOrder([\n            () => observer.onCreate(counterBloc),\n            () => observer.onEvent(counterBloc, CounterEvent.increment),\n            () => observer.onTransition(\n                  counterBloc,\n                  const Transition<CounterEvent, int>(\n                    currentState: 0,\n                    event: CounterEvent.increment,\n                    nextState: 1,\n                  ),\n                ),\n            () => observer.onChange(\n                  counterBloc,\n                  const Change<int>(currentState: 0, nextState: 1),\n                ),\n            () => observer.onDone(counterBloc, CounterEvent.increment),\n            () => observer.onClose(counterBloc),\n          ]);\n\n          expect(counterBloc.state, 1);\n        });\n\n        counterBloc\n          ..add(CounterEvent.increment)\n          ..close();\n      });\n\n      test('multiple Increment event updates state to 3', () {\n        final expectedStates = [1, 2, 3, emitsDone];\n        final expectedTransitions = [\n          '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }''',\n          '''Transition { currentState: 1, event: CounterEvent.increment, nextState: 2 }''',\n          '''Transition { currentState: 2, event: CounterEvent.increment, nextState: 3 }''',\n        ];\n        final counterBloc = CounterBloc(\n          onEventCallback: events.add,\n          onTransitionCallback: (transition) {\n            transitions.add(transition.toString());\n          },\n        );\n\n        expectLater(\n          counterBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          expect(transitions, expectedTransitions);\n\n          verifyInOrder([\n            () => observer.onCreate(counterBloc),\n            () => observer.onEvent(counterBloc, CounterEvent.increment),\n            () => observer.onEvent(counterBloc, CounterEvent.increment),\n            () => observer.onEvent(counterBloc, CounterEvent.increment),\n            () => observer.onTransition(\n                  counterBloc,\n                  const Transition<CounterEvent, int>(\n                    currentState: 0,\n                    event: CounterEvent.increment,\n                    nextState: 1,\n                  ),\n                ),\n            () => observer.onChange(\n                  counterBloc,\n                  const Change<int>(currentState: 0, nextState: 1),\n                ),\n            () => observer.onDone(counterBloc, CounterEvent.increment),\n            () => observer.onTransition(\n                  counterBloc,\n                  const Transition<CounterEvent, int>(\n                    currentState: 1,\n                    event: CounterEvent.increment,\n                    nextState: 2,\n                  ),\n                ),\n            () => observer.onChange(\n                  counterBloc,\n                  const Change<int>(currentState: 1, nextState: 2),\n                ),\n            () => observer.onTransition(\n                  counterBloc,\n                  const Transition<CounterEvent, int>(\n                    currentState: 2,\n                    event: CounterEvent.increment,\n                    nextState: 3,\n                  ),\n                ),\n            () => observer.onChange(\n                  counterBloc,\n                  const Change<int>(currentState: 2, nextState: 3),\n                ),\n            () => observer.onDone(counterBloc, CounterEvent.increment),\n            () => observer.onClose(counterBloc),\n          ]);\n\n          expect(counterBloc.state, equals(3));\n        });\n\n        counterBloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment)\n          ..close();\n      });\n\n      test('is a broadcast stream', () {\n        final expectedStates = [1, emitsDone];\n\n        expect(counterBloc.stream.isBroadcast, isTrue);\n        expectLater(counterBloc.stream, emitsInOrder(expectedStates));\n        expectLater(counterBloc.stream, emitsInOrder(expectedStates));\n\n        counterBloc\n          ..add(CounterEvent.increment)\n          ..close();\n      });\n\n      test('multiple subscribers receive the latest state', () {\n        const expected = <int>[1];\n\n        expectLater(counterBloc.stream, emitsInOrder(expected));\n        expectLater(counterBloc.stream, emitsInOrder(expected));\n        expectLater(counterBloc.stream, emitsInOrder(expected));\n\n        counterBloc.add(CounterEvent.increment);\n      });\n\n      test('maintains correct transition composition', () {\n        final expectedTransitions = <Transition<CounterEvent, int>>[\n          const Transition(\n            currentState: 0,\n            event: CounterEvent.decrement,\n            nextState: -1,\n          ),\n          const Transition(\n            currentState: -1,\n            event: CounterEvent.increment,\n            nextState: 0,\n          ),\n        ];\n\n        final expectedStates = [-1, 0, emitsDone];\n        final transitions = <Transition<CounterEvent, int>>[];\n        final counterBloc = CounterBloc(onTransitionCallback: transitions.add);\n\n        expectLater(\n          counterBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          expect(transitions, expectedTransitions);\n        });\n        counterBloc\n          ..add(CounterEvent.decrement)\n          ..add(CounterEvent.increment)\n          ..close();\n      });\n\n      test('events are processed asynchronously', () async {\n        expect(counterBloc.state, 0);\n        expect(events.isEmpty, true);\n        expect(transitions.isEmpty, true);\n\n        counterBloc.add(CounterEvent.increment);\n\n        expect(counterBloc.state, 0);\n        expect(events, [CounterEvent.increment]);\n        expect(transitions.isEmpty, true);\n\n        await tick();\n\n        expect(counterBloc.state, 1);\n        expect(events, [CounterEvent.increment]);\n        expect(\n          transitions,\n          const [\n            '''Transition { currentState: 0, event: CounterEvent.increment, nextState: 1 }''',\n          ],\n        );\n      });\n    });\n\n    group('Async Bloc', () {\n      late AsyncBloc asyncBloc;\n      late MockBlocObserver observer;\n\n      setUpAll(() {\n        registerFallbackValue(FakeBlocBase<dynamic>());\n        registerFallbackValue(StackTrace.empty);\n      });\n\n      setUp(() {\n        asyncBloc = AsyncBloc();\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('close does not emit new states over the state stream', () async {\n        final expectedStates = [emitsDone];\n\n        unawaited(expectLater(asyncBloc.stream, emitsInOrder(expectedStates)));\n\n        await asyncBloc.close();\n      });\n\n      test(\n          'close while events are pending finishes processing pending events '\n          'and does not trigger onError', () async {\n        final expectedStates = <AsyncState>[\n          AsyncState.initial().copyWith(isLoading: true),\n          AsyncState.initial().copyWith(isSuccess: true),\n        ];\n        final states = <AsyncState>[];\n        final asyncBloc = AsyncBloc()\n          ..stream.listen(states.add)\n          ..add(AsyncEvent());\n\n        await asyncBloc.close();\n\n        expect(states, expectedStates);\n\n        verify(() => observer.onDone(asyncBloc, AsyncEvent())).called(1);\n        verifyNever(() => observer.onError(any(), any(), any()));\n      });\n\n      test('state returns correct value initially', () {\n        expect(asyncBloc.state, AsyncState.initial());\n      });\n\n      test('should map single event to correct state', () {\n        final expectedStates = [\n          const AsyncState(isLoading: true, hasError: false, isSuccess: false),\n          const AsyncState(isLoading: false, hasError: false, isSuccess: true),\n          emitsDone,\n        ];\n        final asyncBloc = AsyncBloc();\n\n        expectLater(\n          asyncBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          verifyInOrder([\n            () => observer.onCreate(asyncBloc),\n            () => observer.onEvent(asyncBloc, AsyncEvent()),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onDone(asyncBloc, AsyncEvent()),\n            () => observer.onClose(asyncBloc),\n          ]);\n\n          expect(\n            asyncBloc.state,\n            const AsyncState(\n              isLoading: false,\n              hasError: false,\n              isSuccess: true,\n            ),\n          );\n        });\n\n        asyncBloc\n          ..add(AsyncEvent())\n          ..close();\n      });\n\n      test('should map multiple events to correct states', () {\n        final expectedStates = [\n          const AsyncState(isLoading: true, hasError: false, isSuccess: false),\n          const AsyncState(isLoading: false, hasError: false, isSuccess: true),\n          const AsyncState(isLoading: true, hasError: false, isSuccess: false),\n          const AsyncState(isLoading: false, hasError: false, isSuccess: true),\n          emitsDone,\n        ];\n        final asyncBloc = AsyncBloc();\n\n        expectLater(\n          asyncBloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          verifyInOrder([\n            () => observer.onCreate(asyncBloc),\n            () => observer.onEvent(asyncBloc, AsyncEvent()),\n            () => observer.onEvent(asyncBloc, AsyncEvent()),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onDone(asyncBloc, AsyncEvent()),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                  ),\n                ),\n            () => observer.onTransition(\n                  asyncBloc,\n                  Transition<AsyncEvent, AsyncState>(\n                    currentState: const AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    event: AsyncEvent(),\n                    nextState: const AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onChange(\n                  asyncBloc,\n                  const Change<AsyncState>(\n                    currentState: AsyncState(\n                      isLoading: true,\n                      hasError: false,\n                      isSuccess: false,\n                    ),\n                    nextState: AsyncState(\n                      isLoading: false,\n                      hasError: false,\n                      isSuccess: true,\n                    ),\n                  ),\n                ),\n            () => observer.onDone(asyncBloc, AsyncEvent()),\n            () => observer.onClose(asyncBloc),\n          ]);\n\n          expect(\n            asyncBloc.state,\n            const AsyncState(\n              isLoading: false,\n              hasError: false,\n              isSuccess: true,\n            ),\n          );\n        });\n\n        asyncBloc\n          ..add(AsyncEvent())\n          ..add(AsyncEvent())\n          ..close();\n      });\n\n      test('is a broadcast stream', () {\n        final expectedStates = [\n          const AsyncState(isLoading: true, hasError: false, isSuccess: false),\n          const AsyncState(isLoading: false, hasError: false, isSuccess: true),\n          emitsDone,\n        ];\n\n        expect(asyncBloc.stream.isBroadcast, isTrue);\n        expectLater(asyncBloc.stream, emitsInOrder(expectedStates));\n        expectLater(asyncBloc.stream, emitsInOrder(expectedStates));\n\n        asyncBloc\n          ..add(AsyncEvent())\n          ..close();\n      });\n\n      test('multiple subscribers receive the latest state', () {\n        final expected = <AsyncState>[\n          const AsyncState(isLoading: true, hasError: false, isSuccess: false),\n          const AsyncState(isLoading: false, hasError: false, isSuccess: true),\n        ];\n\n        expectLater(asyncBloc.stream, emitsInOrder(expected));\n        expectLater(asyncBloc.stream, emitsInOrder(expected));\n        expectLater(asyncBloc.stream, emitsInOrder(expected));\n\n        asyncBloc.add(AsyncEvent());\n      });\n    });\n\n    group('MergeBloc', () {\n      test('maintains correct transition composition', () {\n        final expectedTransitions = <Transition<CounterEvent, int>>[\n          const Transition(\n            currentState: 0,\n            event: CounterEvent.increment,\n            nextState: 1,\n          ),\n          const Transition(\n            currentState: 1,\n            event: CounterEvent.decrement,\n            nextState: 0,\n          ),\n          const Transition(\n            currentState: 0,\n            event: CounterEvent.decrement,\n            nextState: -1,\n          ),\n        ];\n        final expectedStates = [1, 0, -1, emitsDone];\n        final transitions = <Transition<CounterEvent, int>>[];\n\n        final bloc = MergeBloc(\n          onTransitionCallback: transitions.add,\n        );\n\n        expectLater(\n          bloc.stream,\n          emitsInOrder(expectedStates),\n        ).then((dynamic _) {\n          expect(transitions, expectedTransitions);\n        });\n        bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.decrement)\n          ..add(CounterEvent.decrement)\n          ..close();\n      });\n    });\n\n    group('SeededBloc', () {\n      test('does not emit repeated states', () {\n        final seededBloc = SeededBloc(seed: 0, states: [1, 2, 1, 1]);\n        final expectedStates = [1, 2, 1, emitsDone];\n\n        expectLater(seededBloc.stream, emitsInOrder(expectedStates));\n\n        seededBloc\n          ..add('event')\n          ..close();\n      });\n\n      test('can emit initial state only once', () {\n        final seededBloc = SeededBloc(seed: 0, states: [0, 0]);\n        final expectedStates = [0, emitsDone];\n\n        expectLater(seededBloc.stream, emitsInOrder(expectedStates));\n\n        seededBloc\n          ..add('event')\n          ..close();\n      });\n\n      test(\n          'can emit initial state and '\n          'continue emitting distinct states', () {\n        final seededBloc = SeededBloc(seed: 0, states: [0, 0, 1]);\n        final expectedStates = [0, 1, emitsDone];\n\n        expectLater(seededBloc.stream, emitsInOrder(expectedStates));\n\n        seededBloc\n          ..add('event')\n          ..close();\n      });\n\n      test('discards subsequent duplicate states (distinct events)', () {\n        final seededBloc = SeededBloc(seed: 0, states: [1, 1]);\n        final expectedStates = [1, emitsDone];\n\n        expectLater(seededBloc.stream, emitsInOrder(expectedStates));\n\n        seededBloc\n          ..add('eventA')\n          ..add('eventB')\n          ..add('eventC')\n          ..close();\n      });\n\n      test('discards subsequent duplicate states (same event)', () {\n        final seededBloc = SeededBloc(seed: 0, states: [1, 1]);\n        final expectedStates = [1, emitsDone];\n\n        expectLater(seededBloc.stream, emitsInOrder(expectedStates));\n\n        seededBloc\n          ..add('event')\n          ..add('event')\n          ..add('event')\n          ..close();\n      });\n    });\n\n    group('StreamBloc', () {\n      test('cancels subscriptions correctly', () async {\n        const expectedStates = [0, 1, 2, 3, 4];\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = StreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(Subscribe());\n\n        await tick();\n\n        controller\n          ..add(0)\n          ..add(1)\n          ..add(2);\n\n        await tick();\n\n        bloc.add(Subscribe());\n\n        await tick();\n\n        controller\n          ..add(3)\n          ..add(4);\n\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        await bloc.close();\n        expect(states, equals(expectedStates));\n      });\n    });\n\n    group('RestartableStreamBloc', () {\n      test('unawaited forEach throws AssertionError', () async {\n        late final Object blocError;\n        await runZonedGuarded(() async {\n          final controller = StreamController<int>.broadcast();\n          final bloc = RestartableStreamBloc(controller.stream)\n            ..add(UnawaitedForEach());\n\n          await tick();\n\n          controller.add(0);\n\n          await tick();\n\n          await Future<void>.delayed(const Duration(milliseconds: 300));\n\n          await bloc.close();\n        }, (error, stackTrace) {\n          blocError = error;\n        });\n\n        expect(\n          blocError,\n          isA<AssertionError>().having(\n            (e) => e.message,\n            'message',\n            contains(\n              '''An event handler completed but left pending subscriptions behind.''',\n            ),\n          ),\n        );\n      });\n\n      test('forEach cancels subscriptions correctly', () async {\n        const expectedStates = [0, 1, 2, 3, 4];\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(ForEach());\n\n        await tick();\n\n        controller\n          ..add(0)\n          ..add(1)\n          ..add(2);\n\n        await tick();\n\n        bloc.add(ForEach());\n\n        await tick();\n\n        controller\n          ..add(3)\n          ..add(4);\n\n        await bloc.close();\n        expect(states, equals(expectedStates));\n      });\n\n      test(\n          'forEach with onError handles errors emitted by stream '\n          'and does not cancel the subscription', () async {\n        const expectedStates = [1, 2, 3, -1, 4, 5, 6];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(ForEachOnError());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n\n        controller\n          ..addError(error)\n          ..add(4)\n          ..add(5)\n          ..add(6);\n\n        await tick();\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test('forEach with try/catch handles errors emitted by stream', () async {\n        const expectedStates = [1, 2, 3, -1];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(ForEachTryCatch());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n\n        controller.addError(error);\n\n        await tick();\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test(\n          'forEach with catchError '\n          'handles errors emitted by stream', () async {\n        const expectedStates = [1, 2, 3, -1];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(ForEachCatchError());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n\n        controller.addError(error);\n\n        await tick();\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test('forEach throws when stream emits error', () async {\n        const expectedStates = [1, 2, 3];\n        final error = Exception('oops');\n        final states = <int>[];\n        late final dynamic uncaughtError;\n\n        await runZonedGuarded(\n          () async {\n            final controller = StreamController<int>.broadcast();\n            final bloc = RestartableStreamBloc(controller.stream)\n              ..stream.listen(states.add)\n              ..add(ForEach());\n\n            await tick();\n\n            controller\n              ..add(1)\n              ..add(2)\n              ..add(3);\n\n            await tick();\n\n            controller\n              ..addError(error)\n              ..add(3)\n              ..add(4)\n              ..add(5);\n\n            await bloc.close();\n          },\n          (error, stackTrace) => uncaughtError = error,\n        );\n        expect(states, equals(expectedStates));\n        expect(uncaughtError, equals(error));\n      });\n\n      test('unawaited onEach throws AssertionError', () async {\n        late final Object blocError;\n        await runZonedGuarded(() async {\n          final controller = StreamController<int>.broadcast();\n          final bloc = RestartableStreamBloc(controller.stream)\n            ..add(UnawaitedOnEach());\n\n          await bloc.close();\n        }, (error, stackTrace) {\n          blocError = error;\n        });\n\n        expect(\n          blocError,\n          isA<AssertionError>().having(\n            (e) => e.message,\n            'message',\n            contains(\n              '''An event handler completed but left pending subscriptions behind.''',\n            ),\n          ),\n        );\n      });\n\n      test(\n          'onEach with onError handles errors emitted by stream '\n          'and does not cancel subscription', () async {\n        const expectedStates = [1, 2, 3, -1, 4, 5, 6];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(OnEachOnError());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        controller\n          ..addError(error)\n          ..add(4)\n          ..add(5)\n          ..add(6);\n        await tick();\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test('onEach with try/catch handles errors emitted by stream', () async {\n        const expectedStates = [1, 2, 3, -1];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(OnEachTryCatch());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        controller.addError(error);\n        await tick();\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test(\n          'onEach with try/catch handles errors '\n          'emitted by stream and cancels delayed emits', () async {\n        const expectedStates = [-1];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(OnEachTryCatchAbort());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3)\n          ..addError(error);\n\n        await tick();\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test(\n          'onEach with catchError '\n          'handles errors emitted by stream', () async {\n        const expectedStates = [1, 2, 3, -1];\n        final error = Exception('oops');\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(OnEachCatchError());\n\n        await tick();\n\n        controller\n          ..add(1)\n          ..add(2)\n          ..add(3);\n\n        await tick();\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        controller.addError(error);\n        await tick();\n\n        expect(states, equals(expectedStates));\n\n        await bloc.close();\n      });\n\n      test('onEach cancels subscriptions correctly', () async {\n        const expectedStates = [3, 4];\n        final states = <int>[];\n        final controller = StreamController<int>.broadcast();\n        final bloc = RestartableStreamBloc(controller.stream)\n          ..stream.listen(states.add)\n          ..add(OnEach());\n\n        await tick();\n\n        controller\n          ..add(0)\n          ..add(1)\n          ..add(2);\n\n        bloc.add(OnEach());\n        await tick();\n\n        controller\n          ..add(3)\n          ..add(4);\n\n        await Future<void>.delayed(const Duration(milliseconds: 300));\n\n        await bloc.close();\n        expect(states, equals(expectedStates));\n      });\n\n      test('onEach throws when stream emits error', () async {\n        const expectedStates = [1, 2, 3];\n        final error = Exception('oops');\n        final states = <int>[];\n        late final dynamic uncaughtError;\n\n        await runZonedGuarded(\n          () async {\n            final controller = StreamController<int>.broadcast();\n            final bloc = RestartableStreamBloc(controller.stream)\n              ..stream.listen(states.add)\n              ..add(OnEach());\n\n            await tick();\n\n            controller\n              ..add(1)\n              ..add(2)\n              ..add(3);\n\n            await tick();\n            await Future<void>.delayed(const Duration(milliseconds: 300));\n\n            controller\n              ..addError(error)\n              ..add(4)\n              ..add(5)\n              ..add(6);\n\n            await tick();\n            await Future<void>.delayed(const Duration(milliseconds: 300));\n\n            await bloc.close();\n          },\n          (error, stack) => uncaughtError = error,\n        );\n\n        expect(states, equals(expectedStates));\n        expect(uncaughtError, equals(error));\n      });\n    });\n\n    group('UnawaitedBloc', () {\n      test(\n          'throws AssertionError when emit is called '\n          'after the event handler completed normally', () async {\n        late final Object blocError;\n        await runZonedGuarded(\n          () async {\n            final completer = Completer<void>();\n            final bloc = UnawaitedBloc(completer.future)..add(UnawaitedEvent());\n\n            await tick();\n\n            completer.complete();\n\n            await tick();\n\n            await bloc.close();\n          },\n          (error, stackTrace) => blocError = error,\n        );\n\n        expect(\n          blocError,\n          isA<AssertionError>().having(\n            (e) => e.message,\n            'message',\n            contains(\n              'emit was called after an event handler completed normally.',\n            ),\n          ),\n        );\n      });\n    });\n\n    group('Exception', () {\n      test('does not break stream', () {\n        runZonedGuarded(() {\n          final expectedStates = [-1, emitsDone];\n          final counterBloc = CounterExceptionBloc();\n\n          expectLater(counterBloc.stream, emitsInOrder(expectedStates));\n\n          counterBloc\n            ..add(CounterEvent.increment)\n            ..add(CounterEvent.decrement)\n            ..close();\n        }, (Object error, StackTrace stackTrace) {\n          expect(error.toString(), equals('Exception: fatal exception'));\n          expect(stackTrace, isNotNull);\n          expect(stackTrace, isNot(StackTrace.empty));\n        });\n      });\n\n      test('addError triggers onError', () async {\n        final expectedError = Exception('fatal exception');\n\n        runZonedGuarded(() {\n          OnExceptionBloc(\n            exception: expectedError,\n            onErrorCallback: (Object _, StackTrace __) {},\n          ).addError(expectedError, StackTrace.current);\n        }, (Object error, StackTrace stackTrace) {\n          expect(error, equals(expectedError));\n          expect(stackTrace, isNotNull);\n          expect(stackTrace, isNot(StackTrace.empty));\n        });\n      });\n\n      test('triggers onError from on<E>', () {\n        final exception = Exception('fatal exception');\n        runZonedGuarded(() {\n          Object? expectedError;\n          StackTrace? expectedStacktrace;\n\n          final onExceptionBloc = OnExceptionBloc(\n            exception: exception,\n            onErrorCallback: (Object error, StackTrace stackTrace) {\n              expectedError = error;\n              expectedStacktrace = stackTrace;\n            },\n          );\n\n          expectLater(\n            onExceptionBloc.stream,\n            emitsInOrder(<Matcher>[emitsDone]),\n          ).then((dynamic _) {\n            expect(expectedError, exception);\n            expect(expectedStacktrace, isNotNull);\n            expect(expectedStacktrace, isNot(StackTrace.empty));\n          });\n\n          onExceptionBloc\n            ..add(CounterEvent.increment)\n            ..close();\n        }, (Object error, StackTrace stackTrace) {\n          expect(error, equals(exception));\n          expect(stackTrace, isNotNull);\n          expect(stackTrace, isNot(StackTrace.empty));\n        });\n      });\n\n      test('triggers onError from onEvent', () {\n        final exception = Exception('fatal exception');\n        runZonedGuarded(() {\n          OnEventErrorBloc(exception: exception)\n            ..add(CounterEvent.increment)\n            ..close();\n        }, (Object error, StackTrace stackTrace) {\n          expect(error, equals(exception));\n          expect(stackTrace, isNotNull);\n          expect(stackTrace, isNot(StackTrace.empty));\n        });\n      });\n\n      test(\n          'add throws StateError and triggers onError '\n          'when bloc is closed', () {\n        Object? capturedError;\n        StackTrace? capturedStacktrace;\n        var didThrow = false;\n        runZonedGuarded(() {\n          final counterBloc = CounterBloc(\n            onErrorCallback: (error, stackTrace) {\n              capturedError = error;\n              capturedStacktrace = stackTrace;\n            },\n          );\n\n          expectLater(\n            counterBloc.stream,\n            emitsInOrder(<Matcher>[emitsDone]),\n          );\n\n          counterBloc\n            ..close()\n            ..add(CounterEvent.increment);\n        }, (Object error, StackTrace stackTrace) {\n          didThrow = true;\n          expect(error, equals(capturedError));\n          expect(stackTrace, equals(capturedStacktrace));\n        });\n\n        expect(didThrow, isTrue);\n        expect(\n          capturedError,\n          isA<StateError>().having(\n            (e) => e.message,\n            'message',\n            'Cannot add new events after calling close',\n          ),\n        );\n        expect(capturedStacktrace, isNotNull);\n      });\n    });\n\n    group('Error', () {\n      test('does not break stream', () {\n        runZonedGuarded(\n          () {\n            final expectedStates = [-1, emitsDone];\n            final counterBloc = CounterErrorBloc();\n\n            expectLater(counterBloc.stream, emitsInOrder(expectedStates));\n\n            counterBloc\n              ..add(CounterEvent.increment)\n              ..add(CounterEvent.decrement)\n              ..close();\n          },\n          (Object _, StackTrace __) {},\n        );\n      });\n\n      test('triggers onError from event handler', () {\n        runZonedGuarded(\n          () {\n            final error = Error();\n            Object? expectedError;\n            StackTrace? expectedStacktrace;\n\n            final onErrorBloc = OnErrorBloc(\n              error: error,\n              onErrorCallback: (Object error, StackTrace stackTrace) {\n                expectedError = error;\n                expectedStacktrace = stackTrace;\n              },\n            );\n\n            expectLater(\n              onErrorBloc.stream,\n              emitsInOrder(<Matcher>[emitsDone]),\n            ).then((dynamic _) {\n              expect(expectedError, error);\n              expect(expectedStacktrace, isNotNull);\n            });\n\n            onErrorBloc\n              ..add(CounterEvent.increment)\n              ..close();\n          },\n          (Object _, StackTrace __) {},\n        );\n      });\n\n      test('triggers onError from onTransition', () {\n        runZonedGuarded(\n          () {\n            final error = Error();\n            Object? expectedError;\n            StackTrace? expectedStacktrace;\n\n            final onTransitionErrorBloc = OnTransitionErrorBloc(\n              error: error,\n              onErrorCallback: (Object error, StackTrace stackTrace) {\n                expectedError = error;\n                expectedStacktrace = stackTrace;\n              },\n            );\n\n            expectLater(\n              onTransitionErrorBloc.stream,\n              emitsInOrder(<Matcher>[emitsDone]),\n            ).then((dynamic _) {\n              expect(expectedError, error);\n              expect(expectedStacktrace, isNotNull);\n              expect(onTransitionErrorBloc.state, 0);\n            });\n\n            onTransitionErrorBloc\n              ..add(CounterEvent.increment)\n              ..close();\n          },\n          (Object _, StackTrace __) {},\n        );\n      });\n    });\n\n    group('emit', () {\n      test('updates the state', () async {\n        final counterBloc = CounterBloc();\n        unawaited(\n          expectLater(counterBloc.stream, emitsInOrder(const <int>[42])),\n        );\n        counterBloc.emit(42);\n        expect(counterBloc.state, 42);\n        await counterBloc.close();\n      });\n\n      test(\n          'throws StateError and triggers onError '\n          'when bloc is closed', () async {\n        Object? capturedError;\n        StackTrace? capturedStacktrace;\n\n        final states = <int>[];\n        final expectedStateError = isA<StateError>().having(\n          (e) => e.message,\n          'message',\n          'Cannot emit new states after calling close',\n        );\n\n        final counterBloc = CounterBloc(\n          onErrorCallback: (error, stackTrace) {\n            capturedError = error;\n            capturedStacktrace = stackTrace;\n          },\n        )..stream.listen(states.add);\n\n        await counterBloc.close();\n\n        expect(counterBloc.isClosed, isTrue);\n        expect(counterBloc.state, equals(0));\n        expect(states, isEmpty);\n        expect(() => counterBloc.emit(1), throwsA(expectedStateError));\n        expect(counterBloc.state, equals(0));\n        expect(states, isEmpty);\n        expect(capturedError, expectedStateError);\n        expect(capturedStacktrace, isNotNull);\n      });\n    });\n\n    group('close', () {\n      test('emits done (sync)', () {\n        final bloc = CounterBloc()..close();\n        expect(bloc.stream, emitsDone);\n      });\n\n      test('emits done (async)', () async {\n        final bloc = CounterBloc();\n        await bloc.close();\n        expect(bloc.stream, emitsDone);\n      });\n    });\n\n    group('isClosed', () {\n      test('returns true after bloc is closed', () async {\n        final bloc = CounterBloc();\n        expect(bloc.isClosed, isFalse);\n        await bloc.close();\n        expect(bloc.isClosed, isTrue);\n      });\n    });\n  });\n}\n\nvoid unawaited(Future<void> future) {}\n"
  },
  {
    "path": "packages/bloc/test/blocs/async/async_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\n\npart 'async_event.dart';\npart 'async_state.dart';\n\nclass AsyncBloc extends Bloc<AsyncEvent, AsyncState> {\n  AsyncBloc() : super(AsyncState.initial()) {\n    on<AsyncEvent>(\n      (event, emit) async {\n        emit(state.copyWith(isLoading: true, isSuccess: false));\n        await Future<void>.delayed(Duration.zero);\n        emit(state.copyWith(isLoading: false, isSuccess: true));\n      },\n      transformer: (events, mapper) => events.asyncExpand(mapper),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/async/async_event.dart",
    "content": "part of 'async_bloc.dart';\n\n@immutable\nclass AsyncEvent {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is AsyncEvent && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => Object.hashAll([runtimeType]);\n\n  @override\n  String toString() => 'AsyncEvent';\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/async/async_state.dart",
    "content": "part of 'async_bloc.dart';\n\n@immutable\nclass AsyncState {\n  const AsyncState({\n    required this.isLoading,\n    required this.hasError,\n    required this.isSuccess,\n  });\n\n  factory AsyncState.initial() {\n    return const AsyncState(\n      isLoading: false,\n      hasError: false,\n      isSuccess: false,\n    );\n  }\n\n  final bool isLoading;\n  final bool hasError;\n  final bool isSuccess;\n\n  AsyncState copyWith({bool? isLoading, bool? hasError, bool? isSuccess}) {\n    return AsyncState(\n      isLoading: isLoading ?? this.isLoading,\n      hasError: hasError ?? this.hasError,\n      isSuccess: isSuccess ?? this.isSuccess,\n    );\n  }\n\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is AsyncState &&\n          runtimeType == other.runtimeType &&\n          isLoading == other.isLoading &&\n          hasError == other.hasError &&\n          isSuccess == other.isSuccess;\n\n  @override\n  int get hashCode => Object.hashAll([isLoading, hasError, isSuccess]);\n\n  @override\n  String toString() =>\n      'AsyncState { isLoading: $isLoading, hasError: $hasError, '\n      'isSuccess: $isSuccess }';\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/blocs.dart",
    "content": "export './async/async_bloc.dart';\nexport './complex/complex_bloc.dart';\nexport './counter/counter.dart';\nexport './seeded/seeded_bloc.dart';\nexport './simple/simple_bloc.dart';\nexport './stream/stream.dart';\nexport './unawaited/unawaited_bloc.dart';\n"
  },
  {
    "path": "packages/bloc/test/blocs/complex/complex_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\npart 'complex_event.dart';\npart 'complex_state.dart';\n\nconst _delay = Duration(milliseconds: 100);\n\nclass ComplexBloc extends Bloc<ComplexEvent, ComplexState> {\n  ComplexBloc() : super(ComplexStateA()) {\n    on<ComplexEventA>((_, emit) => emit(ComplexStateA()));\n    on<ComplexEventB>((_, emit) => emit(ComplexStateB()));\n    on<ComplexEventC>(\n      (_, emit) => Future<void>.delayed(_delay, () => emit(ComplexStateC())),\n    );\n    on<ComplexEventD>(\n      (_, emit) => Future<void>.delayed(_delay, () => emit(ComplexStateD())),\n    );\n  }\n\n  @override\n  Stream<ComplexState> get stream {\n    return super.stream.debounce(const Duration(milliseconds: 50));\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/complex/complex_event.dart",
    "content": "part of 'complex_bloc.dart';\n\n@immutable\nabstract class ComplexEvent {}\n\nclass ComplexEventA extends ComplexEvent {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexEventA && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 0;\n}\n\nclass ComplexEventB extends ComplexEvent {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexEventB && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 1;\n}\n\nclass ComplexEventC extends ComplexEvent {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexEventC && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 2;\n}\n\nclass ComplexEventD extends ComplexEvent {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexEventD && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 3;\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/complex/complex_state.dart",
    "content": "part of 'complex_bloc.dart';\n\n@immutable\nabstract class ComplexState {}\n\nclass ComplexStateA extends ComplexState {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexStateA && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 0;\n}\n\nclass ComplexStateB extends ComplexState {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexStateB && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 1;\n}\n\nclass ComplexStateC extends ComplexState {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexStateC && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 2;\n}\n\nclass ComplexStateD extends ComplexState {\n  @override\n  bool operator ==(\n    Object other,\n  ) =>\n      identical(\n        this,\n        other,\n      ) ||\n      other is ComplexStateD && runtimeType == other.runtimeType;\n\n  @override\n  int get hashCode => 3;\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/counter.dart",
    "content": "export './counter_bloc.dart';\nexport './counter_error_bloc.dart';\nexport './counter_exception_bloc.dart';\nexport './merge_bloc.dart';\nexport './on_error_bloc.dart';\nexport './on_event_error_bloc.dart';\nexport './on_exception_bloc.dart';\nexport './on_transition_error_bloc.dart';\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\ntypedef OnEventCallback = void Function(CounterEvent);\ntypedef OnTransitionCallback = void Function(Transition<CounterEvent, int>);\ntypedef OnErrorCallback = void Function(Object error, StackTrace? stackTrace);\n\nenum CounterEvent { increment, decrement }\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc({\n    this.onEventCallback,\n    this.onTransitionCallback,\n    this.onErrorCallback,\n  }) : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  final OnEventCallback? onEventCallback;\n  final OnTransitionCallback? onTransitionCallback;\n  final OnErrorCallback? onErrorCallback;\n\n  @override\n  void onEvent(CounterEvent event) {\n    super.onEvent(event);\n    onEventCallback?.call(event);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    onTransitionCallback?.call(transition);\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    onErrorCallback?.call(error, stackTrace);\n    super.onError(error, stackTrace);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    switch (event) {\n      case CounterEvent.increment:\n        return emit(state + 1);\n      case CounterEvent.decrement:\n        return emit(state - 1);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/counter_error_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass CounterErrorBloc extends Bloc<CounterEvent, int> {\n  CounterErrorBloc() : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    switch (event) {\n      case CounterEvent.decrement:\n        return emit(state - 1);\n      case CounterEvent.increment:\n        throw Error();\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/counter_exception_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass CounterExceptionBloc extends Bloc<CounterEvent, int> {\n  CounterExceptionBloc() : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    switch (event) {\n      case CounterEvent.decrement:\n        return emit(state - 1);\n      case CounterEvent.increment:\n        throw Exception('fatal exception');\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/merge_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\nimport '../blocs.dart';\n\nEventTransformer<CounterEvent> customTransformer() {\n  return (events, mapper) {\n    final nonDebounceStream =\n        events.where((event) => event != CounterEvent.increment);\n\n    final debounceStream = events\n        .where((event) => event == CounterEvent.increment)\n        .throttle(const Duration(milliseconds: 100));\n\n    return nonDebounceStream\n        .merge(debounceStream)\n        .concurrentAsyncExpand(mapper);\n  };\n}\n\nclass MergeBloc extends Bloc<CounterEvent, int> {\n  MergeBloc({this.onTransitionCallback}) : super(0) {\n    on<CounterEvent>(_onCounterEvent, transformer: customTransformer());\n  }\n\n  final void Function(Transition<CounterEvent, int>)? onTransitionCallback;\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    onTransitionCallback?.call(transition);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    switch (event) {\n      case CounterEvent.increment:\n        return emit(state + 1);\n      case CounterEvent.decrement:\n        return emit(state - 1);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/on_error_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass OnErrorBloc extends Bloc<CounterEvent, int> {\n  OnErrorBloc({required this.error, required this.onErrorCallback}) : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  final void Function(Object, StackTrace) onErrorCallback;\n  final Error error;\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    onErrorCallback(error, stackTrace);\n    super.onError(error, stackTrace);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    throw error;\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/on_event_error_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass OnEventErrorBloc extends Bloc<CounterEvent, int> {\n  OnEventErrorBloc({required this.exception}) : super(0) {\n    on<CounterEvent>((_, __) {});\n  }\n\n  final Exception exception;\n\n  @override\n  // ignore: must_call_super\n  void onEvent(CounterEvent event) {\n    throw exception;\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/on_exception_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass OnExceptionBloc extends Bloc<CounterEvent, int> {\n  OnExceptionBloc({\n    required this.exception,\n    required this.onErrorCallback,\n  }) : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  final void Function(Object, StackTrace) onErrorCallback;\n  final Exception exception;\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    onErrorCallback(error, stackTrace);\n    super.onError(error, stackTrace);\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    throw exception;\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/counter/on_transition_error_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport '../counter/counter_bloc.dart';\n\nclass OnTransitionErrorBloc extends Bloc<CounterEvent, int> {\n  OnTransitionErrorBloc({\n    required this.error,\n    required this.onErrorCallback,\n  }) : super(0) {\n    on<CounterEvent>(_onCounterEvent);\n  }\n\n  final void Function(Object, StackTrace) onErrorCallback;\n  final Error error;\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    onErrorCallback(error, stackTrace);\n    super.onError(error, stackTrace);\n  }\n\n  @override\n  void onTransition(Transition<CounterEvent, int> transition) {\n    super.onTransition(transition);\n    throw error;\n  }\n\n  void _onCounterEvent(CounterEvent event, Emitter<int> emit) {\n    switch (event) {\n      case CounterEvent.increment:\n        return emit(state + 1);\n      case CounterEvent.decrement:\n        return emit(state - 1);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/seeded/seeded_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass SeededBloc extends Bloc<String, int> {\n  SeededBloc({required this.seed, required this.states}) : super(seed) {\n    on<String>((event, emit) {\n      states.forEach(emit.call);\n    });\n  }\n\n  final List<int> states;\n  final int seed;\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/simple/simple_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass SimpleBloc extends Bloc<dynamic, String> {\n  SimpleBloc() : super('') {\n    on<String>((_, emit) => emit('data'));\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/stream/restartable_stream_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\nabstract class RestartableStreamEvent {}\n\nclass ForEach extends RestartableStreamEvent {}\n\nclass ForEachOnError extends RestartableStreamEvent {}\n\nclass ForEachTryCatch extends RestartableStreamEvent {}\n\nclass ForEachCatchError extends RestartableStreamEvent {}\n\nclass UnawaitedForEach extends RestartableStreamEvent {}\n\nclass OnEach extends RestartableStreamEvent {}\n\nclass OnEachOnError extends RestartableStreamEvent {}\n\nclass OnEachTryCatch extends RestartableStreamEvent {}\n\nclass OnEachTryCatchAbort extends RestartableStreamEvent {}\n\nclass OnEachCatchError extends RestartableStreamEvent {}\n\nclass UnawaitedOnEach extends RestartableStreamEvent {}\n\nconst _delay = Duration(milliseconds: 100);\n\nclass RestartableStreamBloc extends Bloc<RestartableStreamEvent, int> {\n  RestartableStreamBloc(Stream<int> stream) : super(0) {\n    on<ForEach>(\n      (_, emit) async {\n        await emit.forEach<int>(\n          stream,\n          onData: (i) => i,\n        );\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<ForEachOnError>(\n      (_, emit) async {\n        try {\n          await emit.forEach<int>(\n            stream,\n            onData: (i) => i,\n            onError: (_, __) => -1,\n          );\n        } catch (_) {\n          emit(-1);\n        }\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<ForEachTryCatch>(\n      (_, emit) async {\n        try {\n          await emit.forEach<int>(\n            stream,\n            onData: (i) => i,\n          );\n        } catch (_) {\n          emit(-1);\n        }\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<ForEachCatchError>(\n      (_, emit) => emit\n          .forEach<int>(\n            stream,\n            onData: (i) => i,\n          )\n          .catchError((dynamic _) => emit(-1)),\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<UnawaitedForEach>(\n      (_, emit) {\n        emit.forEach<int>(\n          stream,\n          onData: (i) => i,\n        );\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<OnEach>(\n      (_, emit) async {\n        await emit.onEach<int>(\n          stream,\n          onData: (i) => Future<void>.delayed(_delay, () => emit(i)),\n        );\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<OnEachOnError>(\n      (_, emit) async {\n        await emit.onEach<int>(\n          stream,\n          onData: (i) => Future<void>.delayed(_delay, () => emit(i)),\n          onError: (_, __) => emit(-1),\n        );\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<OnEachTryCatch>(\n      (_, emit) async {\n        try {\n          await emit.onEach<int>(\n            stream,\n            onData: (i) => Future<void>.delayed(_delay, () => emit(i)),\n          );\n        } catch (_) {\n          emit(-1);\n        }\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<OnEachTryCatchAbort>(\n      (_, emit) async {\n        try {\n          await emit.onEach<int>(\n            stream,\n            onData: (i) => Future<void>.delayed(_delay, () {\n              if (emit.isDone) return;\n              emit(i);\n            }),\n          );\n        } catch (_) {\n          emit(-1);\n        }\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<OnEachCatchError>(\n      (_, emit) => emit\n          .onEach<int>(\n            stream,\n            onData: (i) => Future<void>.delayed(_delay, () => emit(i)),\n          )\n          .catchError((dynamic _) => emit(-1)),\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n\n    on<UnawaitedOnEach>(\n      (_, emit) {\n        emit.onEach<int>(\n          stream,\n          onData: (i) => Future<void>.delayed(_delay, () => emit(i)),\n        );\n      },\n      transformer: (events, mapper) => events.switchMap(mapper),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/stream/stream.dart",
    "content": "export 'restartable_stream_bloc.dart';\nexport 'stream_bloc.dart';\n"
  },
  {
    "path": "packages/bloc/test/blocs/stream/stream_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nabstract class StreamEvent {}\n\nclass Subscribe extends StreamEvent {}\n\nclass OnData extends StreamEvent {\n  OnData(this.data);\n  final int data;\n}\n\nclass StreamBloc extends Bloc<StreamEvent, int> {\n  StreamBloc(Stream<int> stream) : super(0) {\n    on<StreamEvent>((_, emit) {\n      _subscription?.cancel();\n      _subscription = stream.listen((i) {\n        Future<void>.delayed(\n          const Duration(milliseconds: 100),\n          () => add(OnData(i)),\n        );\n      });\n    });\n\n    on<OnData>((event, emit) => emit(event.data));\n  }\n\n  StreamSubscription<int>? _subscription;\n\n  @override\n  Future<void> close() {\n    _subscription?.cancel();\n    return super.close();\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/blocs/unawaited/unawaited_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nclass UnawaitedEvent {}\n\nclass UnawaitedState {}\n\nclass UnawaitedBloc extends Bloc<UnawaitedEvent, UnawaitedState> {\n  UnawaitedBloc(Future<void> future) : super(UnawaitedState()) {\n    on<UnawaitedEvent>((event, emit) {\n      future.whenComplete(() => emit(UnawaitedState()));\n    });\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/cubit_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'cubits/cubits.dart';\n\nclass MockBlocObserver extends Mock implements BlocObserver {}\n\nclass FakeBlocBase<S> extends Fake implements BlocBase<S> {}\n\nclass FakeChange<S> extends Fake implements Change<S> {}\n\nvoid main() {\n  group('Cubit', () {\n    group('constructor', () {\n      late BlocObserver observer;\n\n      setUp(() {\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('triggers onCreate on observer', () {\n        final cubit = CounterCubit();\n        // ignore: invalid_use_of_protected_member\n        verify(() => observer.onCreate(cubit)).called(1);\n      });\n    });\n\n    group('initial state', () {\n      test('is correct', () {\n        expect(CounterCubit().state, 0);\n      });\n    });\n\n    group('addError', () {\n      late BlocObserver observer;\n\n      setUp(() {\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('triggers onError', () async {\n        final expectedError = Exception('fatal exception');\n        final expectedStackTrace = StackTrace.current;\n        final errors = <Object>[];\n        final stackTraces = <StackTrace>[];\n        final cubit = CounterCubit(\n          onErrorCallback: (error, stackTrace) {\n            errors.add(error);\n            stackTraces.add(stackTrace);\n          },\n          // ignore: invalid_use_of_protected_member\n        )..addError(expectedError, expectedStackTrace);\n\n        expect(errors.length, equals(1));\n        expect(errors.first, equals(expectedError));\n        expect(stackTraces.length, equals(1));\n        expect(stackTraces.first, isNotNull);\n        expect(stackTraces.first, isNot(StackTrace.empty));\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => observer.onError(cubit, expectedError, expectedStackTrace),\n        ).called(1);\n      });\n    });\n\n    group('onChange', () {\n      late BlocObserver observer;\n\n      setUpAll(() {\n        registerFallbackValue(FakeBlocBase<dynamic>());\n        registerFallbackValue(FakeChange<dynamic>());\n      });\n\n      setUp(() {\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('is not called for the initial state', () async {\n        final changes = <Change<int>>[];\n        final cubit = CounterCubit(onChangeCallback: changes.add);\n        await cubit.close();\n        expect(changes, isEmpty);\n        // ignore: invalid_use_of_protected_member\n        verifyNever(() => observer.onChange(any(), any()));\n      });\n\n      test('is called with correct change for a single state change', () async {\n        final changes = <Change<int>>[];\n        final cubit = CounterCubit(onChangeCallback: changes.add)..increment();\n        await cubit.close();\n        expect(\n          changes,\n          const [Change<int>(currentState: 0, nextState: 1)],\n        );\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => observer.onChange(\n            cubit,\n            const Change<int>(currentState: 0, nextState: 1),\n          ),\n        ).called(1);\n      });\n\n      test('is called with correct changes for multiple state changes',\n          () async {\n        final changes = <Change<int>>[];\n        final cubit = CounterCubit(onChangeCallback: changes.add)\n          ..increment()\n          ..increment();\n        await cubit.close();\n        expect(\n          changes,\n          const [\n            Change<int>(currentState: 0, nextState: 1),\n            Change<int>(currentState: 1, nextState: 2),\n          ],\n        );\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => observer.onChange(\n            cubit,\n            const Change<int>(currentState: 0, nextState: 1),\n          ),\n        ).called(1);\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => observer.onChange(\n            cubit,\n            const Change<int>(currentState: 1, nextState: 2),\n          ),\n        ).called(1);\n      });\n    });\n\n    group('emit', () {\n      test('throws StateError if cubit is closed', () {\n        var didThrow = false;\n        runZonedGuarded(() {\n          final cubit = CounterCubit();\n          expectLater(\n            cubit.stream,\n            emitsInOrder(<Matcher>[equals(1), emitsDone]),\n          );\n          cubit\n            ..increment()\n            ..close()\n            ..increment();\n        }, (error, _) {\n          didThrow = true;\n          expect(\n            error,\n            isA<StateError>().having(\n              (e) => e.message,\n              'message',\n              'Cannot emit new states after calling close',\n            ),\n          );\n        });\n        expect(didThrow, isTrue);\n      });\n\n      test('emits states in the correct order', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit.increment();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, [1]);\n      });\n\n      test('can emit initial state only once', () async {\n        final states = <int>[];\n        final cubit = SeededCubit(initialState: 0);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..emitState(0)\n          ..emitState(0);\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, [0]);\n      });\n\n      test(\n          'can emit initial state and '\n          'continue emitting distinct states', () async {\n        final states = <int>[];\n        final cubit = SeededCubit(initialState: 0);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..emitState(0)\n          ..emitState(1);\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, [0, 1]);\n      });\n\n      test('does not emit duplicate states', () async {\n        final states = <int>[];\n        final cubit = SeededCubit(initialState: 0);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..emitState(1)\n          ..emitState(1)\n          ..emitState(2)\n          ..emitState(2)\n          ..emitState(3)\n          ..emitState(3);\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, [1, 2, 3]);\n      });\n    });\n\n    group('listen', () {\n      test('returns a StreamSubscription', () {\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen((_) {});\n        expect(subscription, isA<StreamSubscription<int>>());\n        subscription.cancel();\n        cubit.close();\n      });\n\n      test('does not receive current state upon subscribing', () async {\n        final states = <int>[];\n        final cubit = CounterCubit()..stream.listen(states.add);\n        await cubit.close();\n        expect(states, isEmpty);\n      });\n\n      test('receives single async state', () async {\n        final states = <int>[];\n        final cubit = FakeAsyncCounterCubit()..stream.listen(states.add);\n        await cubit.increment();\n        await cubit.close();\n        expect(states, [equals(1)]);\n      });\n\n      test('receives multiple async states', () async {\n        final states = <int>[];\n        final cubit = FakeAsyncCounterCubit()..stream.listen(states.add);\n        await cubit.increment();\n        await cubit.increment();\n        await cubit.increment();\n        await cubit.close();\n        expect(states, [equals(1), equals(2), equals(3)]);\n      });\n\n      test('can call listen multiple times', () async {\n        final states = <int>[];\n        final cubit = CounterCubit()\n          ..stream.listen(states.add)\n          ..stream.listen(states.add)\n          ..increment();\n        await cubit.close();\n        expect(states, [equals(1), equals(1)]);\n      });\n    });\n\n    group('close', () {\n      late MockBlocObserver observer;\n\n      setUp(() {\n        observer = MockBlocObserver();\n        Bloc.observer = observer;\n      });\n\n      test('triggers onClose on observer', () async {\n        final cubit = CounterCubit();\n        await cubit.close();\n        // ignore: invalid_use_of_protected_member\n        verify(() => observer.onClose(cubit)).called(1);\n      });\n\n      test('emits done (sync)', () {\n        final cubit = CounterCubit()..close();\n        expect(cubit.stream, emitsDone);\n      });\n\n      test('emits done (async)', () async {\n        final cubit = CounterCubit();\n        await cubit.close();\n        expect(cubit.stream, emitsDone);\n      });\n    });\n\n    group('isClosed', () {\n      test('returns true after cubit is closed', () async {\n        final cubit = CounterCubit();\n        expect(cubit.isClosed, isFalse);\n        await cubit.close();\n        expect(cubit.isClosed, isTrue);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc/test/cubits/counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({this.onChangeCallback, this.onErrorCallback}) : super(0);\n\n  final void Function(Change<int>)? onChangeCallback;\n  final void Function(Object error, StackTrace stackTrace)? onErrorCallback;\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n\n  @override\n  void onChange(Change<int> change) {\n    super.onChange(change);\n    onChangeCallback?.call(change);\n  }\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    onErrorCallback?.call(error, stackTrace);\n    super.onError(error, stackTrace);\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/cubits/cubits.dart",
    "content": "export 'counter_cubit.dart';\nexport 'fake_async_cubit.dart';\nexport 'seeded_cubit.dart';\n"
  },
  {
    "path": "packages/bloc/test/cubits/fake_async_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass FakeAsyncCounterCubit extends Cubit<int> {\n  FakeAsyncCounterCubit() : super(0);\n\n  Future<void> increment() async {\n    final nextState = await _increment(state);\n    emit(nextState);\n  }\n\n  Future<int> _increment(int value) async {\n    return value + 1;\n  }\n}\n"
  },
  {
    "path": "packages/bloc/test/cubits/seeded_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass SeededCubit<T> extends Cubit<T> {\n  SeededCubit({required T initialState}) : super(initialState);\n\n  void emitState(T state) => emit(state);\n}\n"
  },
  {
    "path": "packages/bloc/test/transition_test.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport 'package:test/test.dart';\n\n@immutable\nabstract class TransitionEvent {}\n\n@immutable\nabstract class TransitionState {}\n\nclass SimpleTransitionEvent extends TransitionEvent {}\n\nclass SimpleTransitionState extends TransitionState {}\n\nclass CounterEvent extends TransitionEvent {\n  CounterEvent(this.eventData);\n\n  final String eventData;\n\n  @override\n  bool operator ==(Object other) =>\n      identical(this, other) ||\n      other is CounterEvent &&\n          runtimeType == other.runtimeType &&\n          eventData == other.eventData;\n\n  @override\n  int get hashCode => Object.hashAll([eventData]);\n}\n\nclass CounterState extends TransitionState {\n  CounterState(this.count);\n\n  final int count;\n\n  @override\n  bool operator ==(Object other) =>\n      identical(this, other) ||\n      other is CounterState &&\n          runtimeType == other.runtimeType &&\n          count == other.count;\n\n  @override\n  int get hashCode => Object.hashAll([count]);\n}\n\nvoid main() {\n  group('Change Tests', () {\n    group('constructor', () {\n      test(\n          'should return normally when initialized with '\n          'all required parameters', () {\n        expect(\n          () => const Change<int>(currentState: 0, nextState: 1),\n          returnsNormally,\n        );\n      });\n    });\n\n    group('== operator', () {\n      test('should return true if 2 Changes are equal', () {\n        const changeA = Change<int>(currentState: 0, nextState: 1);\n        const changeB = Change<int>(currentState: 0, nextState: 1);\n\n        expect(changeA == changeB, isTrue);\n      });\n\n      test('should return false if 2 Changes are not equal', () {\n        const changeA = Change<int>(currentState: 0, nextState: 1);\n        const changeB = Change<int>(currentState: 0, nextState: -1);\n\n        expect(changeA == changeB, isFalse);\n      });\n    });\n\n    group('hashCode', () {\n      test('should return correct hashCode', () {\n        const change = Change<int>(currentState: 0, nextState: 1);\n        expect(\n          change.hashCode,\n          Object.hashAll([change.currentState, change.nextState]),\n        );\n      });\n    });\n\n    group('toString', () {\n      test('should return correct string representation of Change', () {\n        const change = Change<int>(currentState: 0, nextState: 1);\n\n        expect(\n          change.toString(),\n          'Change { currentState: ${change.currentState}, '\n          'nextState: ${change.nextState} }',\n        );\n      });\n    });\n  });\n\n  group('Transition Tests', () {\n    group('constructor', () {\n      test(\n          'should not throw assertion error when initialized '\n          'with a null currentState', () {\n        expect(\n          () => Transition<TransitionEvent, TransitionState?>(\n            currentState: null,\n            event: SimpleTransitionEvent(),\n            nextState: SimpleTransitionState(),\n          ),\n          isNot(throwsA(isA<AssertionError>())),\n        );\n      });\n\n      test(\n          'should not throw assertion error when initialized with a null event',\n          () {\n        expect(\n          () => Transition<TransitionEvent?, TransitionState>(\n            currentState: SimpleTransitionState(),\n            event: null,\n            nextState: SimpleTransitionState(),\n          ),\n          isNot(throwsA(isA<AssertionError>())),\n        );\n      });\n\n      test(\n          'should not throw assertion error '\n          'when initialized with a null nextState', () {\n        expect(\n          () => Transition<TransitionEvent, TransitionState?>(\n            currentState: SimpleTransitionState(),\n            event: SimpleTransitionEvent(),\n            nextState: null,\n          ),\n          isNot(throwsA(isA<AssertionError>())),\n        );\n      });\n\n      test(\n          'should not throw assertion error when initialized with '\n          'all required parameters', () {\n        try {\n          Transition<TransitionEvent, TransitionState>(\n            currentState: SimpleTransitionState(),\n            event: SimpleTransitionEvent(),\n            nextState: SimpleTransitionState(),\n          );\n        } catch (_) {\n          fail(\n            'should not throw error when initialized '\n            'with all required parameters',\n          );\n        }\n      });\n    });\n\n    group('== operator', () {\n      test('should return true if 2 Transitions are equal', () {\n        final transitionA = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(0),\n          event: CounterEvent('increment'),\n          nextState: CounterState(1),\n        );\n        final transitionB = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(0),\n          event: CounterEvent('increment'),\n          nextState: CounterState(1),\n        );\n\n        expect(transitionA == transitionB, true);\n      });\n\n      test('should return false if 2 Transitions are not equal', () {\n        final transitionA = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(0),\n          event: CounterEvent('increment'),\n          nextState: CounterState(1),\n        );\n        final transitionB = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(1),\n          event: CounterEvent('decrement'),\n          nextState: CounterState(0),\n        );\n\n        expect(transitionA == transitionB, false);\n      });\n    });\n\n    group('hashCode', () {\n      test('should return correct hashCode', () {\n        final transition = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(0),\n          event: CounterEvent('increment'),\n          nextState: CounterState(1),\n        );\n        expect(\n          transition.hashCode,\n          Object.hashAll([\n            transition.currentState,\n            transition.event,\n            transition.nextState,\n          ]),\n        );\n      });\n    });\n\n    group('toString', () {\n      test('should return correct string representation for Transition', () {\n        final transition = Transition<CounterEvent, CounterState>(\n          currentState: CounterState(0),\n          event: CounterEvent('increment'),\n          nextState: CounterState(1),\n        );\n\n        expect(\n            transition.toString(),\n            'Transition { currentState: ${transition.currentState}, '\n            'event: ${transition.event}, '\n            'nextState: ${transition.nextState} }');\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/.gitignore",
    "content": "# Files and directories created by pub\n.dart_tool/\n.packages\npubspec.lock\n\n# Conventional directory for build outputs\nbuild/\n\n# Directory created by dartdoc\ndoc/api/\n\n# Temporary Files\n.tmp/\n\n# Files generated during tests\n.test_coverage.dart\ncoverage/"
  },
  {
    "path": "packages/bloc_concurrency/CHANGELOG.md",
    "content": "# 0.3.0\n\n- chore(deps): upgrade to `package:bloc v9.0.0`\n- chore: update sponsors\n- chore: add `funding` to `pubspec.yaml`\n\n# 0.2.5\n\n- docs: improve diagrams\n- chore: update copyright year\n- chore: update sponsors\n\n# 0.2.4\n\n- chore: update sponsors\n\n# 0.2.3\n\n- chore: fix `require_trailing_commas`\n- chore(deps): upgrade to `package:mocktail v1.0.0`\n- chore: add `topics` to `pubspec.yaml`\n\n# 0.2.2\n\n- docs: upgrade to Dart 3\n- refactor: standardize analysis_options\n\n# 0.2.1\n\n- chore: add screenshots to `pubspec.yaml`\n- refactor: upgrade to Dart 2.19\n  - deps: upgrade to `very_good_analysis 3.1.0`\n- docs: update example to follow naming conventions\n\n# 0.2.0\n\n- feat: upgrade to `bloc: ^8.0.0`\n\n# 0.2.0-dev.2\n\n- feat: upgrade to `bloc: ^8.0.0-dev.5`\n\n# 0.2.0-dev.1\n\n- feat: upgrade to `bloc: ^8.0.0-dev.3`\n\n# 0.1.0\n\n- feat: upgrade to `bloc: ^7.2.0`\n\n# 0.1.0-dev.2\n\n- feat: upgrade to `bloc: ^7.2.0-dev.2`\n\n# 0.1.0-dev.1\n\n- feat: initial development release\n  - includes `EventTransformer` options:\n    - `concurrent`: process events concurrently\n    - `sequential`: process events sequentially\n    - `droppable`: ignore any events added while an event is processing\n    - `restartable`: process only the latest event and cancel previous handlers\n"
  },
  {
    "path": "packages/bloc_concurrency/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "packages/bloc_concurrency/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_concurrency.png\" height=\"100\" alt=\"Bloc Concurrency\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/bloc_concurrency\"><img src=\"https://img.shields.io/pub/v/bloc_concurrency.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nA Dart package that exposes custom event transformers inspired by [ember concurrency](https://github.com/machty/ember-concurrency). Built to work with [bloc](https://pub.dev/packages/bloc).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Event Transformers\n\n![Event Transformers](https://raw.githubusercontent.com/felangel/bloc/master/assets/diagrams/bloc_concurrency.png)\n\n`bloc_concurrency` provides an opinionated set of event transformers:\n\n- `concurrent` - process events concurrently\n- `sequential` - process events sequentially\n- `droppable` - ignore any events added while an event is processing\n- `restartable` - process only the latest event and cancel previous event handlers\n\n## Usage\n\n```dart\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>(\n      (event, emit) async {\n        await Future.delayed(Duration(seconds: 1));\n        emit(state + 1);\n      },\n      /// Specify a custom event transformer from `package:bloc_concurrency`\n      /// in this case events will be processed sequentially.\n      transformer: sequential(),\n    );\n  }\n}\n```\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/bloc_concurrency/analysis_options.yaml",
    "content": "include: \n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\nanalyzer:\n  exclude:\n    - \"**/version.dart\"\n"
  },
  {
    "path": "packages/bloc_concurrency/example/main.dart",
    "content": "// ignore_for_file: avoid_print, prefer_file_naming_conventions\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\n\nFuture<void> tick() => Future<void>.delayed(Duration.zero);\n\nFuture<void> main() async {\n  /// Create a `CounterBloc` instance.\n  final bloc = CounterBloc();\n\n  /// Subscribe to state changes and print each state.\n  final subscription = bloc.stream.listen(print);\n\n  /// Interact with the `bloc` to trigger `state` changes.\n  bloc.add(CounterIncrementPressed());\n  await tick();\n  bloc.add(CounterIncrementPressed());\n  await tick();\n  bloc.add(CounterIncrementPressed());\n  await tick();\n\n  /// Wait 1 second...\n  await Future<void>.delayed(const Duration(seconds: 1));\n\n  /// Close the `bloc` when it is no longer needed.\n  await bloc.close();\n\n  /// Cancel the subscription.\n  await subscription.cancel();\n}\n\n/// The events which `CounterBloc` will react to.\nabstract class CounterEvent {}\n\n/// Notifies bloc to increment state.\nclass CounterIncrementPressed extends CounterEvent {}\n\n/// A `CounterBloc` which handles converting `CounterEvent`s into `int`s.\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  /// The initial state of the `CounterBloc` is 0.\n  CounterBloc() : super(0) {\n    /// When a `CounterIncrementPressed` event is added,\n    /// the current `state` of the bloc is accessed via the `state` property\n    /// and a new state is emitted via `emit`.\n    on<CounterIncrementPressed>(\n      (event, emit) async {\n        await Future<void>.delayed(const Duration(seconds: 1));\n        emit(state + 1);\n      },\n\n      /// Specify a custom event transformer\n      /// in this case events will be processed sequentially.\n      transformer: sequential(),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/lib/bloc_concurrency.dart",
    "content": "/// Custom event transformers inspired by ember concurrency.\n/// Built to be used with the [bloc](https://pub.dev/packages/bloc) state management package.\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary bloc_concurrency;\n\nexport 'src/concurrent.dart';\nexport 'src/droppable.dart';\nexport 'src/restartable.dart';\nexport 'src/sequential.dart';\n"
  },
  {
    "path": "packages/bloc_concurrency/lib/src/concurrent.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\n/// Process events concurrently.\n///\n/// **Note**: there may be event handler overlap and state changes will occur\n/// as soon as they are emitted. This means that states may be emitted in\n/// an order that does not match the order in which the corresponding events\n/// were added.\nEventTransformer<Event> concurrent<Event>() {\n  return (events, mapper) => events.concurrentAsyncExpand(mapper);\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/lib/src/droppable.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\n/// Process only one event and ignore (drop) any new events\n/// until the current event is done.\n///\n/// **Note**: dropped events never trigger the event handler.\nEventTransformer<Event> droppable<Event>() {\n  return (events, mapper) {\n    return events.transform(_ExhaustMapStreamTransformer(mapper));\n  };\n}\n\nclass _ExhaustMapStreamTransformer<T> extends StreamTransformerBase<T, T> {\n  _ExhaustMapStreamTransformer(this.mapper);\n\n  final EventMapper<T> mapper;\n\n  @override\n  Stream<T> bind(Stream<T> stream) {\n    late StreamSubscription<T> subscription;\n    StreamSubscription<T>? mappedSubscription;\n\n    final controller = StreamController<T>(\n      onCancel: () async {\n        await mappedSubscription?.cancel();\n        return subscription.cancel();\n      },\n      sync: true,\n    );\n\n    subscription = stream.listen(\n      (data) {\n        if (mappedSubscription != null) return;\n        final Stream<T> mappedStream;\n\n        mappedStream = mapper(data);\n        mappedSubscription = mappedStream.listen(\n          controller.add,\n          onError: controller.addError,\n          onDone: () => mappedSubscription = null,\n        );\n      },\n      onError: controller.addError,\n      onDone: () => mappedSubscription ?? controller.close(),\n    );\n\n    return controller.stream;\n  }\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/lib/src/restartable.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:stream_transform/stream_transform.dart';\n\n/// Process only one event by cancelling any pending events and\n/// processing the new event immediately.\n///\n/// Avoid using [restartable] if you expect an event to have\n/// immediate results -- it should only be used with asynchronous APIs.\n///\n/// **Note**: there is no event handler overlap and any currently running tasks\n/// will be aborted if a new event is added before a prior one completes.\nEventTransformer<Event> restartable<Event>() {\n  return (events, mapper) => events.switchMap(mapper);\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/lib/src/sequential.dart",
    "content": "import 'package:bloc/bloc.dart';\n\n/// Process events one at a time by maintaining a queue of added events\n/// and processing the events sequentially.\n///\n/// **Note**: there is no event handler overlap and every event is guaranteed\n/// to be handled in the order it was received.\nEventTransformer<Event> sequential<Event>() {\n  return (events, mapper) => events.asyncExpand(mapper);\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/pubspec.yaml",
    "content": "name: bloc_concurrency\ndescription: Custom event transformers inspired by ember concurrency. Built to be used with the bloc state management package.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://github.com/felangel/bloc\ndocumentation: https://bloclibrary.dev\ntopics: [bloc, concurrency, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  stream_transform: ^2.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  mocktail: ^1.0.0\n  test: ^1.17.0\n\nscreenshots:\n  - description: The bloc concurrency package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/bloc_concurrency/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/bloc_concurrency/test/src/concurrent_test.dart",
    "content": "import 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:test/test.dart';\n\nimport 'helpers.dart';\n\nvoid main() {\n  group('concurrent', () {\n    test('processes events concurrently by default', () async {\n      final states = <int>[];\n      final bloc = CounterBloc(concurrent())\n        ..stream.listen(states.add)\n        ..add(Increment())\n        ..add(Increment())\n        ..add(Increment());\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n\n      await bloc.close();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/test/src/droppable_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:test/test.dart';\n\nimport 'helpers.dart';\n\nvoid main() {\n  group('droppable', () {\n    test('processes only the current event and ignores remaining', () async {\n      final states = <int>[];\n      final bloc = CounterBloc(droppable())\n        ..stream.listen(states.add)\n        ..add(Increment())\n        ..add(Increment())\n        ..add(Increment());\n\n      await tick();\n\n      expect(bloc.onCalls, equals([Increment()]));\n\n      await wait();\n\n      expect(bloc.onEmitCalls, equals([Increment()]));\n      expect(states, equals([1]));\n\n      bloc\n        ..add(Increment())\n        ..add(Increment())\n        ..add(Increment());\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment()]),\n      );\n      expect(states, equals([1, 2]));\n\n      bloc\n        ..add(Increment())\n        ..add(Increment())\n        ..add(Increment());\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n      expect(states, equals([1, 2, 3]));\n\n      await bloc.close();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n    });\n\n    test('cancels the mapped subscription when it is active.', () async {\n      final states = <int>[];\n      final controller = StreamController<int>.broadcast();\n      final stream = droppable<int>()(controller.stream, (x) async* {\n        await wait();\n        yield x;\n      });\n\n      final subscription = stream.listen(states.add);\n\n      controller.add(0);\n\n      await wait();\n\n      expect(states, isEmpty);\n      expect(controller.hasListener, isTrue);\n\n      await subscription.cancel();\n\n      expect(states, isEmpty);\n      expect(controller.hasListener, isFalse);\n\n      await controller.close();\n\n      expect(states, isEmpty);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/test/src/helpers.dart",
    "content": "// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes, lines_longer_than_80_chars, prefer_file_naming_conventions, avoid_public_fields\nimport 'package:bloc/bloc.dart';\n\nabstract class CounterEvent {}\n\nclass Increment extends CounterEvent {\n  @override\n  bool operator ==(Object value) {\n    if (identical(this, value)) return true;\n    return value is Increment;\n  }\n\n  @override\n  int get hashCode => 0;\n}\n\nconst delay = Duration(milliseconds: 30);\n\nFuture<void> wait() => Future.delayed(delay);\nFuture<void> tick() => Future.delayed(Duration.zero);\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(EventTransformer<Increment> transformer) : super(0) {\n    on<Increment>(\n      (event, emit) {\n        onCalls.add(event);\n        return Future<void>.delayed(delay, () {\n          if (emit.isDone) return;\n          onEmitCalls.add(event);\n          emit(state + 1);\n        });\n      },\n      transformer: transformer,\n    );\n  }\n\n  final onCalls = <CounterEvent>[];\n  final onEmitCalls = <CounterEvent>[];\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/test/src/restartable_test.dart",
    "content": "import 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:test/test.dart';\n\nimport 'helpers.dart';\n\nvoid main() {\n  group('restartable', () {\n    test('processes only the latest event and cancels remaining', () async {\n      final states = <int>[];\n      final bloc = CounterBloc(restartable())..stream.listen(states.add);\n      Future<void> addEvents() async {\n        const spacer = Duration(milliseconds: 10);\n        await Future<void>.delayed(spacer);\n        bloc.add(Increment());\n        await Future<void>.delayed(spacer);\n        bloc.add(Increment());\n        await Future<void>.delayed(spacer);\n        bloc.add(Increment());\n        await Future<void>.delayed(spacer);\n      }\n\n      await tick();\n      await addEvents();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(bloc.onEmitCalls, equals([Increment()]));\n      expect(states, equals([1]));\n\n      await tick();\n      await addEvents();\n\n      expect(\n        bloc.onCalls,\n        equals([\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n        ]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2]));\n\n      await tick();\n      await addEvents();\n\n      expect(\n        bloc.onCalls,\n        equals([\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n        ]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n\n      await bloc.close();\n\n      expect(\n        bloc.onCalls,\n        equals([\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n          Increment(),\n        ]),\n      );\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_concurrency/test/src/sequential_test.dart",
    "content": "import 'package:bloc_concurrency/bloc_concurrency.dart';\nimport 'package:test/test.dart';\n\nimport 'helpers.dart';\n\nvoid main() {\n  group('sequential', () {\n    test('processes events one at a time', () async {\n      final states = <int>[];\n      final bloc = CounterBloc(sequential())\n        ..stream.listen(states.add)\n        ..add(Increment())\n        ..add(Increment())\n        ..add(Increment());\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment()]),\n      );\n      expect(states, equals([1]));\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2]));\n\n      await tick();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      await wait();\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n\n      await bloc.close();\n\n      expect(\n        bloc.onCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(\n        bloc.onEmitCalls,\n        equals([Increment(), Increment(), Increment()]),\n      );\n\n      expect(states, equals([1, 2, 3]));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/CHANGELOG.md",
    "content": "# 0.4.0\n\n- feat: upgrade to latest analyzer frontend\n\n# 0.3.7\n\n- fix: `analysis_options.yaml` resolution in pub workspace\n- deps: support latest stable Flutter version.\n\n# 0.3.6\n\n- feat: upgrade to latest analyzer frontend\n\n# 0.3.5\n\n- fix: adjust upper bound for `_fe_analyzer_shared`\n\n# 0.3.4\n\n- chore: various dependency upgrades\n\n# 0.3.3\n\n- fix: ignore whitespace in ignore comments\n- docs: add `bloc_lint` badge to `README`\n\n# 0.3.2\n\n- feat: add [prefer_build_context_extensions](https://bloclibrary.dev/lint-rules/prefer_build_context_extensions)\n\n# 0.3.1\n\n- fix: adjust lower bound for `_fe_analyzer_shared`\n\n# 0.3.0\n\n- fix: widen supported version ranges for `_fe_analyzer_shared`\n- feat: add [avoid_build_context_extensions](https://bloclibrary.dev/lint-rules/avoid_build_context_extensions)\n- feat: add [prefer_file_naming_conventions](https://bloclibrary.dev/lint-rules/prefer_file_naming_conventions)\n\n# 0.2.1\n\n- feat: ignore dot directories\n- fix: include bloc/cubit instances defined in any file\n- chore: various dependency upgrades\n\n# 0.2.0\n\n- chore: stable `0.2.0` release 🎉\n- chore: various dependency upgrades\n- fix: ignore `.fvm`\n\n# 0.2.0-dev.6\n\n- fix: `prefer_void_public_cubit_methods` false positive when using switch expressions\n- fix: `avoid_public_bloc_methods` false positive when using switch expressions\n\n# 0.2.0-dev.5\n\n- fix: `avoid_public_bloc_methods` false positive when using switch expressions\n\n# 0.2.0-dev.4\n\n- fix: various bug fixes for Windows\n\n# 0.2.0-dev.3\n\n- feat: add support for `// ignore`\n- feat: add support for `// ignore_for_file`\n\n# 0.2.0-dev.2\n\n- feat: add [prefer_void_public_cubit_methods](https://bloclibrary.dev/lint-rules/prefer_void_public_cubit_methods)\n\n# 0.2.0-dev.1\n\n- fix: package resolution in `include` on windows\n- fix: uri resolution on windows\n- docs: improvements to `README.md`\n\n# 0.2.0-dev.0\n\n- Full rewrite of `pkg:bloc_lint`\n- Supported Lint Rules\n  - [avoid_flutter_imports](https://bloclibrary.dev/lint-rules/avoid_flutter_imports)\n  - [avoid_public_bloc_methods](https://bloclibrary.dev/lint-rules/avoid_public_bloc_methods)\n  - [avoid_public_fields](https://bloclibrary.dev/lint-rules/avoid_public_fields)\n  - [prefer_bloc](https://bloclibrary.dev/lint-rules/prefer_bloc)\n  - [prefer_cubit](https://bloclibrary.dev/lint-rules/prefer_cubit)\n\n# 0.1.0\n\n- Initial experimental community release using `pkg:custom_lint`\n"
  },
  {
    "path": "packages/bloc_lint/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/bloc_lint/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_lint.png\" height=\"100\" alt=\"Bloc\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/pub/v/bloc_lint.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nOfficial lint rules for development when using the bloc state management library.\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\nThis package is built to work with:\n\n- [bloc](https://pub.dev/packages/bloc)\n- [bloc_tools](https://pub.dev/packages/bloc_tools)\n- [flutter_bloc](https://pub.dev/packages/flutter_bloc)\n- [angular_bloc](https://pub.dev/packages/angular_bloc)\n- [bloc_concurrency](https://pub.dev/packages/bloc_concurrency)\n- [bloc_test](https://pub.dev/packages/bloc_test)\n- [hydrated_bloc](https://pub.dev/packages/hydrated_bloc)\n- [replay_bloc](https://pub.dev/packages/replay_bloc)\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Quick Start\n\n1. Install the [bloc command-line tools](https://pub.dev/packages/bloc_tools)\n\n   ```sh\n   dart pub global activate bloc_tools\n   ```\n\n2. Install the [bloc_lint](https://pub.dev/packages/bloc_lint) package\n\n   ```sh\n   dart pub add --dev bloc_lint\n   ```\n\n3. Add an `analysis_options.yaml` to the root of your project with the\n   recommended rules\n\n   ```yaml\n   include: package:bloc_lint/recommended.yaml\n   ```\n\n4. Run the linter\n\n   ```sh\n   bloc lint .\n   ```\n\nFor more information, check out the [official documentation](https://bloclibrary.dev/lint)\n\n## Recommended Lint Rules\n\n- [avoid_flutter_imports](https://bloclibrary.dev/lint-rules/avoid_flutter_imports)\n- [avoid_public_bloc_methods](https://bloclibrary.dev/lint-rules/avoid_public_bloc_methods)\n- [avoid_public_fields](https://bloclibrary.dev/lint-rules/avoid_public_fields)\n- [prefer_file_naming_conventions](https://bloclibrary.dev/lint-rules/prefer_file_naming_conventions)\n- [prefer_void_public_cubit_methods](https://bloclibrary.dev/lint-rules/prefer_void_public_cubit_methods)\n\n## All Lint Rules\n\n- [avoid_build_context_extensions](https://bloclibrary.dev/lint-rules/avoid_build_context_extensions)\n- [avoid_flutter_imports](https://bloclibrary.dev/lint-rules/avoid_flutter_imports)\n- [avoid_public_bloc_methods](https://bloclibrary.dev/lint-rules/avoid_public_bloc_methods)\n- [avoid_public_fields](https://bloclibrary.dev/lint-rules/avoid_public_fields)\n- [prefer_bloc](https://bloclibrary.dev/lint-rules/prefer_bloc)\n- [prefer_build_context_extensions](https://bloclibrary.dev/lint-rules/prefer_build_context_extensions)\n- [prefer_cubit](https://bloclibrary.dev/lint-rules/prefer_cubit)\n- [prefer_file_naming_conventions](https://bloclibrary.dev/lint-rules/prefer_file_naming_conventions)\n- [prefer_void_public_cubit_methods](https://bloclibrary.dev/lint-rules/prefer_void_public_cubit_methods)\n\n## Dart Versions\n\n- Dart 3: >= 3.7.0\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/bloc_lint/analysis_options.yaml",
    "content": "include: ../../analysis_options.yaml\n"
  },
  {
    "path": "packages/bloc_lint/build.yaml",
    "content": "# See https://github.com/dart-lang/build/tree/master/build_web_compilers#configuration\ntargets:\n  $default:\n    builders:\n      source_gen|combining_builder:\n        options:\n          ignore_for_file:\n            - implicit_dynamic_parameter\n            - require_trailing_commas\n            - cast_nullable_to_non_nullable\n            - lines_longer_than_80_chars\n            - strict_raw_type\n      json_serializable:\n        options:\n          any_map: true\n          disallow_unrecognized_keys: false\n          field_rename: kebab\n          include_if_null: false\n          checked: true\n          explicit_to_json: true\n          create_to_json: true\n"
  },
  {
    "path": "packages/bloc_lint/example/main.dart",
    "content": "// ignore_for_file: unused_local_variable\nimport 'package:bloc_lint/bloc_lint.dart';\n\n// Usage: dart run main.dart ./path/to/analyze\nvoid main(List<String> args) {\n  // Analyze the provided file or directory and report all diagnostics.\n  final diagnostics = const Linter().analyze(uri: Uri.parse(args.first));\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/all.yaml",
    "content": "bloc:\n  rules:\n    - avoid_build_context_extensions\n    - avoid_flutter_imports\n    - avoid_public_bloc_methods\n    - avoid_public_fields\n    - prefer_bloc\n    - prefer_build_context_extensions\n    - prefer_cubit\n    - prefer_file_naming_conventions\n    - prefer_void_public_cubit_methods\n"
  },
  {
    "path": "packages/bloc_lint/lib/bloc_lint.dart",
    "content": "export 'package:_fe_analyzer_shared/src/parser/parser.dart'\n    show DeclarationKind, IdentifierContext, Listener;\nexport 'package:_fe_analyzer_shared/src/scanner/token.dart'\n    show BeginToken, Keyword, Token, TokenType;\n\nexport 'src/diagnostic.dart' show Diagnostic, Severity;\nexport 'src/lint_rule.dart' show LintRule, LintRuleBuilder;\nexport 'src/linter.dart' show LintContext, Linter;\nexport 'src/rules/rules.dart'\n    show\n        AvoidBuildContextExtensions,\n        AvoidFlutterImports,\n        AvoidPublicBlocMethods,\n        AvoidPublicFields,\n        PreferBloc,\n        PreferBuildContextExtensions,\n        PreferCubit,\n        PreferFileNamingConventions,\n        PreferVoidPublicCubitMethods;\nexport 'src/text_document.dart'\n    show\n        Position,\n        Range,\n        TextDocument,\n        TextDocumentType,\n        TextDocumentTypeX,\n        TextDocumentX;\n"
  },
  {
    "path": "packages/bloc_lint/lib/recommended.yaml",
    "content": "bloc:\n  rules:\n    - avoid_flutter_imports\n    - avoid_public_bloc_methods\n    - avoid_public_fields\n    - prefer_file_naming_conventions\n    - prefer_void_public_cubit_methods\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/analysis_options.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:bloc_lint/src/diagnostic.dart';\nimport 'package:bloc_lint/src/env.dart';\nimport 'package:checked_yaml/checked_yaml.dart';\nimport 'package:collection/collection.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:path/path.dart' as p;\n\npart 'analysis_options.g.dart';\n\n/// {@template analysis_options}\n/// The analysis_options object which contains the parsed\n/// yaml as well as the file path.\n/// {@endtemplate}\nclass AnalysisOptions {\n  /// {@macro analysis_options}\n  const AnalysisOptions({required this.file, required this.yaml});\n\n  /// Parse the [file] as an [AnalysisOptions].\n  factory AnalysisOptions.parse(File file) {\n    final content = file.readAsStringSync();\n    final yaml = checkedYamlDecode(\n      content,\n      (m) => AnalysisOptionsYaml.fromJson(m!),\n    );\n    return AnalysisOptions(file: file, yaml: yaml);\n  }\n\n  /// Resolve the provided options by recursively joining all includes.\n  factory AnalysisOptions.resolve(File file) {\n    AnalysisOptions recursiveResolver(\n      File file,\n      Directory root,\n      Map<String, dynamic> yaml,\n    ) {\n      final options = AnalysisOptions.parse(file);\n      for (final include in options.yaml.include ?? <String>[]) {\n        final isPackageInclude = include.startsWith('package:');\n        final parsedOptions =\n            isPackageInclude\n                ? AnalysisOptions.tryInclude(include, cwd: root)\n                : AnalysisOptions.tryParse(\n                  File(p.normalize(p.join(options.file.parent.path, include))),\n                );\n        if (parsedOptions == null) continue;\n        final resolved = recursiveResolver(\n          parsedOptions.file,\n          isPackageInclude ? root : file.parent,\n          yaml,\n        );\n        // Accumulate the merged analysis_options yaml content\n        // ignore: parameter_assignments\n        yaml = _merge(yaml, resolved.yaml.toJson());\n      }\n\n      return AnalysisOptions(\n        file: options.file,\n        yaml: AnalysisOptionsYaml.fromJson(_merge(yaml, options.yaml.toJson())),\n      );\n    }\n\n    return recursiveResolver(file, file.parent, {});\n  }\n\n  /// Try to parse [file] and return `null` if parsing fails.\n  static AnalysisOptions? tryParse(File file) {\n    try {\n      return AnalysisOptions.parse(file);\n    } on Exception {\n      return null;\n    }\n  }\n\n  /// Try to resolve [file] and return `null` if resolving fails.\n  static AnalysisOptions? tryResolve(File file) {\n    try {\n      return AnalysisOptions.resolve(file);\n    } on Exception {\n      return null;\n    }\n  }\n\n  /// Try to parse an analysis_options yaml referenced by the [include].\n  static AnalysisOptions? tryInclude(String include, {required Directory cwd}) {\n    final packagePrefix = include.split('/').first;\n    final packageName = packagePrefix.split('package:').last;\n    final packageConfigFile = findPackageConfigFile(cwd);\n    if (packageConfigFile == null) return null;\n    final packageConfig =\n        json.decode(packageConfigFile.readAsStringSync())\n            as Map<String, dynamic>;\n    final packages = packageConfig['packages'] as List;\n    final package = packages.cast<Map<String, dynamic>>().firstWhereOrNull(\n      (entry) => entry['name'] == packageName,\n    );\n    if (package == null) return null;\n    final fullUri = Uri.tryParse(\n      p.join(package['rootUri'] as String, package['packageUri'] as String),\n    );\n    if (fullUri == null) return null;\n    var path = include.split(packagePrefix).last;\n    if (path.startsWith(p.separator)) path = path.substring(1);\n    var resolvedPath = p.fromUri(fullUri) + path;\n    if (!p.isAbsolute(resolvedPath)) {\n      resolvedPath = p.join(packageConfigFile.parent.path, resolvedPath);\n    }\n    return AnalysisOptions.tryParse(File(p.normalize(resolvedPath)));\n  }\n\n  /// The `analysis_options.yaml` file.\n  final File file;\n\n  /// The parsed yaml contents.\n  final AnalysisOptionsYaml yaml;\n}\n\n/// {@template analysis_options_yaml}\n/// The `analysis_options.yaml` configuration.\n/// {@endtemplate}\n@JsonSerializable()\nclass AnalysisOptionsYaml {\n  /// {@macro analysis_options_yaml}\n  AnalysisOptionsYaml({this.include, this.analyzer, this.bloc});\n\n  /// Converts [Map] to [AnalysisOptionsYaml]\n  factory AnalysisOptionsYaml.fromJson(Map<dynamic, dynamic> json) =>\n      _$AnalysisOptionsYamlFromJson(json);\n\n  /// Converts [AnalysisOptionsYaml] to [Map].\n  Map<String, dynamic> toJson() => _$AnalysisOptionsYamlToJson(this);\n\n  /// The list of shared analysis options.\n  @IncludeConverter()\n  final List<String>? include;\n\n  /// The dart analyzer options.\n  final AnalyzerOptions? analyzer;\n\n  /// The bloc lint options.\n  final BlocLintOptions? bloc;\n}\n\n/// {@template analyzer_options}\n/// Dart analyzer options.\n/// {@endtemplate}\n@JsonSerializable()\nclass AnalyzerOptions {\n  /// {@macro analyzer_options}\n  const AnalyzerOptions({this.exclude = const <String>[]});\n\n  /// Converts [Map] to [AnalyzerOptions]\n  factory AnalyzerOptions.fromJson(Map<dynamic, dynamic> json) =>\n      _$AnalyzerOptionsFromJson(json);\n\n  /// Converts [AnalyzerOptions] to [Map].\n  Map<String, dynamic> toJson() => _$AnalyzerOptionsToJson(this);\n\n  /// List of files, directories, or globs to exclude.\n  final List<String> exclude;\n}\n\n/// {@template bloc_lint_options}\n/// Bloc-specific lint options.\n/// {@endtemplate}\n@JsonSerializable()\nclass BlocLintOptions {\n  /// {@macro bloc_lint_options}\n  const BlocLintOptions({required this.rules});\n\n  /// Converts [Map] to [BlocLintOptions].\n  factory BlocLintOptions.fromJson(Map<dynamic, dynamic> json) =>\n      _$BlocLintOptionsFromJson(json);\n\n  /// Converts [BlocLintOptions] to [Map].\n  Map<String, dynamic> toJson() => _$BlocLintOptionsToJson(this);\n\n  /// The configured bloc lint rules.\n  @RulesConverter()\n  final Map<String, LinterRuleState>? rules;\n}\n\n@JsonEnum(valueField: 'value')\n/// The state of a given linter rule.\nenum LinterRuleState {\n  /// The rule is enabled with a default severity.\n  enabled('true'),\n\n  /// The rule is disabled.\n  disabled('false'),\n\n  /// The rule is enabled with a severity of info.\n  info('info'),\n\n  /// The rule is enabled with a severity of error.\n  error('error'),\n\n  /// The rule is enabled with a severity of warning.\n  warning('warning'),\n\n  /// The rule is enabled with a severity of hint.\n  hint('hint');\n\n  const LinterRuleState(this.value);\n\n  /// The underlying value.\n  final String value;\n\n  /// Parse the provided [value] as a [LinterRuleState].\n  static LinterRuleState fromJson(String value) {\n    return LinterRuleState.values.firstWhere((v) => v.value == value);\n  }\n\n  /// Converts the [LinterRuleState] to a json encoded value.\n  String toJson() => value;\n}\n\n/// {@template include_converter}\n/// Json Converter for analysis_options includes (`List<String>`).\n/// {@endtemplate}\nclass IncludeConverter implements JsonConverter<List<String>?, dynamic> {\n  /// {@macro include_converter}\n  const IncludeConverter();\n\n  @override\n  dynamic toJson(List<String>? value) => value;\n\n  @override\n  List<String>? fromJson(dynamic value) {\n    if (value is String) return [value];\n    if (value is List) return value.cast<String>();\n    return null;\n  }\n}\n\n/// {@template rules_converter}\n/// Json Converter for lint rules (`Map<String, LinterRuleState>`).\n/// {@endtemplate}\nclass RulesConverter\n    implements JsonConverter<Map<String, LinterRuleState>?, dynamic> {\n  /// {@macro rules_converter}\n  const RulesConverter();\n\n  @override\n  dynamic toJson(Map<String, LinterRuleState>? value) {\n    return value?.map<String, String>(\n      (key, value) => MapEntry(key, value.toJson()),\n    );\n  }\n\n  @override\n  Map<String, LinterRuleState>? fromJson(dynamic value) {\n    final dynamic decoded = value is String ? json.decode(value) : value;\n    if (decoded is List) {\n      return <String, LinterRuleState>{\n        for (final v in decoded) v as String: LinterRuleState.enabled,\n      };\n    }\n    if (decoded is Map) {\n      return decoded.map(\n        (dynamic key, dynamic value) => MapEntry(\n          key as String,\n          LinterRuleState.fromJson(decoded[key].toString()),\n        ),\n      );\n    }\n    return null;\n  }\n}\n\n/// Extension methods on [LinterRuleState].\nextension LinterRuleStateX on LinterRuleState {\n  /// Whether the rule is enabled.\n  bool get isEnabled => this != LinterRuleState.disabled;\n\n  /// Whether the rule is disabled.\n  bool get isDisabled => !isEnabled;\n\n  /// Converts the rule to a [Severity] using the [fallback].\n  Severity? toSeverity({Severity? fallback}) {\n    return switch (this) {\n      LinterRuleState.enabled => fallback,\n      LinterRuleState.disabled => null,\n      LinterRuleState.info => Severity.info,\n      LinterRuleState.error => Severity.error,\n      LinterRuleState.warning => Severity.warning,\n      LinterRuleState.hint => Severity.hint,\n    };\n  }\n}\n\n/// Merge two maps recursively.\nMap<String, dynamic> _merge(\n  Map<String, dynamic> mapA,\n  Map<String, dynamic> mapB,\n) {\n  return mergeMaps(\n    mapA,\n    mapB,\n    value: (p0, p1) {\n      if (p0 is Map<String, dynamic> && p1 is Map<String, dynamic>) {\n        return _merge(p0, p1);\n      } else if (p0 is List && p1 is List) {\n        return {...p0, ...p1}.toList();\n      } else {\n        return p1;\n      }\n    },\n  );\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/analysis_options.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars, strict_raw_type\n\npart of 'analysis_options.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAnalysisOptionsYaml _$AnalysisOptionsYamlFromJson(Map json) =>\n    $checkedCreate('AnalysisOptionsYaml', json, ($checkedConvert) {\n      final val = AnalysisOptionsYaml(\n        include: $checkedConvert(\n          'include',\n          (v) => const IncludeConverter().fromJson(v),\n        ),\n        analyzer: $checkedConvert(\n          'analyzer',\n          (v) => v == null ? null : AnalyzerOptions.fromJson(v as Map),\n        ),\n        bloc: $checkedConvert(\n          'bloc',\n          (v) => v == null ? null : BlocLintOptions.fromJson(v as Map),\n        ),\n      );\n      return val;\n    });\n\nMap<String, dynamic> _$AnalysisOptionsYamlToJson(\n  AnalysisOptionsYaml instance,\n) => <String, dynamic>{\n  if (const IncludeConverter().toJson(instance.include) case final value?)\n    'include': value,\n  if (instance.analyzer?.toJson() case final value?) 'analyzer': value,\n  if (instance.bloc?.toJson() case final value?) 'bloc': value,\n};\n\nAnalyzerOptions _$AnalyzerOptionsFromJson(Map json) =>\n    $checkedCreate('AnalyzerOptions', json, ($checkedConvert) {\n      final val = AnalyzerOptions(\n        exclude: $checkedConvert(\n          'exclude',\n          (v) =>\n              (v as List<dynamic>?)?.map((e) => e as String).toList() ??\n              const <String>[],\n        ),\n      );\n      return val;\n    });\n\nMap<String, dynamic> _$AnalyzerOptionsToJson(AnalyzerOptions instance) =>\n    <String, dynamic>{'exclude': instance.exclude};\n\nBlocLintOptions _$BlocLintOptionsFromJson(Map json) =>\n    $checkedCreate('BlocLintOptions', json, ($checkedConvert) {\n      final val = BlocLintOptions(\n        rules: $checkedConvert(\n          'rules',\n          (v) => const RulesConverter().fromJson(v),\n        ),\n      );\n      return val;\n    });\n\nMap<String, dynamic> _$BlocLintOptionsToJson(BlocLintOptions instance) =>\n    <String, dynamic>{\n      if (const RulesConverter().toJson(instance.rules) case final value?)\n        'rules': value,\n    };\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/diagnostic.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// The severity of the reported lint rule.\nenum Severity {\n  /// The diagnostic is reported as an error.\n  error,\n\n  /// The diagnostic is reported as an warning.\n  warning,\n\n  /// The diagnostic is reported as info.\n  info,\n\n  /// The diagnostic is reported as a hint.\n  hint,\n}\n\n/// {@template diagnostic}\n/// A diagnostic which is reported by a lint rule.\n/// {@endtemplate}\nclass Diagnostic {\n  /// {@macro diagnostic}\n  const Diagnostic({\n    required this.range,\n    required this.source,\n    required this.message,\n    required this.description,\n    required this.code,\n    required this.severity,\n    this.hint = '',\n  });\n\n  /// The affected range.\n  final Range range;\n\n  /// The source of the lint rule (e.g. who reported it).\n  final String source;\n\n  /// The message associated with the lint.\n  final String message;\n\n  /// An optional property to describe the error code.\n  final String description;\n\n  /// A hint or recommendation usually presented to the user.\n  final String hint;\n\n  /// The diagnostic's code, which usually appear in the user interface.\n  final String code;\n\n  /// The severity level of the lint.\n  final Severity severity;\n\n  /// Converts a [Diagnostic] to a [Map].\n  Map<String, dynamic> toJson() {\n    return {\n      'range': range.toJson(),\n      'source': source,\n      'message': message,\n      'description': description,\n      'hint': hint,\n      'code': code,\n      'severity': severity.name,\n    };\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/env.dart",
    "content": "import 'dart:core';\nimport 'dart:io';\n\nimport 'package:bloc_lint/src/analysis_options.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:pubspec_lock_parse/pubspec_lock_parse.dart';\n\n/// The `package_config.json` file path for this project.\nString packageConfigPath(Directory cwd) {\n  return p.join(cwd.path, '.dart_tool', 'package_config.json');\n}\n\n/// The `pubspec.yaml` file path for this project.\nString pubspecYamlPath(Directory cwd) {\n  return p.join(cwd.path, 'pubspec.yaml');\n}\n\n/// The `pubspec.lock` file path for this project.\nString pubspecLockPath(Directory cwd) {\n  return p.join(cwd.path, 'pubspec.lock');\n}\n\n/// The `analysis_options.yaml` file path for this project.\nString analysisOptionsPath(Directory cwd) {\n  return p.join(cwd.path, 'analysis_options.yaml');\n}\n\n/// The `package_config.json` file for this package.\n///\n/// Returns `null` if the file does not exist or is invalid.\nFile? findPackageConfigFile(Directory cwd) {\n  final root = findProjectRoot(cwd);\n  if (root == null) return null;\n  final file = File(packageConfigPath(root));\n  if (!file.existsSync()) return null;\n  return file;\n}\n\n/// The `analysis_options.yaml` file for this package.\n///\n/// Returns `null` if the file does not exist or is invalid.\nFile? findAnalysisOptionsFile(Directory cwd) {\n  final root = findPackageRoot(cwd);\n  if (root == null) return null;\n  final file = File(analysisOptionsPath(root));\n  if (!file.existsSync()) return null;\n  return file;\n}\n\n/// The resolved `analysis_options.yaml` file for this project.\n///\n/// Returns `null` if the file does not exist or is invalid.\nAnalysisOptions? findAnalysisOptions(Directory cwd) {\n  final file = findAnalysisOptionsFile(cwd);\n  if (file == null) return null;\n  return AnalysisOptions.tryResolve(file);\n}\n\n/// The `pubspec.lock` file for this project, parsed into a [PubspecLock]\n/// object.\n///\n/// Returns `null` if the file does not exist or is invalid.\nPubspecLock? findPubspecLock(Directory cwd) {\n  final root = findProjectRoot(cwd);\n  if (root == null) return null;\n  final file = File(pubspecLockPath(root));\n  if (!file.existsSync()) return null;\n  try {\n    return PubspecLock.parse(file.readAsStringSync());\n  } on Exception {\n    return null;\n  }\n}\n\n/// Returns the root directory of the nearest package.\nDirectory? findPackageRoot(Directory cwd) {\n  final file = findNearestAncestor(\n    where: (path) => File(pubspecYamlPath(Directory(path))),\n    cwd: cwd,\n  );\n  if (file == null) return null;\n  return Directory(p.dirname(file.path));\n}\n\n/// Returns the root directory of the nearest project.\nDirectory? findProjectRoot(Directory cwd) {\n  final file = findNearestAncestor(\n    where: (path) => File(pubspecLockPath(Directory(path))),\n    cwd: cwd,\n  );\n  if (file == null) return null;\n  return Directory(p.dirname(file.path));\n}\n\n/// Finds nearest ancestor file relative to the [cwd] that satisfies [where].\nFile? findNearestAncestor({\n  required File? Function(String path) where,\n  required Directory cwd,\n}) {\n  Directory? prev;\n  var dir = cwd;\n  while (prev?.path != dir.path) {\n    final file = where(dir.path);\n    if (file?.existsSync() ?? false) return file;\n    prev = dir;\n    dir = dir.parent;\n  }\n  return null;\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/lint_rule.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// Signature for a method that builds a [LintRule] with an optional [severity].\ntypedef LintRuleBuilder = LintRule Function([Severity? severity]);\n\n/// {@template lint_rule}\n/// An individual lint rule.\n/// {@endtemplate}\nabstract class LintRule {\n  /// {@macro lint_rule}\n  LintRule({required this.name, required this.severity});\n\n  /// The unique name of the rule.\n  final String name;\n\n  /// The severity of the rule.\n  final Severity severity;\n\n  /// Method that must be implemented which returns a listener\n  /// given a [LintContext].\n  Listener? create(LintContext context);\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/linter.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\n// Use the experimental features from the shared frontend.\n// ignore: implementation_imports\nimport 'package:_fe_analyzer_shared/src/parser/experimental_features.dart'\n    show DefaultExperimentalFeatures;\n\n// Use the parser from the shared frontend.\n// ignore: implementation_imports\nimport 'package:_fe_analyzer_shared/src/parser/parser.dart' show Parser;\n// Use the scanner from the shared frontend.\n// ignore: implementation_imports\nimport 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show scan;\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_lint/src/analysis_options.dart';\nimport 'package:bloc_lint/src/env.dart';\nimport 'package:collection/collection.dart';\nimport 'package:glob/glob.dart';\nimport 'package:path/path.dart' as p;\n\n/// All supported lint rules.\nfinal allRules = <String, LintRuleBuilder>{\n  AvoidBuildContextExtensions.rule: AvoidBuildContextExtensions.new,\n  AvoidFlutterImports.rule: AvoidFlutterImports.new,\n  AvoidPublicBlocMethods.rule: AvoidPublicBlocMethods.new,\n  AvoidPublicFields.rule: AvoidPublicFields.new,\n  PreferBloc.rule: PreferBloc.new,\n  PreferBuildContextExtensions.rule: PreferBuildContextExtensions.new,\n  PreferCubit.rule: PreferCubit.new,\n  PreferFileNamingConventions.rule: PreferFileNamingConventions.new,\n  PreferVoidPublicCubitMethods.rule: PreferVoidPublicCubitMethods.new,\n};\n\n/// {@template linter}\n/// A class that is able to analyze files and directories and\n/// report diagnostics based on a registered set of lint rules.\n/// {@endtemplate}\nclass Linter {\n  /// {@macro linter}\n  const Linter();\n\n  /// Analyzes the provided [uri] and returns all reported diagnostics. If\n  /// [content] is provided, it will be explicitly analyzed, otherwise the [uri]\n  /// will be analyzed (both single files and directories are supported).\n  Map<String, List<Diagnostic>> analyze({required Uri uri, String? content}) {\n    final path = uri.canonicalizedPath.toLongPath();\n    final directory = Directory(path);\n    if (directory.existsSync()) return _analyzeDirectory(directory);\n    final file = File(path);\n    if (file.existsSync() && file.isLintableDartFile) {\n      if (content != null) return _analyzeContent(uri, content);\n      return _analyzeFile(file);\n    }\n    return {};\n  }\n\n  Map<String, List<Diagnostic>> _analyzeDirectory(Directory directory) {\n    final files =\n        directory\n            .listSync(recursive: true)\n            .where((e) => e.isLintableDartFile)\n            .cast<File>();\n\n    return files\n        .map(_analyzeFile)\n        .fold(<String, List<Diagnostic>>{}, (prev, cur) => {...prev, ...cur});\n  }\n\n  Map<String, List<Diagnostic>> _analyzeFile(File file) {\n    return _analyzeContent(file.uri, file.readAsStringSync());\n  }\n\n  Map<String, List<Diagnostic>> _analyzeContent(Uri uri, String content) {\n    final diagnostics = <Diagnostic>[];\n    final canonicalizedPath = uri.canonicalizedPath;\n    final results = {canonicalizedPath: diagnostics};\n    final path = canonicalizedPath.toLongPath();\n    final cwd = File(path).parent;\n    final pubspecLock = findPubspecLock(cwd);\n    if (pubspecLock == null) return results;\n    if (!pubspecLock.packages.keys.contains('bloc')) return results;\n    final analysisOptions = findAnalysisOptions(cwd);\n    if (analysisOptions == null) return results;\n    final relativePath = p\n        .relative(path, from: analysisOptions.file.parent.path)\n        .replaceAll(r'\\', '/');\n    if (analysisOptions.excludes.any((e) => e.matches(relativePath))) {\n      return results;\n    }\n    final document = TextDocument(uri: uri, content: content);\n    final ignoreForFile = document.ignoreForFile;\n    if (ignoreForFile.containsTypeLint) return results;\n    final enabledRules = {...analysisOptions.lintRules}\n      ..removeWhere((rule) => ignoreForFile.contains(rule.name));\n    final tokens = scan(utf8.encode(content)).tokens;\n    for (final rule in enabledRules) {\n      final context = LintContext._(rule: rule, document: document);\n      final listener = rule.create(context);\n      if (listener == null) continue;\n      Parser(\n        listener,\n        experimentalFeatures: const DefaultExperimentalFeatures(),\n      ).parseUnit(tokens);\n      diagnostics.addAll(context.diagnostics);\n    }\n    return results;\n  }\n}\n\nextension on AnalysisOptions {\n  /// Gets the list of [Glob] patterns to be excluded for this project.\n  List<Glob> get excludes {\n    final excludes = yaml.analyzer?.exclude ?? <String>[];\n    final context = p.Context(current: file.parent.path);\n    return excludes.map((e) => Glob(e, context: context)).toList();\n  }\n\n  /// Gets the list of [LintRule] for this project.\n  List<LintRule> get lintRules {\n    final blocLintOptions = yaml.bloc;\n    if (blocLintOptions == null) return [];\n    final rules = blocLintOptions.rules;\n    if (rules == null) return [];\n    return rules.entries\n        .map<LintRule?>((analysisEntry) {\n          final rule = analysisEntry.key;\n          final state = analysisEntry.value;\n          if (state.isDisabled) return null;\n          final entry = allRules.entries.firstWhereOrNull((e) => e.key == rule);\n          if (entry == null) return null;\n          final builder = entry.value;\n          final severity = state.toSeverity(fallback: builder().severity);\n          return builder(severity);\n        })\n        .whereType<LintRule>()\n        .toList();\n  }\n}\n\nextension on FileSystemEntity {\n  bool get isLintableDartFile {\n    return this is File &&\n        p.extension(path) == '.dart' &&\n        !p.basename(path).endsWith('.g.dart') &&\n        !p.split(path).any((segment) => segment.startsWith('.'));\n  }\n}\n\nextension on String {\n  String toLongPath() {\n    // Support long file paths on Windows\n    // https://github.com/dart-lang/sdk/issues/27825\n    if (Platform.isWindows) return r'\\\\?\\' + this;\n    return this;\n  }\n}\n\nextension on Uri {\n  String get canonicalizedPath {\n    final path = isScheme('file') ? p.fromUri(this) : this.path;\n    return p.normalize(p.absolute(path));\n  }\n}\n\n/// {@template lint_context}\n/// A context object that is provided to each [LintRule] and\n/// provides APIs for reporting diagnostics and accessing the\n/// current [TextDocument].\n/// {@endtemplate}\nclass LintContext {\n  /// {@macro lint_context}\n  LintContext._({required LintRule rule, required this.document})\n    : _rule = rule;\n\n  final LintRule _rule;\n\n  /// The current [document] being analyzed.\n  final TextDocument document;\n\n  final List<Diagnostic> _diagnostics = [];\n\n  /// The list of reported diagnostics.\n  List<Diagnostic> get diagnostics => _diagnostics;\n\n  /// Reports a lint at the provided [range].\n  void report({\n    required Range range,\n    required String message,\n    String hint = '',\n  }) {\n    final ignore = document.ignoreForLine(range: range);\n    if (ignore.containsTypeLint || ignore.contains(_rule.name)) return;\n    _diagnostics.add(\n      Diagnostic(\n        range: range,\n        source: 'bloc',\n        message: message,\n        hint: hint,\n        code: _rule.name,\n        description: 'https://bloclibrary.dev/lint-rules/${_rule.name}',\n        severity: _rule.severity,\n      ),\n    );\n  }\n\n  /// Reports a lint from [beginToken] to [endToken].\n  void reportTokenRange({\n    required Token beginToken,\n    required Token endToken,\n    required String message,\n    String hint = '',\n  }) {\n    report(\n      range: Range(\n        start: document.positionAt(beginToken.offset),\n        end: document.positionAt(endToken.offset + endToken.length),\n      ),\n      message: message,\n      hint: hint,\n    );\n  }\n\n  /// Reports a lint at the specified [token].\n  void reportToken({\n    required Token token,\n    required String message,\n    String hint = '',\n  }) {\n    report(\n      range: Range(\n        start: document.positionAt(token.offset),\n        end: document.positionAt(token.offset + token.length),\n      ),\n      message: message,\n      hint: hint,\n    );\n  }\n}\n\nextension on Set<String> {\n  /// Whether the set of strings contains `type=lint`.\n  bool get containsTypeLint => contains('type=lint');\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/avoid_build_context_extensions.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\nimport 'package:collection/collection.dart';\n\n/// {@template avoid_build_context_extensions}\n/// The avoid_build_context_extensions lint rule.\n/// {@endtemplate}\nclass AvoidBuildContextExtensions extends LintRule {\n  /// {@macro avoid_build_context_extensions}\n  AvoidBuildContextExtensions([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'avoid_build_context_extensions';\n\n  @override\n  Listener create(LintContext context) => _Listener(context);\n}\n\n// Supported `BuildContext` extensions methods.\nenum _ContextMethod { read, watch, select }\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  static const _declarationKeywords = {'final', 'const', 'var', 'late'};\n\n  /// Whether the type is implicitly a bloc type.\n  bool _isImplicitBlocType = false;\n\n  @override\n  void beginInitializedIdentifier(Token nameToken) {\n    final prev = nameToken.previous;\n    if (prev != null &&\n        prev.type == TokenType.IDENTIFIER &&\n        !_declarationKeywords.contains(prev.lexeme)) {\n      _isImplicitBlocType = prev.isBlocType;\n    }\n    super.beginInitializedIdentifier(nameToken);\n  }\n\n  @override\n  void endInitializedIdentifier(Token nameToken) {\n    _isImplicitBlocType = false;\n    super.endInitializedIdentifier(nameToken);\n  }\n\n  @override\n  void handleIdentifier(Token token, IdentifierContext _) {\n    final method = _ContextMethod.values.firstWhereOrNull(\n      (value) => value.name == token.lexeme,\n    );\n    if (method == null) return;\n\n    final prev = token.previous;\n    if (prev == null || prev.type != TokenType.PERIOD) return;\n\n    final target = prev.previous;\n    if (target == null) return;\n    if (target.lexeme != 'context') return;\n\n    // Case 1: implicit type\n    // e.g. final MyBloc bloc = context.read();\n    if (_isImplicitBlocType) return _report(method, target, token);\n\n    // Case 2: explicit type\n    // e.g. final bloc = context.read<MyBloc>();\n    final openBracketToken = token.next;\n    if (openBracketToken == null) return;\n    if (openBracketToken.type != TokenType.LT) return;\n\n    final typeToken = openBracketToken.next;\n    if (typeToken == null) return;\n    if (typeToken.type != TokenType.IDENTIFIER) return;\n    if (!typeToken.isBlocType) return;\n\n    return _report(method, target, token);\n  }\n\n  void _report(_ContextMethod method, Token beginToken, Token endToken) {\n    context.reportTokenRange(\n      beginToken: beginToken,\n      endToken: endToken,\n      message: 'Avoid using BuildContext extensions.',\n      hint: 'Prefer using ${method.alternative} instead.',\n    );\n  }\n}\n\nextension on _ContextMethod {\n  String get alternative {\n    switch (this) {\n      case _ContextMethod.read:\n        return 'BlocProvider.of<Bloc>(context, listen: false)';\n      case _ContextMethod.watch:\n        return '''BlocBuilder<Bloc, State>(...) or BlocProvider.of<Bloc>(context)''';\n      case _ContextMethod.select:\n        return 'BlocSelector<Bloc, State>(...)';\n    }\n  }\n}\n\nextension on Token {\n  bool get isBlocType {\n    return lexeme.endsWith('Bloc') || lexeme.endsWith('Cubit');\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/avoid_flutter_imports.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template avoid_flutter_imports}\n/// The avoid_flutter_imports lint rule.\n/// {@endtemplate}\nclass AvoidFlutterImports extends LintRule {\n  /// {@macro avoid_flutter_imports}\n  AvoidFlutterImports([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'avoid_flutter_imports';\n\n  @override\n  Listener? create(LintContext context) {\n    if (context.document.type.isOther) return null;\n    return _Listener(context);\n  }\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  static const flutterImport = 'package:flutter/';\n\n  final LintContext context;\n\n  @override\n  void beginImport(Token importKeyword) {\n    final package = importKeyword.next;\n    if (package == null) return;\n    if (!package.lexeme.substring(1).startsWith(flutterImport)) return;\n    final instance = context.document.type.isBloc ? 'Bloc' : 'Cubit';\n    context.reportToken(\n      token: package,\n      message:\n          '''Avoid importing Flutter within ${instance.toLowerCase()} instances.''',\n      hint: '${instance}s should be decoupled from Flutter.',\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/avoid_public_bloc_methods.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template avoid_public_bloc_methods}\n/// The avoid_public_bloc_methods lint rule.\n/// {@endtemplate}\nclass AvoidPublicBlocMethods extends LintRule {\n  /// {@macro avoid_public_bloc_methods}\n  AvoidPublicBlocMethods([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'avoid_public_bloc_methods';\n\n  @override\n  Listener? create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  static const allowedMethods = [\n    'add',\n    'addError',\n    'close',\n    'emit',\n    'state',\n    'stream',\n    'on',\n    'onChange',\n    'onError',\n    'onEvent',\n    'onTransition',\n    'toString',\n  ];\n\n  final LintContext context;\n\n  var _isOverride = false;\n\n  @override\n  void beginMetadata(Token token) {\n    _isOverride = token.next?.lexeme == 'override';\n  }\n\n  @override\n  void beginMethod(\n    DeclarationKind declarationKind,\n    Token? augmentToken,\n    Token? externalToken,\n    Token? staticToken,\n    Token? covariantToken,\n    Token? varFinalOrConst,\n    Token? getOrSet,\n    Token name,\n    String? enclosingDeclarationName,\n  ) {\n    if (declarationKind != DeclarationKind.Class) return;\n    if (_isOverride || staticToken != null) return;\n    if (!(enclosingDeclarationName?.endsWith('Bloc') ?? false)) {\n      return;\n    }\n    if (name.previous?.type == Keyword.SWITCH) return;\n    final methodName = name.lexeme;\n    if (enclosingDeclarationName == methodName) return;\n    if (allowedMethods.contains(methodName)) return;\n    if (methodName.startsWith('_')) return;\n    context.reportToken(\n      token: name,\n      message: 'Avoid public methods on bloc instances.',\n      hint: 'Prefer notifying bloc instances via `add`.',\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/avoid_public_fields.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template avoid_public_fields}\n/// The avoid_public_fields lint rule.\n/// {@endtemplate}\nclass AvoidPublicFields extends LintRule {\n  /// {@macro avoid_public_fields}\n  AvoidPublicFields([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'avoid_public_fields';\n\n  @override\n  Listener? create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  bool _isRelevantEnclosingClass = false;\n\n  @override\n  void beginClassDeclaration(\n    Token begin,\n    Token? abstractToken,\n    Token? macroToken,\n    Token? sealedToken,\n    Token? baseToken,\n    Token? interfaceToken,\n    Token? finalToken,\n    Token? augmentToken,\n    Token? mixinToken,\n    Token name,\n  ) {\n    _isRelevantEnclosingClass = false;\n\n    final extendz = name.next;\n\n    if (extendz == null || extendz.kind != Keyword.EXTENDS.kind) return;\n\n    final superclazz = extendz.next;\n    if (superclazz == null) return;\n\n    if (superclazz.lexeme.endsWith('Bloc') ||\n        superclazz.lexeme.endsWith('Cubit')) {\n      _isRelevantEnclosingClass = true;\n    }\n    return;\n  }\n\n  @override\n  void endFields(\n    DeclarationKind kind,\n    Token? abstractToken,\n    Token? augmentToken,\n    Token? externalToken,\n    Token? staticToken,\n    Token? covariantToken,\n    Token? lateToken,\n    Token? varFinalOrConst,\n    int count,\n    Token beginToken,\n    Token endToken,\n  ) {\n    if (!_isRelevantEnclosingClass) return;\n    if (staticToken != null) return;\n\n    final fieldName = _getFieldName(beginToken, endToken);\n    if (fieldName.lexeme.startsWith('_')) return;\n\n    context.reportTokenRange(\n      beginToken: beginToken,\n      endToken: endToken,\n      message: 'Avoid public fields.',\n      hint: 'Prefer using the `state` to hold all public fields.',\n    );\n  }\n}\n\nList<Token> _getTokens(Token begin, Token end) {\n  final tokens = <Token>[];\n  Token? token = begin;\n  while (token != null && token != end) {\n    tokens.add(token);\n    token = token.next;\n  }\n  return tokens;\n}\n\nToken _getFieldName(Token begin, Token end) {\n  final tokens = _getTokens(begin, end);\n  final equalsIndex = tokens.indexWhere((token) => token.type == TokenType.EQ);\n  if (equalsIndex != -1) return tokens.elementAt(equalsIndex).previous!;\n  return end.previous!;\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/prefer_bloc.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template prefer_bloc}\n/// The prefer_bloc lint rule.\n/// {@endtemplate}\nclass PreferBloc extends LintRule {\n  /// {@macro prefer_bloc}\n  PreferBloc([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.info);\n\n  /// The name of the lint rule.\n  static const rule = 'prefer_bloc';\n\n  @override\n  Listener create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  @override\n  void beginClassDeclaration(\n    Token begin,\n    Token? abstractToken,\n    Token? macroToken,\n    Token? sealedToken,\n    Token? baseToken,\n    Token? interfaceToken,\n    Token? finalToken,\n    Token? augmentToken,\n    Token? mixinToken,\n    Token name,\n  ) {\n    final extendz = name.next;\n\n    if (extendz == null || extendz.kind != Keyword.EXTENDS.kind) return;\n\n    final superclazz = extendz.next;\n    if (superclazz == null) return;\n\n    if (superclazz.lexeme.endsWith('Cubit')) {\n      final prefix = superclazz.lexeme.split('Cubit').first;\n      context.reportToken(\n        token: name,\n        message: 'Avoid extending ${prefix}Cubit.',\n        hint: 'Prefer extending ${prefix}Bloc instead.',\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/prefer_build_context_extensions.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template prefer_build_context_extensions}\n/// The prefer_build_context_extensions lint rule.\n/// {@endtemplate}\nclass PreferBuildContextExtensions extends LintRule {\n  /// {@macro prefer_build_context_extensions}\n  PreferBuildContextExtensions([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'prefer_build_context_extensions';\n\n  @override\n  Listener create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  static const _message = 'Prefer using BuildContext extensions.';\n\n  @override\n  void handleIdentifier(Token token, IdentifierContext _) {\n    final provider = tryParseProvider(token);\n    if (provider != null) {\n      return context.reportTokenRange(\n        beginToken: provider,\n        endToken: token,\n        message: _message,\n        hint: '''\nAvoid using ${provider.lexeme}.of<T>.\nPrefer using context.read or context.watch instead.''',\n      );\n    }\n\n    final blocBuilder = tryParseBlocBuilder(token);\n    if (blocBuilder != null) {\n      return context.reportTokenRange(\n        beginToken: blocBuilder,\n        endToken: token,\n        message: _message,\n        hint: '''\nAvoid using ${blocBuilder.lexeme}.\nPrefer using context.watch instead.''',\n      );\n    }\n\n    final blocSelector = tryParseBlocSelector(token);\n    if (blocSelector != null) {\n      return context.reportTokenRange(\n        beginToken: blocSelector,\n        endToken: token,\n        message: _message,\n        hint: '''\nAvoid using ${blocSelector.lexeme}.\nPrefer using context.select instead.''',\n      );\n    }\n  }\n\n  Token? tryParseProvider(Token token) {\n    if (token.lexeme != 'of') return null;\n\n    final prev = token.previous;\n    if (prev == null) return null;\n    if (prev.type != TokenType.PERIOD) return null;\n\n    final next = token.next;\n    if (next == null) return null;\n\n    final target = prev.previous;\n    if (target == null) return null;\n\n    const providers = <String>{'BlocProvider', 'RepositoryProvider'};\n    return providers.contains(target.lexeme) ? target : null;\n  }\n\n  Token? tryParseBlocBuilder(Token token) {\n    if (token.lexeme != 'BlocBuilder') return null;\n    return (token.next is BeginToken) ? token : null;\n  }\n\n  Token? tryParseBlocSelector(Token token) {\n    if (token.lexeme != 'BlocSelector') return null;\n    return (token.next is BeginToken) ? token : null;\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/prefer_cubit.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template prefer_cubit}\n/// The prefer_cubit lint rule.\n/// {@endtemplate}\nclass PreferCubit extends LintRule {\n  /// {@macro prefer_cubit}\n  PreferCubit([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.info);\n\n  /// The name of the lint rule.\n  static const rule = 'prefer_cubit';\n\n  @override\n  Listener create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  @override\n  void beginClassDeclaration(\n    Token begin,\n    Token? abstractToken,\n    Token? macroToken,\n    Token? sealedToken,\n    Token? baseToken,\n    Token? interfaceToken,\n    Token? finalToken,\n    Token? augmentToken,\n    Token? mixinToken,\n    Token name,\n  ) {\n    final extendz = name.next;\n\n    if (extendz == null || extendz.kind != Keyword.EXTENDS.kind) return;\n\n    final superclazz = extendz.next;\n    if (superclazz == null) return;\n\n    if (superclazz.lexeme.endsWith('Bloc')) {\n      final prefix = superclazz.lexeme.split('Bloc').first;\n      context.reportToken(\n        token: name,\n        message: 'Avoid extending ${prefix}Bloc.',\n        hint: 'Prefer extending ${prefix}Cubit instead.',\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/prefer_file_naming_conventions.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_lint/src/string_case.dart';\nimport 'package:path/path.dart' as path;\n\n/// {@template prefer_file_naming_conventions}\n/// The prefer_file_naming_conventions lint rule.\n/// {@endtemplate}\nclass PreferFileNamingConventions extends LintRule {\n  /// {@macro prefer_file_naming_conventions}\n  PreferFileNamingConventions([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.info);\n\n  /// The name of the lint rule.\n  static const rule = 'prefer_file_naming_conventions';\n\n  @override\n  Listener create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  @override\n  void beginClassDeclaration(\n    Token begin,\n    Token? abstractToken,\n    Token? macroToken,\n    Token? sealedToken,\n    Token? baseToken,\n    Token? interfaceToken,\n    Token? finalToken,\n    Token? augmentToken,\n    Token? mixinToken,\n    Token name,\n  ) {\n    final extendz = name.next;\n\n    if (extendz == null || extendz.kind != Keyword.EXTENDS.kind) return;\n\n    final superclazz = extendz.next;\n    if (superclazz == null) return;\n\n    if (superclazz.lexeme.endsWith('MockBloc')) return;\n    if (superclazz.lexeme.endsWith('MockCubit')) return;\n\n    if (superclazz.lexeme.endsWith('Bloc') ||\n        superclazz.lexeme.endsWith('Cubit')) {\n      final expectedFileName = '${name.lexeme.toSnakeCase()}.dart';\n      final actualFileName = path.basename(context.document.uri.toString());\n      if (actualFileName == expectedFileName) return;\n      context.reportToken(\n        token: name,\n        message: 'Prefer following file naming conventions.',\n        hint: 'Prefer moving ${name.lexeme} into $expectedFileName.dart',\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/prefer_void_public_cubit_methods.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\n\n/// {@template prefer_void_public_cubit_methods}\n/// The prefer_void_public_cubit_methods lint rule.\n/// {@endtemplate}\nclass PreferVoidPublicCubitMethods extends LintRule {\n  /// {@macro prefer_void_public_cubit_methods}\n  PreferVoidPublicCubitMethods([Severity? severity])\n    : super(name: rule, severity: severity ?? Severity.warning);\n\n  /// The name of the lint rule.\n  static const rule = 'prefer_void_public_cubit_methods';\n\n  @override\n  Listener? create(LintContext context) => _Listener(context);\n}\n\nclass _Listener extends Listener {\n  _Listener(this.context);\n\n  final LintContext context;\n\n  var _isOverride = false;\n\n  static const allowedReturnTypes = ['void', 'Future<void>', 'FutureOr<void>'];\n\n  @override\n  void beginMetadata(Token token) {\n    _isOverride = token.next?.lexeme == 'override';\n  }\n\n  @override\n  void beginMethod(\n    DeclarationKind declarationKind,\n    Token? augmentToken,\n    Token? externalToken,\n    Token? staticToken,\n    Token? covariantToken,\n    Token? varFinalOrConst,\n    Token? getOrSet,\n    Token name,\n    String? enclosingDeclarationName,\n  ) {\n    if (declarationKind != DeclarationKind.Class) return;\n    if (_isOverride || staticToken != null) return;\n    if (!(enclosingDeclarationName?.endsWith('Cubit') ?? false)) {\n      return;\n    }\n    if (name.previous?.type == Keyword.SWITCH) return;\n    final methodName = name.lexeme;\n    if (enclosingDeclarationName == methodName) return;\n    if (methodName.startsWith('_')) return;\n    if (getOrSet?.keyword == Keyword.SET) return;\n    if (allowedReturnTypes.contains(_getReturnType(getOrSet ?? name))) return;\n    context.reportToken(\n      token: name,\n      message: '''\nPrefer void public methods on cubit instances.\nTry adjusting the return type to `void`, `Future<void>`, or `FutureOr<void>`.''',\n      hint: 'Prefer `void` return types.',\n    );\n  }\n}\n\nString _getReturnType(Token name) {\n  const dynamic = 'dynamic';\n  final previous = name.previous;\n  if (previous == null) return dynamic;\n  if (previous.type != TokenType.GT) return previous.lexeme;\n\n  var bracketCount = 0;\n  final chars = <String>[];\n  Token? current = previous;\n\n  bool isDone() {\n    if (current == null) return true;\n    if (bracketCount <= 0) return true;\n    return false;\n  }\n\n  do {\n    chars.insert(0, current!.lexeme);\n    if (current.type == TokenType.GT) bracketCount++;\n    if (current.type == TokenType.LT) bracketCount--;\n    current = current.previous;\n  } while (!isDone());\n\n  if (current != null) chars.insert(0, current.lexeme);\n\n  return chars.join();\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/rules/rules.dart",
    "content": "export 'avoid_build_context_extensions.dart';\nexport 'avoid_flutter_imports.dart';\nexport 'avoid_public_bloc_methods.dart';\nexport 'avoid_public_fields.dart';\nexport 'prefer_bloc.dart';\nexport 'prefer_build_context_extensions.dart';\nexport 'prefer_cubit.dart';\nexport 'prefer_file_naming_conventions.dart';\nexport 'prefer_void_public_cubit_methods.dart';\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/string_case.dart",
    "content": "import 'package:collection/collection.dart';\n\n/// Extension on [String] that add support for converting\n/// to snake_case.\nextension SnakeCaseX on String {\n  /// Returns the snake_case equivalent of the current string.\n  String toSnakeCase() {\n    return split('').mapIndexed((index, character) {\n      if (index == 0) return character.toLowerCase();\n      if (character.toUpperCase() == character) {\n        return '_${character.toLowerCase()}';\n      }\n      return character;\n    }).join();\n  }\n}\n"
  },
  {
    "path": "packages/bloc_lint/lib/src/text_document.dart",
    "content": "import 'dart:math';\n\nimport 'package:path/path.dart' as p;\n\n/// The newline symbol (\\n).\nconst newline = 10;\n\n/// The carriage return symbol (\\r).\nconst carriageReturn = 13;\n\n/// {@template text_document}\n/// A simplified, Dart representation of VSCode's Language Server\n/// [TextDocument](https://github.com/microsoft/vscode-languageserver-node/blob/main/textDocument/src/main.ts).\n/// {@endtemplate}\nclass TextDocument {\n  /// {@macro text_document}\n  TextDocument({required Uri uri, required String content})\n    : _uri = uri,\n      _content = content;\n\n  static final _ignoreForFileRegExp = RegExp(\n    r'^\\s*//\\s*ignore_for_file:(.*?)$',\n    dotAll: true,\n    multiLine: true,\n  );\n\n  static final _ignoreForLineRegExp = RegExp(r'^\\s*//\\s*ignore:(.*)$');\n\n  static final _ignoreAfterLineRegExp = RegExp(r'\\s*//\\s*ignore:(.*)$');\n\n  final Uri _uri;\n  final String _content;\n  List<int>? _lineOffsets;\n\n  /// The associated URI for this document. Most documents have the file scheme,\n  /// indicating that they represent files on disk. However, some documents may\n  /// have other schemes indicating that they are not available on disk.\n  Uri get uri => _uri;\n\n  /// Whether the line for the current range contains an // ignore: for the given rule.\n  Set<String> ignoreForLine({required Range range}) {\n    return {\n      ..._ignoresAboveLine(range: range),\n      ..._ignoresAfterLine(range: range),\n    };\n  }\n\n  Set<String> _ignoresAboveLine({required Range range}) {\n    final previousLine = range.start.line - 1;\n    if (previousLine < 0) return const <String>{};\n    final line = getText(\n      range: Range(\n        start: Position(character: 0, line: previousLine),\n        end: Position(character: _content.length, line: previousLine),\n      ),\n    );\n    return _lineIgnores(line);\n  }\n\n  Set<String> _ignoresAfterLine({required Range range}) {\n    final afterText = getText(\n      range: Range(\n        start: Position(character: range.end.character, line: range.end.line),\n        end: Position(character: _content.length, line: range.end.line),\n      ),\n    );\n    final index = afterText.indexOf(_ignoreAfterLineRegExp);\n    if (index == -1) return const <String>{};\n    final line = afterText.substring(index);\n    return _lineIgnores(line);\n  }\n\n  Set<String> _lineIgnores(String line) {\n    final result = <String>{};\n    final matches = _ignoreForLineRegExp.allMatches(line);\n    if (matches.isEmpty) return result;\n    for (final match in matches) {\n      final contents = match.group(1);\n      if (contents == null) continue;\n      result.addAll(contents.split(',').map((segment) => segment.trim()));\n    }\n    return result;\n  }\n\n  /// Returns a list of rules ignored for the current file.\n  /// e.g. // ignore_for_file: avoid_flutter_imports, prefer_bloc\n  Set<String> get ignoreForFile {\n    final result = <String>{};\n    final matches = _ignoreForFileRegExp.allMatches(_content);\n    if (matches.isEmpty) return result;\n    for (final match in matches) {\n      final contents = match.group(1);\n      if (contents == null) continue;\n      result.addAll(contents.split(',').map((segment) => segment.trim()));\n    }\n    return result;\n  }\n\n  /// Get the text of this document. Provide a [Range] to get a substring.\n  String getText({Range? range}) {\n    if (range != null) {\n      final start = offsetAt(range.start);\n      final end = offsetAt(range.end);\n      return _content.substring(start, end);\n    }\n    return _content;\n  }\n\n  /// Convert a [Position] to a zero-based offset.\n  int offsetAt(Position position) {\n    final lineOffsets = _getLineOffsets();\n    if (position.line >= lineOffsets.length) {\n      return _content.length;\n    } else if (position.line < 0) {\n      return 0;\n    }\n\n    final lineOffset = lineOffsets[position.line];\n    if (position.character <= 0) {\n      return lineOffset;\n    }\n\n    final nextLineOffset =\n        (position.line + 1 < lineOffsets.length)\n            ? lineOffsets[position.line + 1]\n            : _content.length;\n    final offset = min(lineOffset + position.character, nextLineOffset);\n\n    return _ensureBeforeEndOfLine(offset: offset, lineOffset: lineOffset);\n  }\n\n  /// Converts a zero-based offset to a [Position].\n  Position positionAt(int offset) {\n    // ignore: parameter_assignments\n    offset = max(min(offset, _content.length), 0);\n    final lineOffsets = _getLineOffsets();\n    var low = 0;\n    var high = lineOffsets.length;\n    if (high == 0) return Position(character: offset, line: 0);\n\n    while (low < high) {\n      final mid = ((low + high) / 2).floor();\n      if (lineOffsets[mid] > offset) {\n        high = mid;\n      } else {\n        low = mid + 1;\n      }\n    }\n\n    final line = low - 1;\n    // ignore: parameter_assignments\n    offset = _ensureBeforeEndOfLine(\n      offset: offset,\n      lineOffset: lineOffsets[line],\n    );\n\n    return Position(character: offset - lineOffsets[line], line: line);\n  }\n\n  List<int> _getLineOffsets() {\n    _lineOffsets ??= _computeLineOffsets(_content, isAtLineStart: true);\n    return _lineOffsets!;\n  }\n\n  List<int> _computeLineOffsets(\n    String content, {\n    required bool isAtLineStart,\n    int textOffset = 0,\n  }) {\n    final result = isAtLineStart ? [textOffset] : <int>[];\n\n    for (var i = 0; i < content.length; i++) {\n      final char = content.codeUnitAt(i);\n      if (_isEndOfLine(char)) {\n        if (char == carriageReturn) {\n          final nextCharIsnewline =\n              i + 1 < content.length && content.codeUnitAt(i + 1) == newline;\n          if (nextCharIsnewline) {\n            i++;\n          }\n        }\n        result.add(textOffset + i + 1);\n      }\n    }\n\n    return result;\n  }\n\n  bool _isEndOfLine(int char) => char == newline || char == carriageReturn;\n\n  int _ensureBeforeEndOfLine({required int offset, required int lineOffset}) {\n    while (offset > lineOffset &&\n        _isEndOfLine(_content.codeUnitAt(offset - 1))) {\n      offset--;\n    }\n    return offset;\n  }\n}\n\n/// {@template position}\n/// A specific position within a [TextDocument].\n/// {@endtemplate}\nclass Position {\n  /// {@macro position}\n  const Position({required this.line, required this.character});\n\n  /// The line number.\n  final int line;\n\n  /// The character offset within the line.\n  final int character;\n\n  /// Converts a [Position] into a [Map].\n  Map<String, dynamic> toJson() => {'line': line, 'character': character};\n}\n\n/// {@template range}\n/// A range of content within a [TextDocument].\n/// {@endtemplate}\nclass Range {\n  /// {@macro range}\n  const Range({required this.start, required this.end});\n\n  /// The starting position.\n  final Position start;\n\n  /// The ending position.\n  final Position end;\n\n  /// Converts a [Range] to a [Map].\n  Map<String, dynamic> toJson() {\n    return {'start': start.toJson(), 'end': end.toJson()};\n  }\n}\n\n/// Relevant types of text documents.\nenum TextDocumentType {\n  /// A bloc file.\n  bloc,\n\n  /// A cubit file.\n  cubit,\n\n  /// Any other file.\n  other,\n}\n\n/// Extensions on [TextDocument] that provide access to\n/// document type information.\nextension TextDocumentX on TextDocument {\n  /// Returns the [TextDocumentType] for the given document.\n  TextDocumentType get type {\n    final basename = p.basename(uri.path);\n    return basename.endsWith('_bloc.dart')\n        ? TextDocumentType.bloc\n        : basename.endsWith('_cubit.dart')\n        ? TextDocumentType.cubit\n        : TextDocumentType.other;\n  }\n}\n\n/// Extensions on [TextDocumentType] that provide access to\n/// convenience methods for interpreting the type.\nextension TextDocumentTypeX on TextDocumentType {\n  /// Whether the document type is a bloc.\n  bool get isBloc => this == TextDocumentType.bloc;\n\n  /// Whether the document type is a cubit.\n  bool get isCubit => this == TextDocumentType.cubit;\n\n  /// Whether the document type is other.\n  bool get isOther => this == TextDocumentType.other;\n}\n"
  },
  {
    "path": "packages/bloc_lint/pubspec.yaml",
    "content": "name: bloc_lint\ndescription: Official lint rules for development when using the bloc state management library.\nversion: 0.4.0\nrepository: https://github.com/felangel/bloc/tree/master/packages/bloc_lint\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://github.com/felangel/bloc\ndocumentation: https://bloclibrary.dev\ntopics: [bloc, state-management, lint]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=3.7.0 <4.0.0\"\n\ndependencies:\n  _fe_analyzer_shared: \">=93.0.0 <95.0.0\"\n  checked_yaml: ^2.0.0\n  collection: ^1.0.0\n  glob: ^2.0.0\n  json_annotation: ^4.9.0\n  path: ^1.0.0\n  pubspec_lock_parse: ^2.0.0\n\ndev_dependencies:\n  build_runner: ^2.0.0\n  json_serializable: ^6.0.0\n  meta: ^1.0.0\n  mocktail: ^1.0.0\n  test: ^1.0.0\n\nscreenshots:\n  - description: The bloc_lint package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/bloc_lint/test/src/analysis_options_test.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:bloc_lint/src/analysis_options.dart';\nimport 'package:bloc_lint/src/diagnostic.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nvoid main() {\n  group(AnalysisOptionsYaml, () {\n    test('(de)serializes correctly', () {\n      final options = AnalysisOptionsYaml(\n        include: ['include.yaml'],\n        analyzer: const AnalyzerOptions(exclude: ['exclude']),\n        bloc: const BlocLintOptions(\n          rules: {'prefer_bloc': LinterRuleState.enabled},\n        ),\n      );\n      expect(\n        json.encode(AnalysisOptionsYaml.fromJson(options.toJson()).toJson()),\n        equals(json.encode(options.toJson())),\n      );\n    });\n  });\n\n  group(AnalysisOptions, () {\n    group('tryParse', () {\n      test('returns null for invalid file', () {\n        expect(AnalysisOptions.tryParse(File('invalid')), isNull);\n      });\n    });\n\n    group('parse', () {\n      test('completes for empty file', () {\n        final tempDir = Directory.systemTemp.createTempSync();\n        final file = File(p.join(tempDir.path, 'analysis_options.yaml'))\n          ..writeAsStringSync('{}');\n        final parsed = AnalysisOptions.parse(file);\n        expect(parsed.file.path, equals(file.path));\n        expect(parsed.yaml.analyzer, isNull);\n        expect(parsed.yaml.include, isNull);\n        expect(parsed.yaml.bloc, isNull);\n      });\n\n      test('completes for valid file', () {\n        final tempDir = Directory.systemTemp.createTempSync();\n        final file = File(p.join(tempDir.path, 'analysis_options.yaml'))\n          ..writeAsStringSync('''\ninclude: package:bloc_lint/recommended.yaml\n\nanalyzer:\n  exclude:\n    - \"**.g.dart\"\n\nlinter:\n  rules:\n    public_member_api_docs: false\n\nbloc:\n  rules:\n    prefer_bloc: true\n''');\n        final parsed = AnalysisOptions.parse(file);\n        expect(parsed.file.path, equals(file.path));\n        expect(parsed.yaml.analyzer?.exclude, equals(['**.g.dart']));\n        expect(\n          parsed.yaml.include,\n          equals(['package:bloc_lint/recommended.yaml']),\n        );\n        expect(\n          parsed.yaml.bloc?.rules,\n          equals({'prefer_bloc': LinterRuleState.enabled}),\n        );\n      });\n    });\n\n    group('tryResolve', () {\n      test('returns null for invalid file', () {\n        expect(AnalysisOptions.tryResolve(File('invalid')), isNull);\n      });\n    });\n\n    group('resolve', () {\n      test('resolves with no includes', () {\n        final tempDir = Directory.systemTemp.createTempSync();\n        final file = File(p.join(tempDir.path, 'analysis_options.yaml'))\n          ..writeAsStringSync('''\nanalyzer:\n  exclude:\n    - \"**.g.dart\"\n\nlinter:\n  rules:\n    public_member_api_docs: false\n\nbloc:\n  rules:\n    prefer_bloc: true\n''');\n        final parsed = AnalysisOptions.resolve(file);\n        expect(parsed.file.path, equals(file.path));\n        expect(parsed.yaml.analyzer?.exclude, equals(['**.g.dart']));\n        expect(parsed.yaml.include, isNull);\n        expect(\n          parsed.yaml.bloc?.rules,\n          equals({'prefer_bloc': LinterRuleState.enabled}),\n        );\n      });\n\n      test('resolves with package include', () {\n        final tempDir = Directory.systemTemp.createTempSync();\n        File(p.join(tempDir.path, 'bloc_lint', 'lib', 'recommended.yaml'))\n          ..createSync(recursive: true)\n          ..writeAsStringSync('''\nbloc:\n  rules:\n    - avoid_flutter_imports\n    - avoid_public_bloc_methods\n    - avoid_public_fields\n''');\n        File(p.join(tempDir.path, '.dart_tool', 'package_config.json'))\n          ..createSync(recursive: true)\n          ..writeAsStringSync('''\n{\n  \"packages\": [\n    {\n      \"name\": \"bloc_lint\",\n      \"rootUri\": \"../bloc_lint\",\n      \"packageUri\": \"lib/\",\n      \"languageVersion\": \"3.7\"\n    }\n  ]\n}\n''');\n        File(p.join(tempDir.path, 'pubspec.lock')).writeAsStringSync('''\npackages:\n  bloc:\n    dependency: \"direct main\"\n    description:\n      name: bloc\n      sha256: \"52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189\"\n      url: \"https://pub.dev\"\n    source: hosted\n    version: \"9.0.0\"\nsdks:\n  dart: \">=3.6.0 <4.0.0\"\n''');\n        final file = File(p.join(tempDir.path, 'analysis_options.yaml'))\n          ..writeAsStringSync('''\ninclude: package:bloc_lint/recommended.yaml\nanalyzer:\n  exclude:\n    - \"**.g.dart\"\n\nbloc:\n  rules:\n    prefer_bloc: true\n''');\n        final parsed = AnalysisOptions.resolve(file);\n        expect(parsed.file.path, equals(file.path));\n        expect(parsed.yaml.analyzer?.exclude, equals(['**.g.dart']));\n        expect(parsed.yaml.include, ['package:bloc_lint/recommended.yaml']);\n        expect(\n          parsed.yaml.bloc?.rules,\n          equals({\n            'avoid_flutter_imports': LinterRuleState.enabled,\n            'avoid_public_bloc_methods': LinterRuleState.enabled,\n            'avoid_public_fields': LinterRuleState.enabled,\n            'prefer_bloc': LinterRuleState.enabled,\n          }),\n        );\n      });\n\n      test('resolves with nested includes', () {\n        final tempDir = Directory.systemTemp.createTempSync();\n        File(p.join(tempDir.path, 'one.yaml')).writeAsStringSync('''\nanalyzer:\n  exclude:\n    - one.dart\n\nbloc:\n  rules:\n    avoid_flutter_imports: error\n''');\n        File(p.join(tempDir.path, 'two.yaml')).writeAsStringSync('''\nanalyzer:\n  exclude:\n    - two.dart\n\nbloc:\n  rules:\n    prefer_bloc: error\n    prefer_cubit: warning\n''');\n\n        File(p.join(tempDir.path, 'three.yaml')).writeAsStringSync('''\ninclude: two.yaml\n\nanalyzer:\n  exclude:\n    - three.dart\n\nbloc:\n  rules:\n    prefer_bloc: false\n''');\n        final options = File(p.join(tempDir.path, 'analysis_options.yaml'))\n          ..writeAsStringSync('''\ninclude:\n  - one.yaml\n  - three.yaml\n\nanalyzer:\n  exclude:\n    - \"**.g.dart\"\n\nbloc:\n  rules:\n    avoid_public_fields: true\n''');\n        final parsed = AnalysisOptions.resolve(options);\n        expect(parsed.file.path, equals(options.path));\n        expect(\n          parsed.yaml.analyzer?.exclude,\n          equals(['one.dart', 'two.dart', 'three.dart', '**.g.dart']),\n        );\n        expect(\n          parsed.yaml.include,\n          equals(['two.yaml', 'one.yaml', 'three.yaml']),\n        );\n        expect(\n          parsed.yaml.bloc?.rules,\n          equals({\n            'avoid_flutter_imports': LinterRuleState.error,\n            'prefer_cubit': LinterRuleState.warning,\n            'prefer_bloc': LinterRuleState.disabled,\n            'avoid_public_fields': LinterRuleState.enabled,\n          }),\n        );\n      });\n    });\n  });\n\n  group(LinterRuleState, () {\n    test('toSeverity is correct', () {\n      expect(\n        LinterRuleState.enabled.toSeverity(fallback: Severity.info),\n        equals(Severity.info),\n      );\n      expect(LinterRuleState.disabled.toSeverity(), equals(null));\n      expect(LinterRuleState.info.toSeverity(), equals(Severity.info));\n      expect(LinterRuleState.error.toSeverity(), equals(Severity.error));\n      expect(LinterRuleState.warning.toSeverity(), equals(Severity.warning));\n      expect(LinterRuleState.hint.toSeverity(), equals(Severity.hint));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/diagnostic_test.dart",
    "content": "import 'dart:convert';\n\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group(Diagnostic, () {\n    group('toJson', () {\n      test('returns correct value', () {\n        expect(\n          json.encode(\n            const Diagnostic(\n              range: Range(\n                start: Position(line: 1, character: 2),\n                end: Position(line: 3, character: 4),\n              ),\n              source: 'source',\n              message: 'message',\n              description: 'description',\n              hint: 'hint',\n              code: 'code',\n              severity: Severity.error,\n            ).toJson(),\n          ),\n          equals(\n            json.encode({\n              'range': {\n                'start': {'line': 1, 'character': 2},\n                'end': {'line': 3, 'character': 4},\n              },\n              'source': 'source',\n              'message': 'message',\n              'description': 'description',\n              'hint': 'hint',\n              'code': 'code',\n              'severity': 'error',\n            }),\n          ),\n        );\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/lint_test_helper.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:meta/meta.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nconst lintMarker = '^';\n\n@isTest\nvoid lintTest(\n  String description, {\n  required LintRule Function() rule,\n  required String path,\n  required String content,\n}) {\n  test(description, () {\n    final lines = const LineSplitter().convert(content);\n    final sanitizedLines = StringBuffer();\n    final lintedLines = <Range>[];\n    for (var i = 0; i < lines.length; i++) {\n      final line = lines[i];\n      final lineNumber = i + 1;\n      final isLint =\n          line.contains('^') && line.replaceAll('^', '').trim().isEmpty;\n\n      if (!isLint) {\n        sanitizedLines.writeln(line);\n        continue;\n      }\n\n      sanitizedLines.writeln();\n      final lintedLine = lineNumber - 2;\n      lintedLines.add(\n        Range(\n          start: Position(line: lintedLine, character: line.indexOf('^')),\n          end: Position(line: lintedLine, character: line.lastIndexOf('^') + 1),\n        ),\n      );\n    }\n\n    const linter = Linter();\n    final lintRule = rule();\n    final tempDir = Directory.systemTemp.createTempSync();\n    final tempFile =\n        File(p.join(tempDir.path, path))\n          ..createSync(recursive: true)\n          ..writeAsStringSync(content);\n    File(p.join(tempDir.path, 'analysis_options.yaml')).writeAsStringSync('''\nbloc:\n  rules:\n    - ${lintRule.name}\n''');\n    File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''\nname: _\nenvironment:\n  sdk: \">=3.6.0 <4.0.0\"\ndependencies:\n  bloc: any\n''');\n\n    File(p.join(tempDir.path, 'pubspec.lock')).writeAsStringSync('''\npackages:\n  bloc:\n    dependency: \"direct main\"\n    description:\n      name: bloc\n      sha256: \"52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189\"\n      url: \"https://pub.dev\"\n    source: hosted\n    version: \"9.0.0\"\nsdks:\n  dart: \">=3.6.0 <4.0.0\"\n''');\n\n    final diagnostics = linter.analyze(\n      uri: tempFile.uri,\n      content: sanitizedLines.toString(),\n    );\n\n    for (final entry in diagnostics.entries) {\n      final diagnostics = entry.value;\n      if (diagnostics.isEmpty) expect(lintedLines, isEmpty);\n      for (final diagnostic in diagnostics) {\n        expect(diagnostic.code, equals(lintRule.name));\n        expect(diagnostic.source, equals('bloc'));\n        expect(diagnostic.severity, equals(lintRule.severity));\n        final reportedRange = json.encode(diagnostic.range.toJson());\n        final expectedRanges = lintedLines.map((l) => json.encode(l.toJson()));\n        expect(expectedRanges, contains(reportedRange));\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/linter_test.dart",
    "content": "import 'dart:io';\n\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nimport 'lint_test_helper.dart';\n\nclass _MockLintRule extends Mock implements LintRule {}\n\nclass _MockListener extends Mock implements Listener {}\n\nclass _FakeLintContext extends Fake implements LintContext {}\n\nvoid main() {\n  group(Linter, () {\n    const name = 'prefer_bloc';\n\n    late Listener listener;\n    late LintRule rule;\n    late Linter linter;\n\n    setUpAll(() {\n      registerFallbackValue(_FakeLintContext());\n    });\n\n    setUp(() {\n      listener = _MockListener();\n      rule = _MockLintRule();\n      linter = const Linter();\n\n      when(() => rule.create(any())).thenReturn(listener);\n      when(() => rule.name).thenReturn(name);\n    });\n\n    group('analyze', () {\n      late Directory tempDir;\n\n      setUp(() {\n        tempDir = Directory.systemTemp.createTempSync();\n        File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''\nname: _\nenvironment:\n  sdk: \">=3.6.0 <4.0.0\"\ndependencies:\n  bloc: any\n''');\n        File(p.join(tempDir.path, 'pubspec.lock')).writeAsStringSync('''\npackages:\n  bloc:\n    dependency: \"direct main\"\n    description:\n      name: bloc\n      sha256: \"52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189\"\n      url: \"https://pub.dev\"\n    source: hosted\n    version: \"9.0.0\"\nsdks:\n  dart: \">=3.6.0 <4.0.0\"\n''');\n        File(\n          p.join(tempDir.path, 'analysis_options.yaml'),\n        ).writeAsStringSync('{}');\n      });\n\n      tearDown(() {\n        tempDir.deleteSync(recursive: true);\n      });\n\n      test('does nothing if file/directory does not exist', () {\n        final invalid = File('invalid');\n        expect(linter.analyze(uri: invalid.uri), isEmpty);\n      });\n\n      test('does nothing if pubspec.lock is malformed', () {\n        File(p.join(tempDir.path, 'pubspec.lock')).writeAsStringSync('invalid');\n        final file = File(p.join(tempDir.path, 'main.dart'))\n          ..writeAsStringSync('''\nvoid main() {\n  print('hello world');\n}\n''');\n        expect(\n          linter.analyze(uri: file.uri),\n          equals({file.path: <Diagnostic>[]}),\n        );\n      });\n\n      test('analyzes an individual file', () {\n        final file = File(p.join(tempDir.path, 'main.dart'))\n          ..writeAsStringSync('''\nvoid main() {\n  print('hello world');\n}\n''');\n        expect(\n          linter.analyze(uri: file.uri),\n          equals({file.path: <Diagnostic>[]}),\n        );\n\n        File(p.join(tempDir.path, 'analysis_options.yaml')).writeAsStringSync(\n          '''\nbloc:\n  rules:\n''',\n        );\n\n        expect(\n          linter.analyze(uri: file.uri),\n          equals({file.path: <Diagnostic>[]}),\n        );\n      });\n\n      test('analyzes a nested directory file', () {\n        final nested = Directory(p.join(tempDir.path, 'nested'))\n          ..createSync(recursive: true);\n        final main = File(p.join(nested.path, 'main.dart'))\n          ..writeAsStringSync('void main() {}');\n        final other = File(p.join(nested.path, 'other.dart'))\n          ..writeAsStringSync('void other() {}');\n        expect(\n          linter.analyze(uri: nested.uri),\n          equals({main.path: <Diagnostic>[], other.path: <Diagnostic>[]}),\n        );\n      });\n\n      lintTest(\n        'does not report when rule is ignored for file',\n        rule: AvoidFlutterImports.new,\n        path: 'counter_bloc.dart',\n        content: '''\n// ignore_for_file: ${AvoidFlutterImports.rule}\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n      );\n\n      lintTest(\n        'does not report when rule is ignored for file (w/leading space)',\n        rule: AvoidFlutterImports.new,\n        path: 'counter_bloc.dart',\n        content: '''\n // ignore_for_file: ${AvoidFlutterImports.rule}\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n      );\n\n      lintTest(\n        'does not report when rule is ignored for file (w/in-between space)',\n        rule: AvoidFlutterImports.new,\n        path: 'counter_bloc.dart',\n        content: '''\n//   ignore_for_file: ${AvoidFlutterImports.rule}\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n      );\n\n      lintTest(\n        'does not report when rule is ignored for file (w/trailing space)',\n        rule: AvoidFlutterImports.new,\n        path: 'counter_bloc.dart',\n        content: '''\n//   ignore_for_file: ${AvoidFlutterImports.rule}  \nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n      );\n    });\n\n    lintTest(\n      'does not report when ignore_for_file contains type=lint',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\n// ignore_for_file: type=lint\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when //ignore: type=lint above',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\n// ignore: type=lint\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when rule is ignored for line (above)',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\n// ignore: ${AvoidFlutterImports.rule}\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when //ignore: type=lint after',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart'; // ignore: type=lint\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when rule is ignored for line (after)',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart'; // ignore: ${AvoidFlutterImports.rule}\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when rule is ignored for line (after w/leading space)',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart'; //   ignore: ${AvoidFlutterImports.rule}\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when rule is ignored for line (after w/in-between space)',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart'; // ignore:   ${AvoidFlutterImports.rule}\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when rule is ignored for line (after w/trailing space)',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart'; // ignore: ${AvoidFlutterImports.rule}  \n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when file is generated',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.g.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not report when file is in a dot directory',\n      rule: AvoidFlutterImports.new,\n      path: '.fvm/counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/avoid_build_context_extensions_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(AvoidBuildContextExtensions, () {\n    group('lints when', () {\n      group('context.read', () {\n        lintTest(\n          'is used in a field assignment',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatefulWidget {\n  const MyWidget({super.key});\n\n  @override\n  State<MyWidget> createState() => _MyWidgetState();\n}\n\nclass _MyWidgetState extends State<MyWidget> {\n  late final a = context.read<CounterCubit>();\n                 ^^^^^^^^^^^^\n\n  @override\n  Widget build(BuildContext context) {\n    return const SizedBox();\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a variable assignment',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final cubit = context.read<CounterCubit>();\n                  ^^^^^^^^^^^^\n    return const SizedBox();\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a method call',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    context.read<CounterCubit>().increment();\n    ^^^^^^^^^^^^\n    return const SizedBox();\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is declared with another variable on the same line',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final String someVar, counterBloc = context.read<CounterBloc>();\n                                        ^^^^^^^^^^^^\n    return const SizedBox();\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used with an inferred bloc type',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final CounterBloc counterBloc = context.read();\n                                    ^^^^^^^^^^^^\n    return const SizedBox();\n  }\n}\n''',\n        );\n      });\n\n      group('context.watch', () {\n        lintTest(\n          'is used in a getter',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatefulWidget {\n  const MyWidget({super.key});\n\n  @override\n  State<MyWidget> createState() => _MyWidgetState();\n}\n\nclass _MyWidgetState extends State<MyWidget> {\n  CounterCubit get cubit => context.watch<CounterCubit>();\n                            ^^^^^^^^^^^^^\n\n  @override\n  Widget build(BuildContext context) {\n    return const SizedBox();\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a variable assignment',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final count = context.watch<CounterCubit>().state;\n                  ^^^^^^^^^^^^^\n    return Text(count.toString());\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a widget',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Text('${context.watch<CounterCubit>().state}');\n                   ^^^^^^^^^^^^^\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a ternary operator',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final state = true ? context.watch<CounterCubit>().state : null;\n                         ^^^^^^^^^^^^^\n    return Text(state.toString());\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used with Cubit type',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final userCubit = context.watch<UserCubit>();\n                      ^^^^^^^^^^^^^\n    return const SizedBox();\n  }\n}\n''',\n        );\n      });\n\n      group('context.select', () {\n        lintTest(\n          'is used in a variable assignment',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final isEven = context.select<CounterCubit, int>((cubit) => cubit.state.isEven);\n                   ^^^^^^^^^^^^^^\n    return Text(isEven ? 'true' : 'false');\n  }\n}\n''',\n        );\n\n        lintTest(\n          'is used in a widget',\n          rule: AvoidBuildContextExtensions.new,\n          path: 'my_widget.dart',\n          content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Text('${context.select<CounterCubit, int>((cubit) => cubit.state)}');\n                   ^^^^^^^^^^^^^^\n  }\n}\n''',\n        );\n      });\n    });\n\n    group('does not lint when', () {\n      lintTest(\n        'using BlocProvider.of',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final cubit = BlocProvider.of<CounterCubit>(context);\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using BlocProvider(create: ...)',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (context) => CounterCubit(),\n      child: const SizedBox(),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using other method calls on context',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final size = context.size;\n    final theme = context.theme;\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context in a string',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final message = 'context.read is allowed';\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context.read with non-bloc types',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final provider = context.read<MyProvider>();\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context.watch with non-bloc types',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final provider = context.watch<MyProvider>();\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context.select with non-bloc types',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final value = context.select<MyProvider, String>((provider) => provider.value);\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context extensions with implicit dynamic types',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    // These would be invalid Dart but should not trigger our rule\n    context.read();\n    context.watch();\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'using context.read with type inference for non-bloc types',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final MyProvider provider = context.read();\n    return const SizedBox();\n  }\n}\n''',\n      );\n\n      lintTest(\n        'the call target is not context (e.g. cascade)',\n        rule: AvoidBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyThing {\n  MyThing read<T>() => this;\n}\n\nclass MyWidget extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final thing = MyThing()..read<CounterCubit>();\n    return const SizedBox();\n  }\n}\n''',\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/avoid_flutter_imports_test.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(AvoidFlutterImports, () {\n    lintTest(\n      'lints when bloc contains flutter import',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit contains flutter import',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:flutter/material.dart';\n       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nclass CounterCubit extends Cubit<int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when no flutter import exists',\n      rule: AvoidFlutterImports.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when flutter import exists outside of bloc',\n      rule: AvoidFlutterImports.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:flutter/material.dart';\nvoid main() => runApp(MyApp());\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/avoid_public_bloc_methods_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(AvoidPublicBlocMethods, () {\n    lintTest(\n      'lints when bloc contains public methods',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  void foo() { print ('foo'); }\n       ^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when file name does not follow naming conventions',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  void foo() { print ('foo'); }\n       ^^^\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when no public methods exist',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint allowed methods',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  Future<void> close() => super.close();\n}\n''',\n    );\n\n    lintTest(\n      'does not lint public method overrides',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  @override\n  bool operator==(bool value) => false;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when public methods exist on Cubit',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n  \n  void increment() => emit(state + 1);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint on internal switch expression',\n      rule: AvoidPublicBlocMethods.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n  bool _isEven(int x) => switch (x) {\n    int() => false,\n  };\n}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/avoid_public_fields_test.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(AvoidPublicFields, () {\n    lintTest(\n      'lints when bloc contains a public, mutable field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  var count = 0;\n  ^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when file name does not follow naming conventions',\n      rule: AvoidPublicFields.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  var count = 0;\n  ^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit contains a public, mutable field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  var count = 0;\n  ^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc contains a public, mutable field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  int count = 0;\n  ^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit contains a public, mutable field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  int count = 0;\n  ^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, final field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  final int count;\n  ^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit has a public, final field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(this.count) : super(0);\n\n  final int count;\n  ^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, final field with private type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\ntypedef _Count = int;\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  final _Count count;\n  ^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit has a public, final field with private type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\ntypedef _Count = int;\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(this.count) : super(0);\n\n  final _Count count;\n  ^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, late field with private type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\ntypedef _Count = int;\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  late _Count count;\n  ^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, late, final field with private type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\ntypedef _Count = int;\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  late final _Count count;\n  ^^^^^^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit has a public, late, final field with private type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\ntypedef _Count = int;\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(this.count) : super(0);\n\n  late final _Count count;\n  ^^^^^^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, late field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  late int count;\n  ^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit has a public, late field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(this.count) : super(0);\n\n  late int count;\n  ^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when bloc has a public, late, final field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc(this.count) : super(0);\n\n  late final int count;\n  ^^^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when cubit has a public, late, final field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit(this.count) : super(0);\n\n  late final int count;\n  ^^^^^^^^^^^^^^^^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when bloc contains a private, mutable field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  var _count = 0;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when cubit contains a private, mutable field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  var _count = 0;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when bloc contains a private, mutable field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  StreamSubscription? _subscription;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when cubit contains a private, mutable field with type',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  StreamSubscription<int>? _subscription;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when bloc has a private, late, final field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  late final int _count;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when cubit has a private, late, final field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<CounterEvent, int> {\n  CounterCubit() : super(0);\n\n  late final int _count;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when bloc has a public, static field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n\n  static const count = 0;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when cubit has a public, static field',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  static const count = 0;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when bloc has no fields',\n      rule: AvoidPublicFields.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when cubit has no fields',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when external class contains public fields',\n      rule: AvoidPublicFields.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass Other {\n  var state = 0;\n}\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/prefer_bloc_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(PreferBloc, () {\n    lintTest(\n      'lints when class extends Cubit',\n      rule: PreferBloc.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n      ^^^^^^^^^^^^\n  CounterCubit() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'lints when class extends HydratedCubit',\n      rule: PreferBloc.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\nclass CounterCubit extends HydratedCubit<int> {\n      ^^^^^^^^^^^^\n  CounterCubit() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'lints when class extends MockCubit',\n      rule: PreferBloc.new,\n      path: 'app_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n      ^^^^^^^^^^^^^^^^^\n''',\n    );\n\n    lintTest(\n      'does not lint when class extends Bloc',\n      rule: PreferBloc.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when class extends MockBloc',\n      rule: PreferBloc.new,\n      path: 'app_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/prefer_build_context_extensions_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(PreferBuildContextExtensions, () {\n    group('BlocBuilder', () {\n      lintTest(\n        'lints when using BlocBuilder',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterCubit, int>(\n           ^^^^^^^^^^^\n      builder: (context, state) => Text('$state'),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'lints when using BlocBuilder (buildWhen)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterCubit, int>(\n           ^^^^^^^^^^^\n      buildWhen: (previous, current) => previous != current,\n      builder: (context, state) => Text('$state'),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'lints when using BlocBuilder (explicit bloc)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({required this.bloc, super.key});\n\n  final CounterBloc bloc;\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder(\n           ^^^^^^^^^^^\n      bloc: bloc,\n      builder: (context, state) => Text('$state'),\n    );\n  }\n}\n''',\n      );\n    });\n\n    group('BlocSelector', () {\n      lintTest(\n        'lints when using BlocSelector',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<CounterCubit, int, bool>(\n           ^^^^^^^^^^^^\n      selector: (state) => state.isEven,\n      builder: (context, isEven) => Text('$isEven'),\n    );\n  }\n}\n''',\n      );\n    });\n\n    group('BlocProvider', () {\n      lintTest(\n        'lints when using BlocProvider.of (assignment)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: r'''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final count = BlocProvider.of<CounterCubit>(context).state;\n                  ^^^^^^^^^^^^^^^\n    return const Text('\\$count');\n  }\n}\n''',\n      );\n\n      lintTest(\n        'lints when using BlocProvider.of (invocation)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {        \n    return FloatingActionButton(\n      child: const Icon(Icons.add),\n      onPressed: () => BlocProvider.of<CounterBloc>().add(CounterEvent.increment);\n                       ^^^^^^^^^^^^^^^        \n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'does not lint when using BlocProvider(create: ...)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => CounterCubit(),\n      child: const SizedBox(),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'does not lint when using BlocProvider.value',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatefulWidget {\n  const MyWidget({super.key});\n\n  @override\n  State<MyWidget> createState() => _MyWidgetState();\n}\nclass _MyWidgetState extends State<MyWidget> {\n  final bloc = CounterBloc();\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider.value(\n      value: bloc,\n      child: const SizedBox(),\n    );\n  }\n\n  @override\n  void dispose() {\n    bloc.close();\n  }\n}\n''',\n      );\n    });\n\n    group('RepositoryProvider', () {\n      lintTest(\n        'lints when using RepositoryProvider.of (assignment)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {    \n    return BlocProvider(\n      create: (context) => WeatherBloc(\n        weatherRepository: RepositoryProvider.of<WeatherRepository>(context),\n                           ^^^^^^^^^^^^^^^^^^^^^\n      ),\n      child: WeatherView(),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'lints when using RepositoryProvider.of (invocation)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_button.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyButton extends StatelessWidget {\n  const MyButton({super.key});\n\n  @override\n  Widget build(BuildContext context) {        \n    return FloatingActionButton(\n      child: const Icon(Icons.add),\n      onPressed: () => RepositoryProvider.of<MyRepository>().add(),\n                       ^^^^^^^^^^^^^^^^^^^^^                       \n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'does not lint when using RepositoryProvider(create: ...)',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatelessWidget {\n  const MyWidget({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider(\n      create: (_) => MyRepository(),\n      child: const SizedBox(),\n    );\n  }\n}\n''',\n      );\n\n      lintTest(\n        'does not lint when using RepositoryProvider.value',\n        rule: PreferBuildContextExtensions.new,\n        path: 'my_widget.dart',\n        content: '''\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass MyWidget extends StatefulWidget {\n  const MyWidget({super.key});\n\n  @override\n  State<MyWidget> createState() => _MyWidgetState();\n}\nclass _MyWidgetState extends State<MyWidget> {\n  final repository = MyRepository();\n\n  @override\n  Widget build(BuildContext context) {\n    return RepositoryProvider.value(\n      value: repository,\n      child: const SizedBox(),\n    );\n  }  \n}\n''',\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/prefer_cubit_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(PreferCubit, () {\n    lintTest(\n      'lints when class extends Bloc',\n      rule: PreferCubit.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n      ^^^^^^^^^^^\n  CounterBloc() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'lints when class extends HydratedBloc',\n      rule: PreferCubit.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n      ^^^^^^^^^^^\n  CounterBloc() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'lints when class extends MockBloc',\n      rule: PreferCubit.new,\n      path: 'app_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n      ^^^^^^^^^^^^^^^^\n''',\n    );\n\n    lintTest(\n      'does not lint when class extends Cubit',\n      rule: PreferCubit.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when class extends MockCubit',\n      rule: PreferCubit.new,\n      path: 'app_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass _MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/prefer_file_naming_conventions_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(PreferFileNamingConventions, () {\n    lintTest(\n      'lints when CounterBloc is declared in main.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n      ^^^^^^^^^^^\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'lints when CounterBloc is declared in counter_cubit.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n      ^^^^^^^^^^^\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'lints when CounterCubit is declared in main.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n      ^^^^^^^^^^^^\n  CounterCubit() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'lints when CounterCubit is declared in counter_bloc.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n      ^^^^^^^^^^^^\n  CounterCubit() : super(0);       \n}\n''',\n    );\n\n    lintTest(\n      'does not lint when CounterBloc is declared in counter_bloc.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_bloc.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment, decrement }\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when CounterCubit is declared in counter_cubit.dart',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when extending MockBloc in tests',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\n\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\n''',\n    );\n\n    lintTest(\n      'does not lint when extending MockCubit in tests',\n      rule: PreferFileNamingConventions.new,\n      path: 'counter_test.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\n\nclass MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/rules/prefer_void_public_cubit_methods_test.dart",
    "content": "import 'package:bloc_lint/src/rules/rules.dart';\nimport 'package:test/test.dart';\n\nimport '../lint_test_helper.dart';\n\nvoid main() {\n  group(PreferVoidPublicCubitMethods, () {\n    lintTest(\n      'does not lint when no public methods exist on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when void public methods exist on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when Future<void> public methods exist on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  Future<void> increment() async => emit(state + 1);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when FutureOr<void> public methods exist on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  FutureOr<void> increment() async => emit(state + 1);\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when void public getter exists on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void get foo => null;\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when explicit void public setter exists on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n  \n  String _tag = '';\n\n  void set tag(String value) {\n    tag = value;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when implicit void public setter exists on Cubit',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n  \n  String _tag = '';\n\n  set tag(String value) {\n    tag = value;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'does not lint for overridden methods',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n  \n  @override\n  String toString() => 'CounterCubit';\n}\n''',\n    );\n\n    lintTest(\n      'does not lint when non-void public methods exist on other classes',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass Dash {\n  bool get isCool => true;\n}\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''',\n    );\n\n    lintTest(\n      'lints when file name does not follow naming conventions',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'main.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  bool get isEven => state.isEven;\n           ^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when public getter exists on Cubit (bool)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  bool get isEven => state.isEven;\n           ^^^^^^\n}\n''',\n    );\n\n    lintTest(\n      'lints when public methods exist on Cubit (int)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  int increment() {\n      ^^^^^^^^^\n    emit(state + 1);\n    return state;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'lints when public methods exist on Cubit (Future<int>)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  Future<int> increment() async {\n              ^^^^^^^^^\n    emit(state + 1);\n    return state;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'lints when public methods exist on Cubit (Future<Map<String, dynamic>>)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  Future<Map<String, dynamic>> increment() async {\n                               ^^^^^^^^^\n    emit(state + 1);\n    return {'count': state};\n  }\n}\n''',\n    );\n\n    lintTest(\n      'lints when public methods exist on Cubit (explicit dynamic)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  dynamic increment() {\n          ^^^^^^^^^\n    emit(state + 1);\n    return state;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'lints when public methods exist on Cubit (implicit dynamic)',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  increment() {\n  ^^^^^^^^^\n    emit(state + 1);\n    return state;\n  }\n}\n''',\n    );\n\n    lintTest(\n      'does not lint on internal switch expression',\n      rule: PreferVoidPublicCubitMethods.new,\n      path: 'counter_cubit.dart',\n      content: '''\nimport 'package:bloc/bloc.dart';\n\nclass ToggleCubit extends Cubit<bool> {\n  ToggleCubit() : super(false);\n\n  void toggle() => switch (state) {\n    true => emit(false),\n    false => emit(true),\n  };\n}\n''',\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_lint/test/src/text_document_test.dart",
    "content": "import 'package:bloc_lint/bloc_lint.dart';\nimport 'package:test/test.dart';\n\nPosition at({required int line, required int char}) {\n  return Position(character: char, line: line);\n}\n\nPosition position(int line, int char) => at(line: line, char: char);\n\nRange range(int startLine, int startChar, int endLine, int endChar) {\n  return Range(\n    start: at(line: startLine, char: startChar),\n    end: at(line: endLine, char: endChar),\n  );\n}\n\nTextDocument createDocument(String content) {\n  return TextDocument(uri: Uri.parse('test://hello/world'), content: content);\n}\n\nvoid main() {\n  group(TextDocument, () {\n    group('lines, offsets and positions', () {\n      test('empty content', () {\n        final document = createDocument('');\n        expect(document.offsetAt(position(0, 0)), equals(0));\n        final pos = document.positionAt(0);\n        expect(pos.line, equals(0));\n        expect(pos.character, equals(0));\n      });\n\n      test('single line', () {\n        const content = 'Hello World';\n        final document = createDocument(content);\n\n        for (var i = 0; i < content.length; i++) {\n          expect(document.offsetAt(position(0, i)), equals(i));\n\n          final pos = document.positionAt(i);\n          expect(pos.line, equals(0));\n          expect(pos.character, equals(i));\n        }\n      });\n\n      test('multiple lines', () {\n        const content = 'abcde\\nfghij\\nklmno\\n';\n        final document = createDocument(content);\n\n        for (var i = 0; i < content.length; i++) {\n          final line = (i / 6).floor();\n          final char = i % 6;\n          expect(document.offsetAt(position(line, char)), equals(i));\n\n          final pos = document.positionAt(i);\n          expect(pos.line, equals(line));\n          expect(pos.character, equals(char));\n        }\n\n        // Out of bounds.\n        expect(document.offsetAt(position(3, 0)), content.length);\n        expect(document.offsetAt(position(3, 1)), content.length);\n\n        var pos = document.positionAt(18);\n        expect(pos.line, equals(3));\n        expect(pos.character, equals(0));\n\n        pos = document.positionAt(19);\n        expect(pos.line, equals(3));\n        expect(pos.character, equals(0));\n      });\n\n      test('getText', () {\n        const content = 'abcde\\nfghij\\nklmno';\n        final document = createDocument(content);\n        expect(document.getText(), equals(content));\n\n        expect(document.getText(range: range(0, 0, 0, 5)), equals('abcde'));\n        expect(document.getText(range: range(0, 4, 1, 1)), equals('e\\nf'));\n      });\n\n      test('invalid input at beginning of file', () {\n        final document = createDocument('asdf');\n        expect(document.offsetAt(position(-1, 0)), 0);\n        expect(document.offsetAt(position(0, -1)), 0);\n\n        final pos = document.positionAt(-1);\n        expect(pos.line, equals(0));\n        expect(pos.character, equals(0));\n      });\n\n      test('invalid input at end of file', () {\n        final document = createDocument('asdf');\n        expect(document.offsetAt(position(1, 1)), 4);\n\n        final pos = document.positionAt(8);\n        expect(pos.line, equals(0));\n        expect(pos.character, equals(4));\n      });\n\n      test('invalid input at beginning of line', () {\n        final document = createDocument('a\\ns\\nd\\r\\nf');\n        expect(document.offsetAt(position(0, -1)), 0);\n        expect(document.offsetAt(position(1, -1)), 2);\n        expect(document.offsetAt(position(2, -1)), 4);\n        expect(document.offsetAt(position(3, -1)), 7);\n      });\n\n      test('invalid input at end of line', () {\n        final document = createDocument('a\\ns\\nd\\r\\nf');\n        expect(document.offsetAt(position(0, 10)), 1);\n        expect(document.offsetAt(position(1, 10)), 3);\n        expect(document.offsetAt(position(2, 2)), 5);\n        expect(document.offsetAt(position(2, 3)), 5);\n        expect(document.offsetAt(position(2, 10)), 5);\n        expect(document.offsetAt(position(3, 10)), 8);\n\n        final pos = document.positionAt(6);\n        expect(pos.line, equals(2));\n        expect(pos.character, equals(1));\n      });\n    });\n  });\n\n  group(TextDocumentType, () {\n    test('detects file types', () {\n      final blocFile = TextDocument(\n        uri: Uri.parse('counter_bloc.dart'),\n        content: '',\n      );\n      expect(blocFile.type, equals(TextDocumentType.bloc));\n      expect(blocFile.type.isBloc, isTrue);\n      expect(blocFile.type.isCubit, isFalse);\n      expect(blocFile.type.isOther, isFalse);\n\n      final cubitFile = TextDocument(\n        uri: Uri.parse('counter_cubit.dart'),\n        content: '',\n      );\n      expect(cubitFile.type, equals(TextDocumentType.cubit));\n      expect(cubitFile.type.isBloc, isFalse);\n      expect(cubitFile.type.isCubit, isTrue);\n      expect(cubitFile.type.isOther, isFalse);\n\n      final otherFile = TextDocument(uri: Uri.parse('main.dart'), content: '');\n      expect(otherFile.type, equals(TextDocumentType.other));\n      expect(otherFile.type.isBloc, isFalse);\n      expect(otherFile.type.isCubit, isFalse);\n      expect(otherFile.type.isOther, isTrue);\n    });\n  });\n\n  group('ignoreForFile', () {\n    test('returns empty when no file ignores exist', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_bloc.dart'),\n        content: '''\nvoid main() {\n  print(\"hello world\");\n}\n''',\n      );\n      expect(document.ignoreForFile, isEmpty);\n    });\n\n    test('detects ignore_for_file at start of file', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_bloc.dart'),\n        content: '''\n// ignore_for_file: ${PreferBloc.rule}, ${PreferCubit.rule}\nvoid main() {\n  print(\"hello world\");\n}\n''',\n      );\n      expect(\n        document.ignoreForFile,\n        equals({PreferBloc.rule, PreferCubit.rule}),\n      );\n    });\n\n    test('detects multiple ignore_for_file', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_bloc.dart'),\n        content: '''\n// ignore_for_file: ${PreferBloc.rule}\n// ignore_for_file: ${PreferCubit.rule}\nvoid main() {\n  print(\"hello world\");\n}\n''',\n      );\n      expect(\n        document.ignoreForFile,\n        equals({PreferBloc.rule, PreferCubit.rule}),\n      );\n    });\n\n    test('ignore_for_file throughout file', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_bloc.dart'),\n        content: '''\nvoid main() {\n// ignore_for_file: ${PreferBloc.rule}\n  print(\"hello world\");\n}\n// ignore_for_file: ${PreferCubit.rule}\n''',\n      );\n      expect(\n        document.ignoreForFile,\n        equals({PreferBloc.rule, PreferCubit.rule}),\n      );\n    });\n  });\n\n  group('ignoreForLine', () {\n    test('returns empty when no ignore exists', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_cubit.dart'),\n        content: '''\nimport 'package:flutter/material.dart';\n''',\n      );\n      const range = Range(\n        start: Position(character: 0, line: 0),\n        end: Position(character: 39, line: 0),\n      );\n      expect(document.ignoreForLine(range: range), isEmpty);\n    });\n\n    test('returns ignores above line', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_cubit.dart'),\n        content: '''\n// ignore: ${PreferBloc.rule}, ${PreferCubit.rule}\nimport 'package:flutter/material.dart';''',\n      );\n      const range = Range(\n        start: Position(character: 0, line: 1),\n        end: Position(character: 39, line: 1),\n      );\n      expect(\n        document.ignoreForLine(range: range),\n        equals({PreferBloc.rule, PreferCubit.rule}),\n      );\n    });\n\n    test('returns ignores after line', () {\n      final document = TextDocument(\n        uri: Uri.parse('counter_cubit.dart'),\n        content: '''\nimport 'package:flutter/material.dart'; // ignore: ${PreferBloc.rule}, ${PreferCubit.rule}''',\n      );\n      const range = Range(\n        start: Position(character: 0, line: 0),\n        end: Position(character: 39, line: 0),\n      );\n      expect(\n        document.ignoreForLine(range: range),\n        equals({PreferBloc.rule, PreferCubit.rule}),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_test/CHANGELOG.md",
    "content": "# 10.0.0\n\n- refactor: `blocTest` depends on core interfaces instead of `BlocBase` ([#4311](https://github.com/felangel/bloc/pull/4311))\n- chore: update to `bloc: ^9.0.0`\n- chore: bump minimum Dart SDK to 2.14\n- chore: add `funding` to `pubspec.yaml`\n- chore: update sponsors\n\n# 9.1.7\n\n- chore: update copyright year\n- chore: update sponsors\n\n# 9.1.6\n\n- chore: update sponsors ([#4054](https://github.com/felangel/bloc/pull/4054))\n\n# 9.1.5\n\n- fix: `blocTest` supports `async` `expect` ([#3976](https://github.com/felangel/bloc/pull/3976))\n\n# 9.1.4\n\n- deps: support `mocktail: ^1.0.0` ([#3917](https://github.com/felangel/bloc/pull/3917))\n- chore: add topics to `pubspec.yaml` ([#3914](https://github.com/felangel/bloc/pull/3914))\n\n# 9.1.3\n\n- fix: test timeouts due to uncaught exceptions which occur with `package:test ^1.22.2` ([#3854](https://github.com/felangel/bloc/pull/3854))\n\n# 9.1.2\n\n- docs: upgrade to Dart 3\n- refactor: standardize analysis_options\n\n# 9.1.1\n\n- chore: upgrade to `bloc ^8.1.1` ([#3723](https://github.com/felangel/bloc/pull/3723))\n  - refactor: `BlocObserver` instances to use `const` constructors ([#3713](https://github.com/felangel/bloc/pull/3713))\n- refactor: upgrade to Dart 2.19 ([#3699](https://github.com/felangel/bloc/pull/3699))\n  - remove deprecated `invariant_booleans` lint rule\n- docs: fix snippet in `README` ([#3552](https://github.com/felangel/bloc/pull/3552))\n\n# 9.1.0\n\n- feat: upgrade to `bloc: ^8.1.0` ([#3502](https://github.com/felangel/bloc/pull/3502))\n\n# 9.0.3\n\n- chore: support for mocktail v0.3.0 ([#3252](https://github.com/felangel/bloc/pull/3252))\n- docs: update GetStream utm tags ([#3136](https://github.com/felangel/bloc/pull/3136))\n- docs: update VGV sponsors logo ([#3125](https://github.com/felangel/bloc/pull/3125))\n\n# 9.0.2\n\n- fix: throw uncaught exceptions ([#3070](https://github.com/felangel/bloc/pull/3070))\n- chore: upgrade to `bloc v8.0.2`\n- docs: update example to follow naming conventions ([#3032](https://github.com/felangel/bloc/pull/3032))\n\n# 9.0.1\n\n- chore: upgrade to `bloc v8.0.1`\n\n# 9.0.0\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0`\n- feat: `MockBloc` no longer implicitly requires `registerFallbackValue` for events and states\n\n# 9.0.0-dev.5\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`\n\n# 9.0.0-dev.4\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.4`\n\n# 9.0.0-dev.3\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`\n\n# 9.0.0-dev.2\n\n- **BREAKING**: feat: upgrade to `mocktail v0.2.0`\n\n# 9.0.0-dev.1\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`\n- feat: `MockBloc` no longer implicitly requires `registerFallbackValue` for events and states\n\n# 8.5.0\n\n- feat: prettier diffing when using `blocTest` and `expect` does not match emitted states ([#1783](https://github.com/felangel/bloc/issues/1783))\n\n# 8.4.0\n\n- feat: upgrade to `mocktail ^0.2.0`\n\n# 8.3.0\n\n- feat: accept optional tags in `blocTest`\n  - `tags` is optional and if it is passed, it declares user-defined tags that are applied to the test. These tags can be used to select or skip the test on the command line, or to do bulk test configuration.\n\n# 8.2.0\n\n- feat: upgrade to `bloc ^7.2.0`\n\n# 8.1.0\n\n- feat: add `setUp` and `tearDown` to `blocTest`\n\n# 8.0.2\n\n- fix: revert `package:mocktail` export to reduce conflicts with `package:mockito`\n\n# 8.0.1\n\n- fix: allow `blocTest` to capture non-exceptions\n- feat: export `package:mocktail`\n\n# 8.0.0\n\n- **BREAKING**: feat: opt into null safety\n  - upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- **BREAKING**: feat: `seed` returns a `Function` to support dynamic seed values\n- **BREAKING**: refactor: remove `emitsExactly`\n- **BREAKING**: feat: introduce `MockCubit`\n- **BREAKING**: refactor: `MockBloc` uses [package:mocktail](https://pub.dev/packages/mocktail)\n- **BREAKING**: refactor: `expect` returns a `Function` with `Matcher` support\n- **BREAKING**: refactor: `errors` returns a `Function` with `Matcher` support\n- **BREAKING**: refactor: `whenListen` does not stub `skip`\n- feat: `MockBloc` and `MockCubit` automatically stub core API\n- feat: add optional `initialState` to `whenListen`\n- feat: upgrade to `bloc ^7.0.0`\n- feat: upgrade to `mocktail: ^0.1.0`\n\n# 8.0.0-nullsafety.6\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.4`\n\n# 8.0.0-nullsafety.5\n\n- feat: upgrade to `mocktail: ^0.1.0`\n- feat: use `package:test` instead of `package:test_api`\n\n# 8.0.0-nullsafety.4\n\n- **BREAKING**: feat: `seed` returns a `Function` to support dynamic seed values\n\n# 8.0.0-nullsafety.3\n\n- feat: upgrade to `mocktail: \">=0.0.2-dev.5 <0.0.2\"`\n\n# 8.0.0-nullsafety.2\n\n- fix: restrict to `mocktail: \">=0.0.1-dev.12 <0.0.1\"`\n- feat: use `package:test_api` instead of `package:test` for sound null safety\n\n# 8.0.0-nullsafety.1\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.3`\n- chore: upgrade to `mocktail ^0.0.1-dev.12`\n\n# 8.0.0-nullsafety.0\n\n- **BREAKING**: feat: opt into null safety\n- **BREAKING**: feat: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- **BREAKING**: refactor: remove `emitsExactly`\n- **BREAKING**: refactor: `MockBloc` uses [package:mocktail](https://pub.dev/packages/mocktail)\n- **BREAKING**: feat: introduce `MockCubit` which uses [package:mocktail](https://pub.dev/packages/mocktail)\n- **BREAKING**: refactor: `expect` returns a `Function` with `Matcher` support\n- **BREAKING**: refactor: `errors` returns a `Function` with `Matcher` support\n- **BREAKING**: refactor: `whenListen` does not stub `skip`\n- feat: introduce `MockCubit`\n- feat: `MockBloc` and `MockCubit` automatically stub core API\n- feat: add optional `initialState` to `whenListen`\n\n# 7.1.0\n\n- feat: add `seed` property to `blocTest`\n\n# 7.0.6\n\n- chore: revert support `dart >=2.7.0`\n\n# 7.0.5\n\n- fix: update to `mockito ^4.1.2`\n- chore: update to `dart >=2.10.0`\n\n# 7.0.4\n\n- feat: `blocTest` provides warning to implement deep equality when shallow equality is true\n\n# 7.0.3\n\n- restrict `mockito` to `<4.1.2` to prevent breaking changes due to NNBD\n\n# 7.0.2\n\n- fix: `blocTest` timeouts when verify fails ([#1639](https://github.com/felangel/bloc/issues/1639))\n- fix: `blocTest` timeouts when expect fails ([#1645](https://github.com/felangel/bloc/issues/1645))\n\n# 7.0.1\n\n- chore: deprecate `emitsExactly` in favor of `blocTest`\n- fix: capture uncaught exceptions in `Cubit`\n\n# 7.0.0\n\n- **BREAKING**: upgrade to `bloc ^6.0.0`\n- **BREAKING**: `MockBloc` only requires `State` type\n- **BREAKING**: `whenListen` only requires `State` type\n- **BREAKING**: `blocTest` only requires `State` type\n- **BREAKING**: `blocTest` `skip` defaults to `0`\n- **BREAKING**: `blocTest` make `build` synchronous\n- fix: `blocTest` improve `wait` behavior when debouncing, etc...\n- feat: `blocTest` do not require `async` on `act` and `verify`\n- feat: remove external dependency on [package:cubit_test](https://pub.dev/packages/cubit_test)\n- feat: `MockBloc` is compatible with `cubit`\n- feat: `whenListen` is compatible with `cubit`\n- feat: `blocTest` is compatible with `cubit`\n\n# 7.0.0-dev.2\n\n- **BREAKING**: `blocTest` make `build` synchronous\n- fix: `blocTest` improve `wait` behavior when debouncing, etc...\n- feat: `blocTest` do not require `async` on `act` and `verify`\n\n# 7.0.0-dev.1\n\n- **BREAKING**: upgrade to `bloc ^6.0.0-dev.1`\n- **BREAKING**: `MockBloc` only requires `State` type\n- **BREAKING**: `whenListen` only requires `State` type\n- **BREAKING**: `blocTest` only requires `State` type\n- **BREAKING**: `blocTest` `skip` defaults to `0`\n- feat: remove external dependency on [package:cubit_test](https://pub.dev/packages/cubit_test)\n- feat: `MockBloc` is compatible with `cubit`\n- feat: `whenListen` is compatible with `cubit`\n- feat: `blocTest` is compatible with `cubit`\n\n# 6.0.1\n\n- fix: upgrade to `bloc ^5.0.1`\n- fix: upgrade to `cubit_test ^0.1.1`\n- docs: minor documentation updates\n\n# 6.0.0\n\n- feat: upgrade to `bloc ^5.0.0`\n- refactor: internal implementation updates to use [cubit_test](https://pub.dev/packages/cubit_test)\n\n# 6.0.0-dev.4\n\n- Update to `bloc ^5.0.0-dev.11`.\n\n# 6.0.0-dev.3\n\n- Update to `bloc ^5.0.0-dev.10`.\n\n# 6.0.0-dev.2\n\n- Update to `bloc ^5.0.0-dev.7`.\n\n# 6.0.0-dev.1\n\n- Update to `bloc ^5.0.0-dev.6`.\n- Internal implementation updates to use [cubit_test](https://pub.dev/packages/cubit_test)\n\n# 5.1.0\n\n- Add `errors` to `blocTest` to enable expecting unhandled exceptions within blocs.\n- Update `whenListen` to also handle stubbing the state property of the bloc.\n\n# 5.0.0\n\n- Update to `bloc: ^4.0.0`\n\n# 5.0.0-dev.4\n\n- Update to `bloc: ^4.0.0-dev.4`\n\n# 5.0.0-dev.3\n\n- Update to `bloc: ^4.0.0-dev.3`\n\n# 5.0.0-dev.2\n\n- Update to `bloc: ^4.0.0-dev.2`\n\n# 5.0.0-dev.1\n\n- Update to `bloc: ^4.0.0-dev.1`\n\n# 4.0.0\n\n- `blocTest` and `emitsExactly` skip `initialState` by default and expose optional `skip` ([#910](https://github.com/felangel/bloc/issues/910))\n- `blocTest` async `build` ([#910](https://github.com/felangel/bloc/issues/910))\n- `blocTest` `expect` is optional ([#910](https://github.com/felangel/bloc/issues/910))\n- `blocTest` `verify` includes the built bloc ([#910](https://github.com/felangel/bloc/issues/910))\n\n# 3.1.0\n\n- Add `verify` to `blocTest` ([#781](https://github.com/felangel/bloc/issues/781))\n\n# 3.0.1\n\n- Enable `blocTest` to add more than one asynchronous event at a time ([#724](https://github.com/felangel/bloc/issues/724))\n\n# 3.0.0\n\n- Update to `bloc: ^3.0.0`\n- `emitsExactly` supports optional `duration` for async operators like `debounceTime` ([#726](https://github.com/felangel/bloc/issues/726))\n- `blocTest` supports optional `wait` for async operators like `debounceTime` ([#726](https://github.com/felangel/bloc/issues/726))\n\n# 3.0.0-dev.1\n\n- Update to `bloc: ^3.0.0-dev.1`\n\n# 2.2.2\n\n- Minor internal improvements (fixed analysis warning in `emitsExactly`)\n\n# 2.2.1\n\n- Minor documentation improvements (syntax highlighting in README)\n\n# 2.2.0\n\n- `emitsExactly` and `blocTest` support `Iterable<Matcher` ([#695](https://github.com/felangel/bloc/issues/695))\n\n# 2.1.0\n\n- Add `MockBloc` to `bloc_test` in order to simplify bloc mocks (addresses [#636](https://github.com/felangel/bloc/issues/636))\n- Documentation and example updates\n\n# 2.0.0\n\n- Updated to `bloc: ^2.0.0` and Documentation Updates\n- Adhere to [effective dart](https://dart.dev/guides/language/effective-dart) ([#561](https://github.com/felangel/bloc/issues/561))\n\n# 1.0.0\n\nUpdated to `bloc: ^1.0.0` and Documentation Updates\n\n# 0.2.1\n\n`whenListen` automatically converts `Stream` to `BroadcastStream`\n\n# 0.2.0\n\n`whenListen` handles internal `skip` from `BlocBuilder` and `BlocListener`\n\n# 0.1.0\n\nInitial Version of the library.\n\n- Includes `whenListen` to enable mocking a `Bloc` state `Stream`.\n"
  },
  {
    "path": "packages/bloc_test/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "packages/bloc_test/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_test.png\" height=\"100\" alt=\"Bloc Test Package\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/bloc_test\"><img src=\"https://img.shields.io/pub/v/bloc_test.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nA Dart package that makes testing blocs and cubits easy. Built to work with [bloc](https://pub.dev/packages/bloc) and [mocktail](https://pub.dev/packages/mocktail).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Create a Mock\n\n```dart\nimport 'package:bloc_test/bloc_test.dart';\n\nclass MockCounterBloc extends MockBloc<CounterEvent, int> implements CounterBloc {}\nclass MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n```\n\n## Stub the State Stream\n\n**whenListen** creates a stub response for the `listen` method on a bloc or cubit. Use `whenListen` if you want to return a canned `Stream` of states. `whenListen` also handles stubbing the `state` to stay in sync with the emitted state.\n\n```dart\n// Create a mock instance\nfinal counterBloc = MockCounterBloc();\n\n// Stub the state stream\nwhenListen(\n  counterBloc,\n  Stream.fromIterable([0, 1, 2, 3]),\n  initialState: 0,\n);\n\n// Assert that the initial state is correct.\nexpect(counterBloc.state, equals(0));\n\n// Assert that the stubbed stream is emitted.\nawait expectLater(counterBloc.stream, emitsInOrder(<int>[0, 1, 2, 3]));\n\n// Assert that the current state is in sync with the stubbed stream.\nexpect(counterBloc.state, equals(3));\n```\n\n## Unit Test with blocTest\n\n**blocTest** creates a new `bloc`-specific test case with the given `description`.\n`blocTest` will handle asserting that the `bloc` emits the `expect`ed states (in order) after `act` is executed. `blocTest` also handles ensuring that no additional states are emitted by closing the `bloc` stream before evaluating the `expect`ation.\n\n`setUp` is optional and should be used to set up any dependencies prior to initializing the `bloc` under test. `setUp` should be used to set up state necessary for a particular test case. For common set up code, prefer to use `setUp` from `package:test/test.dart`.\n\n`build` should construct and return the `bloc` under test.\n\n`seed` is an optional `Function` that returns a state which will be used to seed the `bloc` before `act` is called.\n\n`act` is an optional callback which will be invoked with the `bloc` under test and should be used to interact with the `bloc`.\n\n`skip` is an optional `int` which can be used to skip any number of states. `skip` defaults to 0.\n\n`wait` is an optional `Duration` which can be used to wait for async operations within the `bloc` under test such as `debounceTime`.\n\n`expect` is an optional `Function` that returns a `Matcher` which the `bloc` under test is expected to emit after `act` is executed.\n\n`verify` is an optional callback which is invoked after `expect` and can be used for additional verification/assertions. `verify` is called with the `bloc` returned by `build`.\n\n`errors` is an optional `Function` that returns a `Matcher` which the `bloc` under test is expected to throw after `act` is executed.\n\n`tearDown` is optional and can be used to execute any code after the test has run. `tearDown` should be used to clean up after a particular test case. For common tear down code, prefer to use `tearDown` from `package:test/test.dart`.\n\n`tags` is optional and if it is passed, it declares user-defined tags that are applied to the test. These tags can be used to select or skip the test on the command line, or to do bulk test configuration.\n\n```dart\ngroup('CounterBloc', () {\n  blocTest(\n    'emits [] when nothing is added',\n    build: () => CounterBloc(),\n    expect: () => [],\n  );\n\n  blocTest(\n    'emits [1] when CounterIncrementPressed is added',\n    build: () => CounterBloc(),\n    act: (bloc) => bloc.add(CounterIncrementPressed()),\n    expect: () => [1],\n  );\n});\n```\n\n`blocTest` can optionally be used with a seeded state.\n\n```dart\nblocTest(\n  'emits [10] when seeded with 9',\n  build: () => CounterBloc(),\n  seed: () => 9,\n  act: (bloc) => bloc.add(CounterIncrementPressed()),\n  expect: () => [10],\n);\n```\n\n`blocTest` can also be used to `skip` any number of emitted states before asserting against the expected states. The default value is 0.\n\n```dart\nblocTest(\n  'emits [2] when CounterIncrementPressed is added twice',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc..add(CounterIncrementPressed())..add(CounterIncrementPressed()),\n  skip: 1,\n  expect: () => [2],\n);\n```\n\n`blocTest` can also be used to wait for async operations like `debounceTime` by providing a `Duration` to `wait`.\n\n```dart\nblocTest(\n  'emits [MyState] when MyEvent is added',\n  build: () => MyBloc(),\n  act: (bloc) => bloc.add(MyEvent()),\n  wait: const Duration(milliseconds: 300),\n  expect: () => [isA<MyState>()],\n);\n```\n\n`blocTest` can also be used to `verify` internal bloc functionality.\n\n```dart\nblocTest(\n  'emits [MyState] when MyEvent is added',\n  build: () => MyBloc(),\n  act: (bloc) => bloc.add(MyEvent()),\n  expect: () => [isA<MyState>()],\n  verify: (_) {\n    verify(() => repository.someMethod(any())).called(1);\n  }\n);\n```\n\n`blocTest` can also be used to expect that exceptions have been thrown.\n\n```dart\nblocTest(\n  'throws Exception when null is added',\n  build: () => MyBloc(),\n  act: (bloc) => bloc.add(null),\n  errors: () => [isA<Exception>()]\n);\n```\n\n**Note:** when using `blocTest` with state classes which don't override `==` and `hashCode` you can provide an `Iterable` of matchers instead of explicit state instances.\n\n```dart\nblocTest(\n  'emits [MyState] when MyEvent is added',\n  build: () => MyBloc(),\n  act: (bloc) => bloc.add(MyEvent()),\n  expect: () => [isA<MyState>()],\n);\n```\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/bloc_test/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n"
  },
  {
    "path": "packages/bloc_test/example/main.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:test/test.dart';\n\n// Mock Cubit\nclass MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n\n// Mock Bloc\nclass MockCounterBloc extends MockBloc<CounterEvent, int>\n    implements CounterBloc {}\n\nvoid main() {\n  mainCubit();\n  mainBloc();\n}\n\nvoid mainCubit() {\n  group('whenListen', () {\n    test(\"Let's mock the CounterCubit's stream!\", () {\n      // Create Mock CounterCubit Instance\n      final cubit = MockCounterCubit();\n\n      // Stub the listen with a fake Stream\n      whenListen(cubit, Stream.fromIterable([0, 1, 2, 3]));\n\n      // Expect that the CounterCubit instance emitted the stubbed Stream of\n      // states\n      expectLater(cubit.stream, emitsInOrder(<int>[0, 1, 2, 3]));\n    });\n  });\n\n  group('CounterCubit', () {\n    blocTest<CounterCubit, int>(\n      'emits [] when nothing is called',\n      build: () => CounterCubit(),\n      expect: () => const <int>[],\n    );\n\n    blocTest<CounterCubit, int>(\n      'emits [1] when increment is called',\n      build: () => CounterCubit(),\n      act: (cubit) => cubit.increment(),\n      expect: () => const <int>[1],\n    );\n  });\n}\n\nvoid mainBloc() {\n  group('whenListen', () {\n    test(\"Let's mock the CounterBloc's stream!\", () {\n      // Create Mock CounterBloc Instance\n      final bloc = MockCounterBloc();\n\n      // Stub the listen with a fake Stream\n      whenListen(bloc, Stream.fromIterable([0, 1, 2, 3]));\n\n      // Expect that the CounterBloc instance emitted the stubbed Stream of\n      // states\n      expectLater(bloc.stream, emitsInOrder(<int>[0, 1, 2, 3]));\n    });\n  });\n\n  group('CounterBloc', () {\n    blocTest<CounterBloc, int>(\n      'emits [] when nothing is added',\n      build: () => CounterBloc(),\n      expect: () => const <int>[],\n    );\n\n    blocTest<CounterBloc, int>(\n      'emits [1] when CounterIncrementPressed is added',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterIncrementPressed()),\n      expect: () => const <int>[1],\n    );\n  });\n}\n\n// ignore: prefer_file_naming_conventions\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n\nabstract class CounterEvent {}\n\nclass CounterIncrementPressed extends CounterEvent {}\n\n// ignore: prefer_file_naming_conventions\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/lib/bloc_test.dart",
    "content": "/// A testing library which makes it easy to test blocs.\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary bloc_test;\n\nexport 'src/bloc_test.dart';\nexport 'src/mock_bloc.dart';\nexport 'src/when_listen.dart';\n"
  },
  {
    "path": "packages/bloc_test/lib/src/bloc_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:diff_match_patch/diff_match_patch.dart';\nimport 'package:meta/meta.dart';\nimport 'package:test/test.dart' as test;\n\n/// Creates a new `bloc`-specific test case with the given [description].\n/// [blocTest] will handle asserting that the `bloc` emits the [expect]ed\n/// states (in order) after [act] is executed.\n/// [blocTest] also handles ensuring that no additional states are emitted\n/// by closing the `bloc` stream before evaluating the [expect]ation.\n///\n/// [setUp] is optional and should be used to set up\n/// any dependencies prior to initializing the `bloc` under test.\n/// [setUp] should be used to set up state necessary for a particular test case.\n/// For common set up code, prefer to use `setUp` from `package:test/test.dart`.\n///\n/// [build] should construct and return the `bloc` under test.\n///\n/// [seed] is an optional `Function` that returns a state\n/// which will be used to seed the `bloc` before [act] is called.\n///\n/// [act] is an optional callback which will be invoked with the `bloc` under\n/// test and should be used to interact with the `bloc`.\n///\n/// [skip] is an optional `int` which can be used to skip any number of states.\n/// [skip] defaults to 0.\n///\n/// [wait] is an optional `Duration` which can be used to wait for\n/// async operations within the `bloc` under test such as `debounceTime`.\n///\n/// [expect] is an optional `Function` that returns a `Matcher` which the `bloc`\n/// under test is expected to emit after [act] is executed.\n///\n/// [verify] is an optional callback which is invoked after [expect]\n/// and can be used for additional verification/assertions.\n/// [verify] is called with the `bloc` returned by [build].\n///\n/// [errors] is an optional `Function` that returns a `Matcher` which the `bloc`\n/// under test is expected to throw after [act] is executed.\n///\n/// [tearDown] is optional and can be used to\n/// execute any code after the test has run.\n/// [tearDown] should be used to clean up after a particular test case.\n/// For common tear down code, prefer to use `tearDown` from `package:test/test.dart`.\n///\n/// [tags] is optional and if it is passed, it declares user-defined tags\n/// that are applied to the test. These tags can be used to select or\n/// skip the test on the command line, or to do bulk test configuration.\n///\n/// ```dart\n/// blocTest(\n///   'CounterBloc emits [1] when increment is added',\n///   build: () => CounterBloc(),\n///   act: (bloc) => bloc.add(CounterEvent.increment),\n///   expect: () => [1],\n/// );\n/// ```\n///\n/// [blocTest] can optionally be used with a seeded state.\n///\n/// ```dart\n/// blocTest(\n///   'CounterBloc emits [10] when seeded with 9',\n///   build: () => CounterBloc(),\n///   seed: () => 9,\n///   act: (bloc) => bloc.add(CounterEvent.increment),\n///   expect: () => [10],\n/// );\n/// ```\n///\n/// [blocTest] can also be used to [skip] any number of emitted states\n/// before asserting against the expected states.\n/// [skip] defaults to 0.\n///\n/// ```dart\n/// blocTest(\n///   'CounterBloc emits [2] when increment is added twice',\n///   build: () => CounterBloc(),\n///   act: (bloc) {\n///     bloc\n///       ..add(CounterEvent.increment)\n///       ..add(CounterEvent.increment);\n///   },\n///   skip: 1,\n///   expect: () => [2],\n/// );\n/// ```\n///\n/// [blocTest] can also be used to wait for async operations\n/// by optionally providing a `Duration` to [wait].\n///\n/// ```dart\n/// blocTest(\n///   'CounterBloc emits [1] when increment is added',\n///   build: () => CounterBloc(),\n///   act: (bloc) => bloc.add(CounterEvent.increment),\n///   wait: const Duration(milliseconds: 300),\n///   expect: () => [1],\n/// );\n/// ```\n///\n/// [blocTest] can also be used to [verify] internal bloc functionality.\n///\n/// ```dart\n/// blocTest(\n///   'CounterBloc emits [1] when increment is added',\n///   build: () => CounterBloc(),\n///   act: (bloc) => bloc.add(CounterEvent.increment),\n///   expect: () => [1],\n///   verify: (_) {\n///     verify(() => repository.someMethod(any())).called(1);\n///   }\n/// );\n/// ```\n///\n/// **Note:** when using [blocTest] with state classes which don't override\n/// `==` and `hashCode` you can provide an `Iterable` of matchers instead of\n/// explicit state instances.\n///\n/// ```dart\n/// blocTest(\n///  'emits [StateB] when EventB is added',\n///  build: () => MyBloc(),\n///  act: (bloc) => bloc.add(EventB()),\n///  expect: () => [isA<StateB>()],\n/// );\n/// ```\n///\n/// If [tags] is passed, it declares user-defined tags that are applied to the\n/// test. These tags can be used to select or skip the test on the command line,\n/// or to do bulk test configuration. All tags should be declared in the\n/// [package configuration file][configuring tags]. The parameter can be an\n/// [Iterable] of tag names, or a [String] representing a single tag.\n///\n/// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags\n@isTest\nvoid blocTest<B extends EmittableStateStreamableSource<State>, State>(\n  String description, {\n  required B Function() build,\n  FutureOr<void> Function()? setUp,\n  State Function()? seed,\n  dynamic Function(B bloc)? act,\n  Duration? wait,\n  int skip = 0,\n  dynamic Function()? expect,\n  dynamic Function(B bloc)? verify,\n  dynamic Function()? errors,\n  FutureOr<void> Function()? tearDown,\n  dynamic tags,\n}) {\n  test.test(\n    description,\n    () async {\n      await testBloc<B, State>(\n        setUp: setUp,\n        build: build,\n        seed: seed,\n        act: act,\n        wait: wait,\n        skip: skip,\n        expect: expect,\n        verify: verify,\n        errors: errors,\n        tearDown: tearDown,\n      );\n    },\n    tags: tags,\n  );\n}\n\n/// Internal [blocTest] runner which is only visible for testing.\n/// This should never be used directly -- please use [blocTest] instead.\n@visibleForTesting\nFuture<void> testBloc<B extends EmittableStateStreamableSource<State>, State>({\n  required B Function() build,\n  FutureOr<void> Function()? setUp,\n  State Function()? seed,\n  dynamic Function(B bloc)? act,\n  Duration? wait,\n  int skip = 0,\n  dynamic Function()? expect,\n  dynamic Function(B bloc)? verify,\n  dynamic Function()? errors,\n  FutureOr<void> Function()? tearDown,\n}) async {\n  var shallowEquality = false;\n  final unhandledErrors = <Object>[];\n  final localBlocObserver = Bloc.observer;\n  final testObserver = _TestBlocObserver(\n    localBlocObserver,\n    unhandledErrors.add,\n  );\n  Bloc.observer = testObserver;\n\n  try {\n    await _runZonedGuarded(() async {\n      await setUp?.call();\n      final states = <State>[];\n      final bloc = build();\n      if (seed != null) bloc.emit(seed());\n      final subscription = bloc.stream.skip(skip).listen(states.add);\n      try {\n        await act?.call(bloc);\n      } catch (error) {\n        if (errors == null) rethrow;\n        unhandledErrors.add(error);\n      }\n      if (wait != null) await Future<void>.delayed(wait);\n      await Future<void>.delayed(Duration.zero);\n      await bloc.close();\n      if (expect != null) {\n        final dynamic expected = await expect();\n        shallowEquality = '$states' == '$expected';\n        try {\n          test.expect(states, test.wrapMatcher(expected));\n        } on test.TestFailure catch (e) {\n          if (shallowEquality || expected is! List<State>) rethrow;\n          final diff = _diff(expected: expected, actual: states);\n          final message = '${e.message}\\n$diff';\n          throw test.TestFailure(message);\n        }\n      }\n      await subscription.cancel();\n      await verify?.call(bloc);\n      await tearDown?.call();\n    });\n  } catch (error) {\n    if (shallowEquality && error is test.TestFailure) {\n      throw test.TestFailure(\n        '''\n${error.message}\nWARNING: Please ensure state instances extend Equatable, override == and hashCode, or implement Comparable.\nAlternatively, consider using Matchers in the expect of the blocTest rather than concrete state instances.\\n''',\n      );\n    }\n    if (errors == null || !unhandledErrors.contains(error)) {\n      rethrow;\n    }\n  }\n\n  if (errors != null) test.expect(unhandledErrors, test.wrapMatcher(errors()));\n}\n\nFuture<void> _runZonedGuarded(Future<void> Function() body) {\n  final completer = Completer<void>();\n  runZonedGuarded(() async {\n    await body();\n    if (!completer.isCompleted) completer.complete();\n  }, (error, stackTrace) {\n    if (!completer.isCompleted) completer.completeError(error, stackTrace);\n  });\n  return completer.future;\n}\n\nclass _TestBlocObserver extends BlocObserver {\n  const _TestBlocObserver(this._localObserver, this._onError);\n\n  final BlocObserver _localObserver;\n  final void Function(Object error) _onError;\n\n  @override\n  void onCreate(BlocBase<dynamic> bloc) {\n    _localObserver.onCreate(bloc);\n    super.onCreate(bloc);\n  }\n\n  @override\n  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {\n    _localObserver.onEvent(bloc, event);\n    super.onEvent(bloc, event);\n  }\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    _localObserver.onChange(bloc, change);\n    super.onChange(bloc, change);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    _localObserver.onTransition(bloc, transition);\n    super.onTransition(bloc, transition);\n  }\n\n  @override\n  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {\n    _localObserver.onError(bloc, error, stackTrace);\n    _onError(error);\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onDone(\n    Bloc<dynamic, dynamic> bloc,\n    Object? event, [\n    Object? error,\n    StackTrace? stackTrace,\n  ]) {\n    _localObserver.onDone(bloc, event, error, stackTrace);\n    super.onDone(bloc, event, error, stackTrace);\n  }\n\n  @override\n  void onClose(BlocBase<dynamic> bloc) {\n    _localObserver.onClose(bloc);\n    super.onClose(bloc);\n  }\n}\n\nString _diff({required dynamic expected, required dynamic actual}) {\n  final buffer = StringBuffer();\n  final differences = diff(expected.toString(), actual.toString());\n  buffer\n    ..writeln('${\"=\" * 4} diff ${\"=\" * 40}')\n    ..writeln()\n    ..writeln(differences.toPrettyString())\n    ..writeln()\n    ..writeln('${\"=\" * 4} end diff ${\"=\" * 36}');\n  return buffer.toString();\n}\n\nextension on List<Diff> {\n  String toPrettyString() {\n    String identical(String str) => '\\u001b[90m$str\\u001B[0m';\n    String deletion(String str) => '\\u001b[31m[-$str-]\\u001B[0m';\n    String insertion(String str) => '\\u001b[32m{+$str+}\\u001B[0m';\n\n    final buffer = StringBuffer();\n    for (final difference in this) {\n      switch (difference.operation) {\n        case DIFF_EQUAL:\n          buffer.write(identical(difference.text));\n          break;\n        case DIFF_DELETE:\n          buffer.write(deletion(difference.text));\n          break;\n        case DIFF_INSERT:\n          buffer.write(insertion(difference.text));\n          break;\n      }\n    }\n    return buffer.toString();\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/lib/src/mock_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:mocktail/mocktail.dart';\n\n/// {@template mock_bloc}\n/// Extend or mixin this class to mark the implementation as a [MockBloc].\n///\n/// A mocked bloc implements all fields and methods with a default\n/// implementation that does not throw a [NoSuchMethodError],\n/// and may be further customized at runtime to define how it may behave using\n/// [when] and `whenListen`.\n///\n/// _**Note**: It is critical to explicitly provide the event and state\n/// types when extending [MockBloc]_.\n///\n/// **GOOD**\n/// ```dart\n/// class MockCounterBloc extends MockBloc<CounterEvent, int>\n///   implements CounterBloc {}\n/// ```\n///\n/// **BAD**\n/// ```dart\n/// class MockCounterBloc extends MockBloc implements CounterBloc {}\n/// ```\n/// {@endtemplate}\nclass MockBloc<E, S> extends _MockBlocBase<S> implements Bloc<E, S> {}\n\n/// {@template mock_cubit}\n/// Extend or mixin this class to mark the implementation as a [MockCubit].\n///\n/// A mocked cubit implements all fields and methods with a default\n/// implementation that does not throw a [NoSuchMethodError],\n/// and may be further customized at runtime to define how it may behave using\n/// [when] and `whenListen`.\n///\n/// _**Note**: It is critical to explicitly provide the state\n/// types when extending [MockCubit]_.\n///\n/// **GOOD**\n/// ```dart\n/// class MockCounterCubit extends MockCubit<int>\n///   implements CounterCubit {}\n/// ```\n///\n/// **BAD**\n/// ```dart\n/// class MockCounterCubit extends MockBloc implements CounterCubit {}\n/// ```\n/// {@endtemplate}\nclass MockCubit<S> extends _MockBlocBase<S> implements Cubit<S> {}\n\nclass _MockBlocBase<S> extends Mock implements BlocBase<S> {\n  _MockBlocBase() {\n    when(() => stream).thenAnswer((_) => Stream<S>.empty());\n    when(close).thenAnswer((_) => Future<void>.value());\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/lib/src/when_listen.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:mocktail/mocktail.dart';\n\n/// Creates a stub response for the `listen` method on a [bloc].\n/// Use [whenListen] if you want to return a canned `Stream` of states\n/// for a [bloc] instance.\n///\n/// [whenListen] also handles stubbing the `state` of the [bloc] to stay\n/// in sync with the emitted state.\n///\n/// Return a canned state stream of `[0, 1, 2, 3]`\n/// when `counterBloc.stream.listen` is called.\n///\n/// ```dart\n/// whenListen(counterBloc, Stream.fromIterable([0, 1, 2, 3]));\n/// ```\n///\n/// Assert that the `counterBloc` state `Stream` is the canned `Stream`.\n///\n/// ```dart\n/// await expectLater(\n///   counterBloc.stream,\n///   emitsInOrder(\n///     <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n///   )\n/// );\n/// expect(counterBloc.state, equals(3));\n/// ```\n///\n/// Optionally provide an [initialState] to stub the state of the [bloc]\n/// before any subscriptions.\n///\n/// ```dart\n/// whenListen(\n///   counterBloc,\n///   Stream.fromIterable([0, 1, 2, 3]),\n///   initialState: 0,\n/// );\n///\n/// expect(counterBloc.state, equals(0));\n/// ```\nvoid whenListen<State>(\n  BlocBase<State> bloc,\n  Stream<State> stream, {\n  State? initialState,\n}) {\n  final broadcastStream = stream.asBroadcastStream();\n\n  if (initialState != null) {\n    when(() => bloc.state).thenReturn(initialState);\n  }\n\n  when(() => bloc.stream).thenAnswer(\n    (_) => broadcastStream.map((state) {\n      when(() => bloc.state).thenReturn(state);\n      return state;\n    }),\n  );\n}\n"
  },
  {
    "path": "packages/bloc_test/pubspec.yaml",
    "content": "name: bloc_test\ndescription: A testing library which makes it easy to test blocs. Built to be used with the bloc state management package.\nversion: 10.0.0\nrepository: https://github.com/felangel/bloc/tree/master/packages/bloc_test\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://bloclibrary.dev\ndocumentation: https://bloclibrary.dev/getting-started\ntopics: [bloc, state-management, test]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.1.0\n  diff_match_patch: ^0.4.1\n  meta: ^1.3.0\n  mocktail: ^1.0.0\n  test: ^1.16.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  rxdart: ^0.28.0\n\nscreenshots:\n  - description: The bloc test package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/bloc_test/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/bloc_test/test/bloc_bloc_test_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'blocs/blocs.dart';\n\nclass MockRepository extends Mock implements Repository {}\n\nvoid unawaited(Future<void>? _) {}\n\nvoid main() {\n  group('blocTest', () {\n    group('CounterBloc', () {\n      blocTest<CounterBloc, int>(\n        'supports matchers (contains)',\n        build: () => CounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => contains(1),\n      );\n\n      blocTest<CounterBloc, int>(\n        'supports matchers (containsAll)',\n        build: () => CounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => containsAll(<int>[2, 1]),\n      );\n\n      blocTest<CounterBloc, int>(\n        'supports matchers (containsAllInOrder)',\n        build: () => CounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => containsAllInOrder(<int>[1, 2]),\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => CounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added',\n        build: () => CounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added with async act',\n        build: () => CounterBloc(),\n        act: (bloc) async {\n          await Future<void>.delayed(const Duration(seconds: 1));\n          bloc.add(CounterEvent.increment);\n        },\n        expect: () => const <int>[1],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [1, 2] when CounterEvent.increment is called multiple times '\n        'with async act',\n        build: () => CounterBloc(),\n        act: (bloc) async {\n          bloc.add(CounterEvent.increment);\n          await Future<void>.delayed(const Duration(milliseconds: 10));\n          bloc.add(CounterEvent.increment);\n        },\n        expect: () => const <int>[1, 2],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [2] when CounterEvent.increment is added twice and skip: 1',\n        build: () => CounterBloc(),\n        act: (bloc) {\n          bloc\n            ..add(CounterEvent.increment)\n            ..add(CounterEvent.increment);\n        },\n        skip: 1,\n        expect: () => const <int>[2],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [11] when CounterEvent.increment is added and emitted 10',\n        build: () => CounterBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[11],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [11] when CounterEvent.increment is added and seed 10',\n        build: () => CounterBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[11],\n      );\n\n      blocTest<CounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added and expect is async',\n        build: () => CounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () async => <int>[1],\n      );\n\n      test('fails immediately when expectation is incorrect', () async {\n        const expectedError = 'Expected: [2]\\n'\n            '  Actual: [1]\\n'\n            '   Which: at location [0] is <1> instead of <2>\\n'\n            '\\n'\n            '==== diff ========================================\\n'\n            '\\n'\n            '''\\x1B[90m[\\x1B[0m\\x1B[31m[-2-]\\x1B[0m\\x1B[32m{+1+}\\x1B[0m\\x1B[90m]\\x1B[0m\\n'''\n            '\\n'\n            '==== end diff ====================================\\n';\n        late Object actualError;\n        final completer = Completer<void>();\n        await runZonedGuarded(() async {\n          unawaited(\n            testBloc<CounterBloc, int>(\n              build: () => CounterBloc(),\n              act: (bloc) => bloc.add(CounterEvent.increment),\n              expect: () => const <int>[2],\n            ).then((_) => completer.complete()),\n          );\n          await completer.future;\n        }, (Object error, _) {\n          actualError = error;\n          if (!completer.isCompleted) completer.complete();\n        });\n        expect((actualError as TestFailure).message, expectedError);\n      });\n\n      test(\n          'fails immediately when '\n          'uncaught exception occurs within bloc', () async {\n        late Object actualError;\n        final completer = Completer<void>();\n        await runZonedGuarded(() async {\n          unawaited(\n            testBloc<ErrorCounterBloc, int>(\n              build: () => ErrorCounterBloc(),\n              act: (bloc) => bloc.add(CounterEvent.increment),\n              expect: () => const <int>[1],\n            ).then((_) => completer.complete()),\n          );\n          await completer.future;\n        }, (Object error, _) {\n          actualError = error;\n          if (!completer.isCompleted) completer.complete();\n        });\n        expect(actualError, isA<ErrorCounterBlocError>());\n      });\n\n      test('fails immediately when exception occurs in act', () async {\n        final exception = Exception('oops');\n        late Object actualError;\n        final completer = Completer<void>();\n        await runZonedGuarded(() async {\n          unawaited(\n            testBloc<ErrorCounterBloc, int>(\n              build: () => ErrorCounterBloc(),\n              act: (_) => throw exception,\n              expect: () => const <int>[1],\n            ).then((_) => completer.complete()),\n          );\n          await completer.future;\n        }, (Object error, _) {\n          actualError = error;\n          if (!completer.isCompleted) completer.complete();\n        });\n        expect(actualError, exception);\n      });\n\n      test('future still completes when uncaught exception occurs', () async {\n        await expectLater(\n          () => testBloc<ErrorCounterBloc, int>(\n            build: () => ErrorCounterBloc(),\n            act: (bloc) => bloc.add(CounterEvent.increment),\n            expect: () => const <int>[1],\n          ),\n          throwsA(isA<ErrorCounterBlocError>()),\n        );\n      });\n    });\n\n    group('AsyncCounterBloc', () {\n      blocTest<AsyncCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => AsyncCounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<AsyncCounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added',\n        build: () => AsyncCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n      );\n\n      blocTest<AsyncCounterBloc, int>(\n        'emits [1, 2] when CounterEvent.increment is called multiple '\n        'times with async act',\n        build: () => AsyncCounterBloc(),\n        act: (bloc) async {\n          bloc.add(CounterEvent.increment);\n          await Future<void>.delayed(const Duration(milliseconds: 10));\n          bloc.add(CounterEvent.increment);\n        },\n        expect: () => const <int>[1, 2],\n      );\n\n      blocTest<AsyncCounterBloc, int>(\n        'emits [2] when CounterEvent.increment is added twice and skip: 1',\n        build: () => AsyncCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 1,\n        expect: () => const <int>[2],\n      );\n\n      blocTest<AsyncCounterBloc, int>(\n        'emits [11] when CounterEvent.increment is added and emitted 10',\n        build: () => AsyncCounterBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[11],\n      );\n    });\n\n    group('DebounceCounterBloc', () {\n      blocTest<DebounceCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => DebounceCounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<DebounceCounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added',\n        build: () => DebounceCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        wait: const Duration(milliseconds: 300),\n        expect: () => const <int>[1],\n      );\n\n      blocTest<DebounceCounterBloc, int>(\n        'emits [2] when CounterEvent.increment '\n        'is added twice and skip: 1',\n        build: () => DebounceCounterBloc(),\n        act: (bloc) async {\n          bloc.add(CounterEvent.increment);\n          await Future<void>.delayed(const Duration(milliseconds: 305));\n          bloc.add(CounterEvent.increment);\n        },\n        skip: 1,\n        wait: const Duration(milliseconds: 300),\n        expect: () => const <int>[2],\n      );\n\n      blocTest<DebounceCounterBloc, int>(\n        'emits [11] when CounterEvent.increment is added and emitted 10',\n        build: () => DebounceCounterBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        wait: const Duration(milliseconds: 300),\n        expect: () => const <int>[11],\n      );\n    });\n\n    group('InstantEmitBloc', () {\n      blocTest<InstantEmitBloc, int>(\n        'emits [1] when nothing is added',\n        build: () => InstantEmitBloc(),\n        expect: () => const <int>[1],\n      );\n\n      blocTest<InstantEmitBloc, int>(\n        'emits [1, 2] when CounterEvent.increment is added',\n        build: () => InstantEmitBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n      );\n\n      blocTest<InstantEmitBloc, int>(\n        'emits [1, 2, 3] when CounterEvent.increment is called '\n        'multiple times with async act',\n        build: () => InstantEmitBloc(),\n        act: (bloc) async {\n          bloc.add(CounterEvent.increment);\n          await Future<void>.delayed(const Duration(milliseconds: 10));\n          bloc.add(CounterEvent.increment);\n        },\n        expect: () => const <int>[1, 2, 3],\n      );\n\n      blocTest<InstantEmitBloc, int>(\n        'emits [3] when CounterEvent.increment is added twice and skip: 2',\n        build: () => InstantEmitBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 2,\n        expect: () => const <int>[3],\n      );\n\n      blocTest<InstantEmitBloc, int>(\n        'emits [11, 12] when CounterEvent.increment is added and seeded 10',\n        build: () => InstantEmitBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[11, 12],\n      );\n    });\n\n    group('MultiCounterBloc', () {\n      blocTest<MultiCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => MultiCounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<MultiCounterBloc, int>(\n        'emits [1, 2] when CounterEvent.increment is added',\n        build: () => MultiCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n      );\n\n      blocTest<MultiCounterBloc, int>(\n        'emits [1, 2, 3, 4] when CounterEvent.increment is called '\n        'multiple times with async act',\n        build: () => MultiCounterBloc(),\n        act: (bloc) async {\n          bloc.add(CounterEvent.increment);\n          await Future<void>.delayed(const Duration(milliseconds: 10));\n          bloc.add(CounterEvent.increment);\n        },\n        expect: () => const <int>[1, 2, 3, 4],\n      );\n\n      blocTest<MultiCounterBloc, int>(\n        'emits [4] when CounterEvent.increment is added twice and skip: 3',\n        build: () => MultiCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 3,\n        expect: () => const <int>[4],\n      );\n\n      blocTest<MultiCounterBloc, int>(\n        'emits [11, 12] when CounterEvent.increment is added and emitted 10',\n        build: () => MultiCounterBloc(),\n        seed: () => 10,\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[11, 12],\n      );\n    });\n\n    group('ComplexBloc', () {\n      blocTest<ComplexBloc, ComplexState>(\n        'emits [] when nothing is added',\n        build: () => ComplexBloc(),\n        expect: () => const <ComplexState>[],\n      );\n\n      blocTest<ComplexBloc, ComplexState>(\n        'emits [ComplexStateB] when ComplexEventB is added',\n        build: () => ComplexBloc(),\n        act: (bloc) => bloc.add(ComplexEventB()),\n        expect: () => [isA<ComplexStateB>()],\n      );\n\n      blocTest<ComplexBloc, ComplexState>(\n        'emits [ComplexStateA] when [ComplexEventB, ComplexEventA] '\n        'is added and skip: 1',\n        build: () => ComplexBloc(),\n        act: (bloc) => bloc\n          ..add(ComplexEventB())\n          ..add(ComplexEventA()),\n        skip: 1,\n        expect: () => [isA<ComplexStateA>()],\n      );\n    });\n    group('ErrorCounterBloc', () {\n      blocTest<ErrorCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => ErrorCounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'emits [2] when increment is added twice and skip: 1',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 1,\n        expect: () => const <int>[2],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'emits [1] when increment is added',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'throws ErrorCounterBlocException when increment is added',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        errors: () => [isA<ErrorCounterBlocError>()],\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'emits [1] and throws ErrorCounterBlocError '\n        'when increment is added',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n        errors: () => [isA<ErrorCounterBlocError>()],\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'emits [1, 2] when increment is added twice',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'throws two ErrorCounterBlocErrors '\n        'when increment is added twice',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        errors: () => [\n          isA<ErrorCounterBlocError>(),\n          isA<ErrorCounterBlocError>(),\n        ],\n      );\n\n      blocTest<ErrorCounterBloc, int>(\n        'emits [1, 2] and throws two ErrorCounterBlocErrors '\n        'when increment is added twice',\n        build: () => ErrorCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n        errors: () => [\n          isA<ErrorCounterBlocError>(),\n          isA<ErrorCounterBlocError>(),\n        ],\n      );\n    });\n\n    group('ExceptionCounterBloc', () {\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => ExceptionCounterBloc(),\n        expect: () => const <int>[],\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [2] when increment is added twice and skip: 1',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 1,\n        expect: () => const <int>[2],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [1] when increment is added',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'throws ExceptionCounterBlocException when increment is added',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        errors: () => [isA<ExceptionCounterBlocException>()],\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [1] and throws ExceptionCounterBlocException '\n        'when increment is added',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n        errors: () => [isA<ExceptionCounterBlocException>()],\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [1, 2] when increment is added twice',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n        errors: () => isNotEmpty,\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'throws two ExceptionCounterBlocExceptions '\n        'when increment is added twice',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        errors: () => [\n          isA<ExceptionCounterBlocException>(),\n          isA<ExceptionCounterBlocException>(),\n        ],\n      );\n\n      blocTest<ExceptionCounterBloc, int>(\n        'emits [1, 2] and throws two ExceptionCounterBlocException '\n        'when increment is added twice',\n        build: () => ExceptionCounterBloc(),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        expect: () => const <int>[1, 2],\n        errors: () => [\n          isA<ExceptionCounterBlocException>(),\n          isA<ExceptionCounterBlocException>(),\n        ],\n      );\n    });\n\n    group('SideEffectCounterBloc', () {\n      late Repository repository;\n\n      setUp(() {\n        repository = MockRepository();\n        when(() => repository.sideEffect()).thenReturn(null);\n      });\n\n      blocTest<SideEffectCounterBloc, int>(\n        'emits [] when nothing is added',\n        build: () => SideEffectCounterBloc(repository),\n        expect: () => const <int>[],\n      );\n\n      blocTest<SideEffectCounterBloc, int>(\n        'emits [1] when CounterEvent.increment is added',\n        build: () => SideEffectCounterBloc(repository),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[1],\n        verify: (_) {\n          verify(() => repository.sideEffect()).called(1);\n        },\n      );\n\n      blocTest<SideEffectCounterBloc, int>(\n        'emits [2] when CounterEvent.increment '\n        'is added twice and skip: 1',\n        build: () => SideEffectCounterBloc(repository),\n        act: (bloc) => bloc\n          ..add(CounterEvent.increment)\n          ..add(CounterEvent.increment),\n        skip: 1,\n        expect: () => const <int>[2],\n      );\n\n      blocTest<SideEffectCounterBloc, int>(\n        'does not require an expect',\n        build: () => SideEffectCounterBloc(repository),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        verify: (_) {\n          verify(() => repository.sideEffect()).called(1);\n        },\n      );\n\n      blocTest<SideEffectCounterBloc, int>(\n        'async verify',\n        build: () => SideEffectCounterBloc(repository),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        verify: (_) async {\n          await Future<void>.delayed(Duration.zero);\n          verify(() => repository.sideEffect()).called(1);\n        },\n      );\n\n      blocTest<SideEffectCounterBloc, int>(\n        'setUp is executed before build/act',\n        setUp: () {\n          when(() => repository.sideEffect()).thenThrow(Exception());\n        },\n        build: () => SideEffectCounterBloc(repository),\n        act: (bloc) => bloc.add(CounterEvent.increment),\n        expect: () => const <int>[],\n        errors: () => [isException],\n      );\n\n      test('fails immediately when verify is incorrect', () async {\n        const expectedError =\n            '''Expected: <2>\\n  Actual: <1>\\nUnexpected number of calls\\n''';\n        late Object actualError;\n        final completer = Completer<void>();\n        await runZonedGuarded(() async {\n          unawaited(\n            testBloc<SideEffectCounterBloc, int>(\n              build: () => SideEffectCounterBloc(repository),\n              act: (bloc) => bloc.add(CounterEvent.increment),\n              verify: (_) {\n                verify(() => repository.sideEffect()).called(2);\n              },\n            ).then((_) => completer.complete()),\n          );\n          await completer.future;\n        }, (Object error, _) {\n          actualError = error;\n          if (!completer.isCompleted) completer.complete();\n        });\n        expect((actualError as TestFailure).message, expectedError);\n      });\n\n      test('shows equality warning when strings are identical', () async {\n        const expectedError = '''\nExpected: [Instance of 'ComplexStateA']\n  Actual: [Instance of 'ComplexStateA']\n   Which: at location [0] is <Instance of 'ComplexStateA'> instead of <Instance of 'ComplexStateA'>\\n\nWARNING: Please ensure state instances extend Equatable, override == and hashCode, or implement Comparable.\nAlternatively, consider using Matchers in the expect of the blocTest rather than concrete state instances.\\n''';\n        late Object actualError;\n        final completer = Completer<void>();\n        await runZonedGuarded(() async {\n          unawaited(\n            testBloc<ComplexBloc, ComplexState>(\n              build: () => ComplexBloc(),\n              act: (bloc) => bloc.add(ComplexEventA()),\n              expect: () => <ComplexState>[ComplexStateA()],\n            ).then((_) => completer.complete()),\n          );\n          await completer.future;\n        }, (Object error, _) {\n          actualError = error;\n          if (!completer.isCompleted) completer.complete();\n        });\n        expect((actualError as TestFailure).message, expectedError);\n      });\n    });\n  });\n\n  group('tearDown', () {\n    late int tearDownCallCount;\n    int? state;\n\n    setUp(() {\n      tearDownCallCount = 0;\n    });\n\n    tearDown(() {\n      expect(tearDownCallCount, equals(1));\n    });\n\n    blocTest<CounterBloc, int>(\n      'is called after the test is run',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      verify: (bloc) {\n        state = bloc.state;\n      },\n      tearDown: () {\n        tearDownCallCount++;\n        expect(state, equals(1));\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_test/test/bloc_observer_test.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'blocs/counter_bloc.dart';\nimport 'blocs/exception_counter_bloc.dart';\n\nclass _MockBlocObserver extends Mock implements BlocObserver {}\n\nvoid main() {\n  group('BlocObserver', () {\n    late BlocObserver blocObserver;\n\n    setUp(() {\n      blocObserver = _MockBlocObserver();\n      final previousObserver = Bloc.observer;\n      addTearDown(() => Bloc.observer = previousObserver);\n      Bloc.observer = blocObserver;\n    });\n\n    blocTest<CounterBloc, int>(\n      'calls onCreate',\n      build: () => CounterBloc(),\n      verify: (bloc) {\n        // ignore: invalid_use_of_protected_member\n        verify(() => blocObserver.onCreate(bloc)).called(1);\n      },\n    );\n\n    blocTest<CounterBloc, int>(\n      'calls onEvent',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      verify: (bloc) {\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => blocObserver.onEvent(bloc, CounterEvent.increment),\n        ).called(1);\n      },\n    );\n\n    blocTest<CounterBloc, int>(\n      'calls onChange',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      verify: (bloc) {\n        const change = Change<int>(currentState: 0, nextState: 1);\n        // ignore: invalid_use_of_protected_member\n        verify(() => blocObserver.onChange(bloc, change)).called(1);\n      },\n    );\n\n    blocTest<CounterBloc, int>(\n      'calls onTransition',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      verify: (bloc) {\n        const transition = Transition<CounterEvent, int>(\n          event: CounterEvent.increment,\n          currentState: 0,\n          nextState: 1,\n        );\n        // ignore: invalid_use_of_protected_member\n        verify(() => blocObserver.onTransition(bloc, transition)).called(1);\n      },\n    );\n\n    blocTest<ExceptionCounterBloc, int>(\n      'calls onError',\n      build: () => ExceptionCounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      setUp: () {\n        registerFallbackValue(StackTrace.empty);\n      },\n      verify: (bloc) {\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => blocObserver.onError(\n            bloc,\n            ExceptionCounterBlocException(),\n            any(),\n          ),\n        ).called(1);\n      },\n      errors: () => containsOnce(isA<ExceptionCounterBlocException>()),\n    );\n\n    blocTest<CounterBloc, int>(\n      'calls onDone',\n      build: () => CounterBloc(),\n      act: (bloc) => bloc.add(CounterEvent.increment),\n      verify: (bloc) {\n        verify(\n          // ignore: invalid_use_of_protected_member\n          () => blocObserver.onDone(bloc, CounterEvent.increment),\n        ).called(1);\n      },\n    );\n\n    blocTest<CounterBloc, int>(\n      'calls onClose',\n      build: () => CounterBloc(),\n      verify: (bloc) {\n        // ignore: invalid_use_of_protected_member\n        verify(() => blocObserver.onClose(bloc)).called(1);\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/async_counter_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass AsyncCounterBloc extends Bloc<CounterEvent, int> {\n  AsyncCounterBloc() : super(0) {\n    on<CounterEvent>(\n      (event, emit) async {\n        switch (event) {\n          case CounterEvent.increment:\n            await Future<void>.delayed(const Duration(microseconds: 1));\n            return emit(state + 1);\n        }\n      },\n      transformer: (events, mapper) => events.asyncExpand(mapper),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/blocs.dart",
    "content": "export 'async_counter_bloc.dart';\nexport 'complex_bloc.dart';\nexport 'counter_bloc.dart';\nexport 'debounce_counter_bloc.dart';\nexport 'error_counter_bloc.dart';\nexport 'exception_counter_bloc.dart';\nexport 'instant_emit_bloc.dart';\nexport 'multi_counter_bloc.dart';\nexport 'side_effect_counter_bloc.dart';\nexport 'sum_bloc.dart';\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/complex_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nabstract class ComplexEvent {}\n\nclass ComplexEventA extends ComplexEvent {}\n\nclass ComplexEventB extends ComplexEvent {}\n\nabstract class ComplexState {}\n\nclass ComplexStateA extends ComplexState {}\n\nclass ComplexStateB extends ComplexState {}\n\nclass ComplexBloc extends Bloc<ComplexEvent, ComplexState> {\n  ComplexBloc() : super(ComplexStateA()) {\n    on<ComplexEventA>((event, emit) => emit(ComplexStateA()));\n    on<ComplexEventB>((event, emit) => emit(ComplexStateB()));\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nenum CounterEvent { increment }\n\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          return emit(state + 1);\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/debounce_counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:rxdart/rxdart.dart';\n\nimport 'blocs.dart';\n\nEventTransformer<E> debounce<E>() {\n  return (events, mapper) {\n    return events\n        .debounceTime(const Duration(milliseconds: 300))\n        .switchMap(mapper);\n  };\n}\n\nclass DebounceCounterBloc extends Bloc<CounterEvent, int> {\n  DebounceCounterBloc() : super(0) {\n    on<CounterEvent>(\n      (event, emit) {\n        switch (event) {\n          case CounterEvent.increment:\n            return emit(state + 1);\n        }\n      },\n      transformer: debounce(),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/error_counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass ErrorCounterBlocError extends Error {}\n\nclass ErrorCounterBloc extends Bloc<CounterEvent, int> {\n  ErrorCounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          emit(state + 1);\n          throw ErrorCounterBlocError();\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/exception_counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass ExceptionCounterBlocException implements Exception {}\n\nclass ExceptionCounterBloc extends Bloc<CounterEvent, int> {\n  ExceptionCounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          emit(state + 1);\n          throw ExceptionCounterBlocException();\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/instant_emit_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass InstantEmitBloc extends Bloc<CounterEvent, int> {\n  InstantEmitBloc() : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          return emit(state + 1);\n      }\n    });\n    add(CounterEvent.increment);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/multi_counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass MultiCounterBloc extends Bloc<CounterEvent, int> {\n  MultiCounterBloc() : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          emit(state + 1);\n          emit(state + 1);\n          break;\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/side_effect_counter_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nimport 'blocs.dart';\n\nclass Repository {\n  void sideEffect() {}\n}\n\nclass SideEffectCounterBloc extends Bloc<CounterEvent, int> {\n  SideEffectCounterBloc(this._repository) : super(0) {\n    on<CounterEvent>((event, emit) {\n      switch (event) {\n        case CounterEvent.increment:\n          _repository.sideEffect();\n          return emit(state + 1);\n      }\n    });\n  }\n\n  final Repository _repository;\n}\n"
  },
  {
    "path": "packages/bloc_test/test/blocs/sum_bloc.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nimport 'counter_bloc.dart';\n\nclass SumEvent {\n  const SumEvent(this.value);\n\n  final int value;\n}\n\nclass SumBloc extends Bloc<SumEvent, int> {\n  SumBloc(CounterBloc counterBloc) : super(0) {\n    on<SumEvent>((event, emit) => emit(state + event.value));\n\n    _countSubscription = counterBloc.stream.listen(\n      (count) => add(SumEvent(count)),\n    );\n  }\n\n  late StreamSubscription<int> _countSubscription;\n\n  @override\n  Future<void> close() {\n    _countSubscription.cancel();\n    return super.close();\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubit_bloc_test_test.dart",
    "content": "import 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nimport 'cubits/cubits.dart';\n\nclass MockRepository extends Mock implements Repository {}\n\nvoid main() {\n  group('blocTest', () {\n    group('CounterCubit', () {\n      blocTest<CounterCubit, int>(\n        'emits [] when nothing is called',\n        build: () => CounterCubit(),\n        expect: () => <int>[],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [1] when increment is called',\n        build: () => CounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[1],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [1] when increment is called with async act',\n        build: () => CounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[1],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [1, 2] when increment is called multiple times '\n        'with async act',\n        build: () => CounterCubit(),\n        act: (cubit) => cubit\n          ..increment()\n          ..increment(),\n        expect: () => <int>[1, 2],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [3] when increment is called and seed is 2 '\n        'with async act',\n        build: () => CounterCubit(),\n        seed: () => 2,\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[3],\n      );\n\n      blocTest<CounterCubit, int>(\n        'emits [1] when increment is called and expect is async',\n        build: () => CounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () async => <int>[1],\n      );\n    });\n\n    group('AsyncCounterCubit', () {\n      blocTest<AsyncCounterCubit, int>(\n        'emits [] when nothing is called',\n        build: () => AsyncCounterCubit(),\n        expect: () => <int>[],\n      );\n\n      blocTest<AsyncCounterCubit, int>(\n        'emits [1] when increment is called',\n        build: () => AsyncCounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[1],\n      );\n\n      blocTest<AsyncCounterCubit, int>(\n        'emits [1, 2] when increment is called multiple '\n        'times with async act',\n        build: () => AsyncCounterCubit(),\n        act: (cubit) async {\n          await cubit.increment();\n          await cubit.increment();\n        },\n        expect: () => <int>[1, 2],\n      );\n    });\n\n    group('DelayedCounterCubit', () {\n      blocTest<DelayedCounterCubit, int>(\n        'emits [] when nothing is called',\n        build: () => DelayedCounterCubit(),\n        expect: () => <int>[],\n      );\n\n      blocTest<DelayedCounterCubit, int>(\n        'emits [] when increment is called without wait',\n        build: () => DelayedCounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[],\n      );\n\n      blocTest<DelayedCounterCubit, int>(\n        'emits [1] when increment is called with wait',\n        build: () => DelayedCounterCubit(),\n        act: (cubit) => cubit.increment(),\n        wait: const Duration(milliseconds: 300),\n        expect: () => <int>[1],\n      );\n    });\n\n    group('InstantEmitCubit', () {\n      blocTest<InstantEmitCubit, int>(\n        'emits [] when nothing is called',\n        build: () => InstantEmitCubit(),\n        expect: () => <int>[],\n      );\n\n      blocTest<InstantEmitCubit, int>(\n        'emits [2] when increment is called',\n        build: () => InstantEmitCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[2],\n      );\n\n      blocTest<InstantEmitCubit, int>(\n        'emits [2, 3] when increment is called '\n        'multiple times with async act',\n        build: () => InstantEmitCubit(),\n        act: (cubit) => cubit\n          ..increment()\n          ..increment(),\n        expect: () => <int>[2, 3],\n      );\n    });\n\n    group('MultiCounterCubit', () {\n      blocTest<MultiCounterCubit, int>(\n        'emits [] when nothing is called',\n        build: () => MultiCounterCubit(),\n        expect: () => <int>[],\n      );\n\n      blocTest<MultiCounterCubit, int>(\n        'emits [1, 2] when increment is called',\n        build: () => MultiCounterCubit(),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[1, 2],\n      );\n\n      blocTest<MultiCounterCubit, int>(\n        'emits [1, 2, 3, 4] when increment is called '\n        'multiple times with async act',\n        build: () => MultiCounterCubit(),\n        act: (cubit) => cubit\n          ..increment()\n          ..increment(),\n        expect: () => <int>[1, 2, 3, 4],\n      );\n    });\n\n    group('ComplexCubit', () {\n      blocTest<ComplexCubit, ComplexState>(\n        'emits [] when nothing is called',\n        build: () => ComplexCubit(),\n        expect: () => <Matcher>[],\n      );\n\n      blocTest<ComplexCubit, ComplexState>(\n        'emits [ComplexStateB] when emitB is called',\n        build: () => ComplexCubit(),\n        act: (cubit) => cubit.emitB(),\n        expect: () => [isA<ComplexStateB>()],\n      );\n    });\n\n    group('SideEffectCounterCubit', () {\n      late Repository repository;\n\n      setUp(() {\n        repository = MockRepository();\n        when(() => repository.sideEffect()).thenReturn(null);\n      });\n\n      blocTest<SideEffectCounterCubit, int>(\n        'emits [] when nothing is called',\n        build: () => SideEffectCounterCubit(repository),\n        expect: () => <int>[],\n      );\n\n      blocTest<SideEffectCounterCubit, int>(\n        'emits [1] when increment is called',\n        build: () => SideEffectCounterCubit(repository),\n        act: (cubit) => cubit.increment(),\n        expect: () => <int>[1],\n        verify: (_) async {\n          verify(() => repository.sideEffect()).called(1);\n        },\n      );\n\n      blocTest<SideEffectCounterCubit, int>(\n        'does not require an expect',\n        build: () => SideEffectCounterCubit(repository),\n        act: (cubit) => cubit.increment(),\n        verify: (_) async {\n          verify(() => repository.sideEffect()).called(1);\n        },\n      );\n    });\n\n    group('ExceptionCubit', () {\n      final exception = Exception('oops');\n\n      blocTest<ExceptionCubit, int>(\n        'errors supports matchers',\n        build: () => ExceptionCubit(),\n        act: (cubit) => cubit.throwException(exception),\n        errors: () => contains(exception),\n      );\n\n      blocTest<ExceptionCubit, int>(\n        'captures uncaught exceptions',\n        build: () => ExceptionCubit(),\n        act: (cubit) => cubit.throwException(exception),\n        errors: () => <Matcher>[equals(exception)],\n      );\n\n      blocTest<ExceptionCubit, int>(\n        'captures calls to addError',\n        build: () => ExceptionCubit(),\n        // ignore: invalid_use_of_protected_member\n        act: (cubit) => cubit.addError(exception),\n        errors: () => <Matcher>[equals(exception)],\n      );\n    });\n\n    group('ErrorCubit', () {\n      final error = Error();\n\n      blocTest<ErrorCubit, int>(\n        'errors supports matchers',\n        build: () => ErrorCubit(),\n        act: (cubit) => cubit.throwError(error),\n        errors: () => contains(error),\n      );\n\n      blocTest<ErrorCubit, int>(\n        'captures uncaught errors',\n        build: () => ErrorCubit(),\n        act: (cubit) => cubit.throwError(error),\n        errors: () => <Matcher>[equals(error)],\n      );\n\n      blocTest<ErrorCubit, int>(\n        'captures calls to addError',\n        build: () => ErrorCubit(),\n        // ignore: invalid_use_of_protected_member\n        act: (cubit) => cubit.addError(error),\n        errors: () => <Matcher>[equals(error)],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/async_counter_cubit.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nclass AsyncCounterCubit extends Cubit<int> {\n  AsyncCounterCubit() : super(0);\n\n  Future<void> increment() async {\n    await Future<void>.delayed(const Duration(microseconds: 1));\n    emit(state + 1);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/complex_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nabstract class ComplexState {}\n\nclass ComplexStateA extends ComplexState {}\n\nclass ComplexStateB extends ComplexState {}\n\nclass ComplexCubit extends Cubit<ComplexState> {\n  ComplexCubit() : super(ComplexStateA());\n\n  void emitA() => emit(ComplexStateA());\n  void emitB() => emit(ComplexStateB());\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/cubits.dart",
    "content": "export 'async_counter_cubit.dart';\nexport 'complex_cubit.dart';\nexport 'counter_cubit.dart';\nexport 'delayed_counter_cubit.dart';\nexport 'error_cubit.dart';\nexport 'exception_cubit.dart';\nexport 'instant_emit_cubit.dart';\nexport 'multi_counter_cubit.dart';\nexport 'side_effect_counter_cubit.dart';\nexport 'sum_cubit.dart';\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/delayed_counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass DelayedCounterCubit extends Cubit<int> {\n  DelayedCounterCubit() : super(0);\n\n  void increment() {\n    Future<void>.delayed(\n      const Duration(milliseconds: 300),\n      () {\n        if (!isClosed) emit(state + 1);\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/error_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass ErrorCubit extends Cubit<int> {\n  ErrorCubit() : super(0);\n\n  void throwError(Error e) => throw e;\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/exception_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass ExceptionCubit extends Cubit<int> {\n  ExceptionCubit() : super(0);\n\n  void throwException(Exception e) => throw e;\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/instant_emit_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass InstantEmitCubit extends Cubit<int> {\n  InstantEmitCubit() : super(0) {\n    emit(1);\n  }\n\n  void increment() => emit(state + 1);\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/multi_counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass MultiCounterCubit extends Cubit<int> {\n  MultiCounterCubit() : super(0);\n\n  void increment() {\n    emit(state + 1);\n    emit(state + 1);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/side_effect_counter_cubit.dart",
    "content": "import 'package:bloc/bloc.dart';\n\nclass Repository {\n  void sideEffect() {}\n}\n\nclass SideEffectCounterCubit extends Cubit<int> {\n  SideEffectCounterCubit(this._repository) : super(0);\n\n  final Repository _repository;\n\n  void increment() {\n    _repository.sideEffect();\n    emit(state + 1);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/cubits/sum_cubit.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\n\nimport 'counter_cubit.dart';\n\nclass SumCubit extends Cubit<int> {\n  SumCubit(CounterCubit counterCubit) : super(0) {\n    _countSubscription = counterCubit.stream.listen(\n      (count) => emit(state + count),\n    );\n  }\n\n  late StreamSubscription<int> _countSubscription;\n\n  @override\n  Future<void> close() {\n    _countSubscription.cancel();\n    return super.close();\n  }\n}\n"
  },
  {
    "path": "packages/bloc_test/test/mock_bloc_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:mocktail/mocktail.dart';\n\nimport 'package:test/test.dart';\n\nimport 'blocs/blocs.dart';\nimport 'cubits/cubits.dart';\n\nclass MockCounterBloc extends MockBloc<CounterEvent, int>\n    implements CounterBloc {}\n\nclass MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n\nvoid main() {\n  group('MockBloc', () {\n    late CounterBloc counterBloc;\n\n    setUp(() {\n      counterBloc = MockCounterBloc();\n    });\n\n    test('is compatible with when', () {\n      when(() => counterBloc.state).thenReturn(10);\n      expect(counterBloc.state, 10);\n    });\n\n    test('is compatible with listen', () {\n      expect(\n        counterBloc.stream.listen((_) {}),\n        isA<StreamSubscription<dynamic>>(),\n      );\n    });\n\n    test('is compatible with add', () {\n      counterBloc.add(CounterEvent.increment);\n    });\n\n    test('is compatible with addError without StackTrace', () {\n      // ignore: invalid_use_of_protected_member\n      counterBloc.addError(Exception('oops'));\n    });\n\n    test('is compatible with addError with StackTrace', () {\n      // ignore: invalid_use_of_protected_member\n      counterBloc.addError(Exception('oops'), StackTrace.empty);\n    });\n\n    test('is compatible with onEvent', () {\n      // ignore: invalid_use_of_protected_member\n      counterBloc.onEvent(CounterEvent.increment);\n    });\n\n    test('is compatible with onError', () {\n      // ignore: invalid_use_of_protected_member\n      counterBloc.onError(Exception('oops'), StackTrace.empty);\n    });\n\n    test('is compatible with onTransition', () {\n      // ignore: invalid_use_of_protected_member\n      counterBloc.onTransition(\n        const Transition(\n          currentState: 0,\n          event: CounterEvent.increment,\n          nextState: 1,\n        ),\n      );\n    });\n\n    test('is compatible with close', () {\n      expect(counterBloc.close(), completes);\n    });\n\n    test('is automatically compatible with whenListen', () {\n      whenListen(\n        counterBloc,\n        Stream<int>.fromIterable([0, 1, 2, 3]),\n      );\n      expectLater(\n        counterBloc.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n    });\n  });\n\n  group('MockCubit', () {\n    late CounterCubit counterCubit;\n\n    setUp(() {\n      counterCubit = MockCounterCubit();\n    });\n\n    test('is compatible with when', () {\n      when(() => counterCubit.state).thenReturn(10);\n      expect(counterCubit.state, 10);\n    });\n\n    test('is automatically compatible with whenListen', () {\n      whenListen(\n        counterCubit,\n        Stream<int>.fromIterable([0, 1, 2, 3]),\n      );\n      expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_test/test/when_listen_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:bloc_test/bloc_test.dart';\nimport 'package:test/test.dart';\n\nimport 'cubits/cubits.dart';\n\nclass MockCounterCubit extends MockCubit<int> implements CounterCubit {}\n\nvoid unawaited(Future<void>? _) {}\n\nvoid main() {\n  group('whenListen', () {\n    test('can mock the stream of a single cubit with an empty Stream', () {\n      final counterCubit = MockCounterCubit();\n      whenListen(counterCubit, const Stream<int>.empty());\n      expectLater(counterCubit.stream, emitsInOrder(<int>[]));\n    });\n\n    test('can mock the stream of a single cubit', () async {\n      final counterCubit = MockCounterCubit();\n      whenListen(\n        counterCubit,\n        Stream.fromIterable([0, 1, 2, 3]),\n      );\n      await expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n    });\n\n    test('can mock the stream of a single cubit with delays', () async {\n      final counterCubit = MockCounterCubit();\n      final controller = StreamController<int>();\n      whenListen(counterCubit, controller.stream);\n      unawaited(\n        expectLater(\n          counterCubit.stream,\n          emitsInOrder(\n            <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n          ),\n        ),\n      );\n      controller.add(0);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(1);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(2);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(3);\n      await controller.close();\n    });\n\n    test('can mock the state of a single cubit with delays', () async {\n      final counterCubit = MockCounterCubit();\n      final controller = StreamController<int>();\n      whenListen(counterCubit, controller.stream);\n      unawaited(\n        expectLater(\n          counterCubit.stream,\n          emitsInOrder(\n            <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n          ),\n        ).then((dynamic _) {\n          expect(counterCubit.state, equals(3));\n        }),\n      );\n      controller.add(0);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(1);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(2);\n      await Future<void>.delayed(Duration.zero);\n      controller.add(3);\n      await controller.close();\n    });\n\n    test('can mock the state of a single cubit', () async {\n      final counterCubit = MockCounterCubit();\n      whenListen(\n        counterCubit,\n        Stream.fromIterable([0, 1, 2, 3]),\n      );\n      await expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n      expect(counterCubit.state, equals(3));\n    });\n\n    test('can mock the initial state of a single cubit', () async {\n      final counterCubit = MockCounterCubit();\n      whenListen(\n        counterCubit,\n        Stream.fromIterable([0, 1, 2, 3]),\n        initialState: 0,\n      );\n      expect(counterCubit.state, equals(0));\n      await expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n      expect(counterCubit.state, equals(3));\n    });\n\n    test('can mock the stream of a single cubit as broadcast stream', () {\n      final counterCubit = MockCounterCubit();\n      whenListen(\n        counterCubit,\n        Stream.fromIterable([0, 1, 2, 3]),\n      );\n      expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n      expectLater(\n        counterCubit.stream,\n        emitsInOrder(\n          <Matcher>[equals(0), equals(1), equals(2), equals(3), emitsDone],\n        ),\n      );\n    });\n\n    test(\n        'can mock the stream of a cubit dependency '\n        '(with initial state)', () async {\n      final controller = StreamController<int>();\n      final counterCubit = MockCounterCubit();\n      whenListen(counterCubit, controller.stream);\n      final sumCubit = SumCubit(counterCubit);\n      unawaited(expectLater(sumCubit.stream, emitsInOrder(<int>[0, 1, 3, 6])));\n      controller\n        ..add(0)\n        ..add(1)\n        ..add(2)\n        ..add(3);\n      await controller.close();\n      expect(sumCubit.state, equals(6));\n    });\n\n    test('can mock the stream of a cubit dependency', () async {\n      final controller = StreamController<int>();\n      final counterCubit = MockCounterCubit();\n      whenListen(counterCubit, controller.stream);\n      final sumCubit = SumCubit(counterCubit);\n      unawaited(expectLater(sumCubit.stream, emitsInOrder(<int>[1, 3, 6])));\n      controller\n        ..add(1)\n        ..add(2)\n        ..add(3);\n      await controller.close();\n      expect(sumCubit.state, equals(6));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_tools/.gitignore",
    "content": "# Files and directories created by pub\n.dart_tool/\n.packages\npubspec.lock\n\n# Conventional directory for build outputs\nbuild/\n\n# Directory created by dartdoc\ndoc/api/\n\n# Temporary Files\n.tmp/\n\n# Files generated during tests\n.test_coverage.dart\ncoverage/"
  },
  {
    "path": "packages/bloc_tools/CHANGELOG.md",
    "content": "# 0.1.0-dev.23\n\n- deps: bump `pkg:bloc_lint` to `^0.4.0`\n\n# 0.1.0-dev.22\n\n- fix: version config mismatch\n\n# 0.1.0-dev.21\n\n- deps: bump `pkg:bloc_lint` to `^0.3.7`\n\n# 0.1.0-dev.20\n\n- deps: bump `pkg:bloc_lint` to `^0.3.3`\n- docs: add `bloc_lint` badge to `README`\n- refactor: analysis options updates\n\n# 0.1.0-dev.19\n\n- deps: bump `pkg:bloc_lint` to `^0.3.2`\n\n# 0.1.0-dev.18\n\n- fix: migrate to `pkg:lsp_server_ce`\n- deps: bump `pkg:bloc_lint` to `^0.3.0`\n\n# 0.1.0-dev.17\n\n- deps: bump `pkg:bloc_lint` to `^0.2.1`\n- docs: various `README.md` improvements\n\n# 0.1.0-dev.16\n\n- deps: bump `pkg:bloc_lint` to `^0.2.0`\n\n# 0.1.0-dev.15\n\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.6`\n\n# 0.1.0-dev.14\n\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.5`\n\n# 0.1.0-dev.13\n\n- fix: various bug fixes for Windows\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.4`\n\n# 0.1.0-dev.12\n\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.3`\n\n# 0.1.0-dev.11\n\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.2`\n\n# 0.1.0-dev.10\n\n- fix: language server diagnostic uri resolution on windows\n- deps: bump `pkg:bloc_lint` to `^0.2.0-dev.1`\n\n# 0.1.0-dev.9\n\n- feat: add `bloc lint` command\n\n  ```sh\n  $ bloc lint --help\n  Lint Dart source code.\n\n  Usage: bloc lint [arguments]\n  -h, --help    Print this usage information.\n\n  Run \"bloc help\" to see global options.\n  ```\n\n# 0.1.0-dev.8\n\n- feat: add `bloc new <template>` command\n\n  ```sh\n  $ bloc new --help\n  Generate new bloc components.\n\n  Usage: bloc new <subcommand> [arguments]\n  -h, --help    Print this usage information.\n\n  Available subcommands:\n    bloc             Generate a new Bloc in Dart. Built for the bloc state management library.\n    cubit            Generate a new Cubit in Dart. Built for the bloc state management library.\n    hydrated_bloc    Generate a new HydratedBloc in Dart. Built for the bloc state management library.\n    hydrated_cubit   Generate a new HydratedCubit in Dart. Built for the bloc state management library.\n    replay_bloc      Generate a new ReplayBloc in Dart. Built for the bloc state management library.\n    replay_cubit     Generate a new ReplayCubit in Dart. Built for the bloc state management library.\n\n  Run \"bloc help\" to see global options.\n  ```\n\n- chore: bump minimum Dart SDK version to 3.7.0\n\n# 0.1.0-dev.7\n\n- fix: version constant\n- chore(deps): upgrade to `pub_updater ^0.5.0`\n\n# 0.1.0-dev.6\n\n- chore: add screenshot to `pubspec.yaml`\n- chore(deps): upgrade to `mocktail ^1.0.0`\n- chore: add `topics` to `pubspec.yaml`\n- chore: update copyright year\n- chore: update logos\n\n# 0.1.0-dev.5\n\n- refactor: standardize analysis options\n- deps: remove `package:universal_io`\n- deps: upgrade to `pub_updater ^0.3.0`\n- deps: upgrade to `mason ^0.1.0-dev.33`\n\n# 0.1.0-dev.4\n\n- feat: improve update prompt style\n\n# 0.1.0-dev.3\n\n- fix: upgrade to `pub_updater ^0.2.1`\n\n# 0.1.0-dev.2\n\n- feat: automatic update support\n- docs: minor README improvements\n\n# 0.1.0-dev.1\n\n- feat: initial development release\n  - includes `help` command for usage\n  - includes `--version` flag\n"
  },
  {
    "path": "packages/bloc_tools/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/bloc_tools/README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/bloc_tools.png\" height=\"100\" alt=\"Bloc Tools\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/bloc_tools\"><img src=\"https://img.shields.io/pub/v/bloc_tools.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nTools for building applications using the bloc state management library.\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Installing\n\n```sh\ndart pub global activate bloc_tools\n```\n\n## Commands\n\n### `$ bloc lint [files...]`\n\nAnalyze Dart source code using the official bloc linter to improve code quality and enforce consistency.\n\n**Usage**\n\n```sh\n# Lint the current directory\nbloc lint .\n\n# Lint multiple files\nbloc lint ./path/to/bloc.dart ./path/to/cubit.dart\n```\n\nCheck out the [official documentation](https://bloclibrary.dev/lint/) for information.\n\n### `$ bloc new [component]`\n\nCreate new bloc/cubit components from various templates.\n\n**Usage**\n\n```sh\n# Create a CounterBloc\nbloc new bloc --name counter\n\n# Create a CounterCubit\nbloc new cubit --name counter\n```\n\n**Components**\n\n| Component      | Description                  |\n| -------------- | ---------------------------- |\n| bloc           | Generate a new Bloc          |\n| cubit          | Generate a new Cubit         |\n| hydrated_bloc  | Generate a new HydratedBloc  |\n| hydrated_cubit | Generate a new HydratedCubit |\n| replay_bloc    | Generate a new ReplayBloc    |\n| replay_cubit   | Generate a new ReplayCubit   |\n\n## Usage\n\n```sh\nCommand Line Tools for the Bloc Library.\n\nUsage: bloc <command> [arguments]\n\nGlobal options:\n-h, --help       Print this usage information.\n    --version    Print the current version.\n\nAvailable commands:\n  lint   bloc lint [arguments]\n         Lint Dart source code.\n  new    bloc new <subcommand> [arguments]\n         Generate new bloc components.\n\nRun \"bloc help <command>\" for more information about a command.\n```\n"
  },
  {
    "path": "packages/bloc_tools/analysis_options.yaml",
    "content": "include: ../../analysis_options.yaml\nanalyzer:\n  exclude:\n    - \"**/version.dart\"\n"
  },
  {
    "path": "packages/bloc_tools/bin/bloc.dart",
    "content": "import 'dart:io';\n\nimport 'package:bloc_tools/src/command_runner.dart';\n\nFuture<void> main(List<String> args) async {\n  await _flushThenExit(await BlocToolsCommandRunner().run(args));\n}\n\n/// Flushes the stdout and stderr streams, then exits the program with the given\n/// status code.\n///\n/// This returns a Future that will never complete, since the program will have\n/// exited already. This is useful to prevent Future chains from proceeding\n/// after you've decided to exit.\nFuture<void> _flushThenExit(int status) {\n  return Future.wait<void>([\n    stdout.close(),\n    stderr.close(),\n  ]).then<void>((_) => exit(status));\n}\n"
  },
  {
    "path": "packages/bloc_tools/dart_test.yaml",
    "content": "tags:\n  pull-request-only:\n    skip: \"Should only be run during pull request\"\n"
  },
  {
    "path": "packages/bloc_tools/e2e/main.dart",
    "content": "// ignore_for_file: avoid_print\nimport 'dart:io';\nimport 'package:path/path.dart' as p;\n\nFuture<void> main() async {\n  final projectRoot = flutterCreate();\n\n  installBloc(projectRoot);\n  installBlocLint(projectRoot);\n  createAnalysisOptions(projectRoot);\n  createCubit(projectRoot);\n\n  final actual = lint(projectRoot);\n  const expected = '''\nwarning[avoid_flutter_imports]: Avoid importing Flutter within cubit instances.\n --> lib/counter_cubit.dart:1\n  |\n  | import 'package:flutter/material.dart';\n  |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  = hint: Cubits should be decoupled from Flutter.\n docs: https://bloclibrary.dev/lint-rules/avoid_flutter_imports\n\n1 issue found\nAnalyzed 2 files\n''';\n  if (!actual.contains(expected)) {\n    throw Exception('''\nFAIL\n\nExpected ↓\n$expected\n\nActual ↓\n$actual\n''');\n  }\n  print('PASS');\n  Directory(projectRoot).deleteSync(recursive: true);\n}\n\nString flutterCreate() {\n  final tempDir = Directory.systemTemp.createTempSync();\n  const projectName = 'example';\n  exec('flutter', [\n    'create',\n    '-e',\n    projectName,\n  ], workingDirectory: tempDir.path);\n  return p.join(tempDir.path, projectName);\n}\n\nString exec(\n  String executable,\n  List<String> arguments, {\n  String? workingDirectory,\n  int? exitCode = 0,\n}) {\n  final result = Process.runSync(\n    executable,\n    arguments,\n    workingDirectory: workingDirectory,\n    runInShell: Platform.isWindows,\n  );\n  if (result.exitCode != exitCode) {\n    throw Exception('''\nError running \"$executable ${arguments.join(' ')}\"\nExited with code: ${result.exitCode}\nstdout: ${result.stdout}\nstderr: ${result.stderr}\n''');\n  }\n  return result.stdout as String;\n}\n\nvoid installBloc(String projectRoot) {\n  exec('flutter', ['pub', 'add', 'bloc'], workingDirectory: projectRoot);\n}\n\nvoid installBlocLint(String projectRoot) {\n  exec('flutter', [\n    'pub',\n    'add',\n    '--dev',\n    'bloc_lint:^0.2.0-dev.3',\n  ], workingDirectory: projectRoot);\n}\n\nvoid createAnalysisOptions(String projectRoot) {\n  File(p.join(projectRoot, 'analysis_options.yaml')).writeAsStringSync('''\ninclude: package:bloc_lint/recommended.yaml\n''');\n}\n\nvoid createCubit(String projectRoot) {\n  File(p.join(projectRoot, 'lib', 'counter_cubit.dart')).writeAsStringSync('''\nimport 'package:flutter/material.dart';\nimport 'package:bloc/bloc.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n}\n''');\n}\n\nString lint(String projectRoot) {\n  return exec(\n    'bloc',\n    ['lint', '.'],\n    workingDirectory: projectRoot,\n    exitCode: Platform.isWindows ? 0 : 1,\n  );\n}\n"
  },
  {
    "path": "packages/bloc_tools/e2e/pubspec.yaml",
    "content": "name: e2e\nversion: 0.1.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.7.0 <4.0.0\"\n\ndependencies:\n  path: ^1.0.0\n"
  },
  {
    "path": "packages/bloc_tools/example/README.md",
    "content": "# Activate the Bloc Command Line Tools\n\n```sh\ndart pub global activate bloc_tools\n```\n\n# See list of available commands\n\n```sh\nbloc --help\n```\n"
  },
  {
    "path": "packages/bloc_tools/lib/bloc_tools.dart",
    "content": "/// Tools for building applications using the\n/// [bloc state management library](https://pub.dev/packages/bloc).\n///\n/// ```sh\n/// # activate bloc_tools\n/// dart pub global activate bloc_tools\n///\n/// # see usage\n/// bloc --help\n/// ```\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary bloc_tools;\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/command_runner.dart",
    "content": "import 'package:args/args.dart';\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_tools/src/commands/commands.dart';\nimport 'package:bloc_tools/src/lsp/language_server.dart';\nimport 'package:bloc_tools/src/version.dart';\nimport 'package:mason/mason.dart' show ExitCode, Logger, lightCyan, lightYellow;\nimport 'package:pub_updater/pub_updater.dart';\n\n/// The package name.\nconst packageName = 'bloc_tools';\n\n/// {@template bloc_tools_command_runner}\n/// A [CommandRunner] for the Bloc Tools CLI.\n/// {@endtemplate}\nclass BlocToolsCommandRunner extends CommandRunner<int> {\n  /// {@macro bloc_tools_command_runner}\n  BlocToolsCommandRunner({\n    Logger? logger,\n    PubUpdater? pubUpdater,\n    Linter linter = const Linter(),\n    LanguageServer Function()? languageServerBuilder,\n  }) : _logger = logger ?? Logger(),\n       _pubUpdater = pubUpdater ?? PubUpdater(),\n       super('bloc', 'Command Line Tools for the Bloc Library.') {\n    argParser.addFlag(\n      'version',\n      negatable: false,\n      help: 'Print the current version.',\n    );\n    addCommand(\n      LanguageServerCommand(languageServerBuilder: languageServerBuilder),\n    );\n    addCommand(NewCommand(logger: _logger));\n    addCommand(LintCommand(linter: linter, logger: _logger));\n  }\n\n  final Logger _logger;\n  final PubUpdater _pubUpdater;\n\n  @override\n  Future<int> run(Iterable<String> args) async {\n    try {\n      final argResults = parse(args);\n      return await runCommand(argResults) ?? ExitCode.success.code;\n    } on FormatException catch (e, stackTrace) {\n      _logger\n        ..err(e.message)\n        ..err('$stackTrace')\n        ..info('')\n        ..info(usage);\n      return ExitCode.usage.code;\n    } on UsageException catch (e) {\n      _logger\n        ..err(e.message)\n        ..info('')\n        ..info(usage);\n      return ExitCode.usage.code;\n    }\n  }\n\n  @override\n  Future<int?> runCommand(ArgResults topLevelResults) async {\n    int? exitCode = ExitCode.unavailable.code;\n    if (topLevelResults['version'] == true) {\n      _logger.info(packageVersion);\n      exitCode = ExitCode.success.code;\n    } else {\n      exitCode = await super.runCommand(topLevelResults);\n    }\n    // Avoid disrupting stdout when running the language server.\n    if (topLevelResults.command?.name == 'language-server') return exitCode;\n    await _checkForUpdates();\n    return exitCode;\n  }\n\n  Future<void> _checkForUpdates() async {\n    try {\n      final latestVersion = await _pubUpdater.getLatestVersion(packageName);\n      final isUpToDate = packageVersion == latestVersion;\n      if (!isUpToDate) {\n        _logger\n          ..info('')\n          ..info('''\n+------------------------------------------------------------------------------------+\n|                                                                                    |\n|                    ${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \\u2192 ${lightCyan.wrap(latestVersion)}                     |\n|  ${lightYellow.wrap('Changelog:')} ${lightCyan.wrap('https://github.com/felangel/bloc/releases/tag/$packageName-v$latestVersion')}  |\n|                                                                                    |\n+------------------------------------------------------------------------------------+\n''');\n        final confirm = _logger.confirm('Would you like to update?');\n        if (confirm) {\n          final progress = _logger.progress('Updating to $latestVersion');\n          await _pubUpdater.update(packageName: packageName);\n          progress.complete('Updated to $latestVersion');\n        }\n      }\n    } catch (_) {}\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/commands.dart",
    "content": "export 'language_server/language_server_command.dart';\nexport 'lint/lint_command.dart';\nexport 'new/new_command.dart';\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/language_server/language_server_command.dart",
    "content": "import 'dart:async';\n\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_tools/src/lsp/language_server.dart';\nimport 'package:mason/mason.dart';\n\n/// {@template language_server_command}\n/// The `bloc language-server` command starts bloc's language server.\n/// {@endtemplate}\nclass LanguageServerCommand extends Command<int> {\n  /// {@macro language_server_command}\n  LanguageServerCommand({LanguageServer Function()? languageServerBuilder})\n    : _languageServerBuilder = languageServerBuilder ?? LanguageServer.new;\n\n  @override\n  String get summary => '$invocation\\n$description';\n\n  @override\n  String get description => 'Start the bloc language server.';\n\n  @override\n  String get name => 'language-server';\n\n  @override\n  bool get hidden => true;\n\n  final LanguageServer Function() _languageServerBuilder;\n\n  @override\n  Future<int> run() async {\n    await _languageServerBuilder().listen();\n    return ExitCode.success.code;\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/lint/lint_command.dart",
    "content": "import 'dart:io';\n\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:mason/mason.dart';\nimport 'package:path/path.dart' as p;\n\n/// {@template new_command}\n/// The `bloc lint` command lints Dart source code.\n/// {@endtemplate}\nclass LintCommand extends Command<int> {\n  /// {@macro new_command}\n  LintCommand({required Logger logger, required Linter linter})\n    : _linter = linter,\n      _logger = logger;\n\n  @override\n  String get summary => '$invocation\\n$description';\n\n  @override\n  String get description => 'Lint Dart source code.';\n\n  @override\n  String get name => 'lint';\n\n  final Linter _linter;\n  final Logger _logger;\n\n  @override\n  int run() {\n    final rest = argResults?.rest ?? [];\n    if (rest.isEmpty) {\n      _logger.info('''\n${styleBold.wrap(lightRed.wrap('error'))}: No files specified.\nUsage: bloc lint [options] [files]...''');\n      return ExitCode.usage.code;\n    }\n\n    final uris = rest.map((path) => path.toUri()).whereType<Uri>();\n    final diagnostics = uris\n        .map((uri) => _linter.analyze(uri: uri))\n        .fold(<String, List<Diagnostic>>{}, (prev, curr) => {...prev, ...curr});\n\n    if (diagnostics.isEmpty) {\n      _logger.info(\n        '${styleBold.wrap(lightRed.wrap('error'))}: No files found.',\n      );\n      return ExitCode.usage.code;\n    }\n\n    var issueCount = 0;\n    var fileCount = 0;\n    for (final entry in diagnostics.entries) {\n      for (final diagnostic in entry.value) {\n        final document = TextDocument(\n          uri: Uri.parse(entry.key),\n          content: File(entry.key).readAsStringSync(),\n        );\n        _logger.info(diagnostic.prettify(entry.key, document));\n        issueCount++;\n      }\n      fileCount++;\n    }\n    _logger.info('''\n$issueCount ${issueCount == 1 ? 'issue' : 'issues'} found\nAnalyzed $fileCount ${fileCount == 1 ? 'file' : 'files'}''');\n\n    return issueCount > 0 ? 1 : 0;\n  }\n}\n\nextension on String {\n  Uri? toUri() {\n    /// This function implements the behavior of `canonicalize` from\n    /// `package:path`.\n    /// However, it does not change the ASCII case of the path.\n    /// See https://github.com/dart-lang/path/issues/102.\n    return Uri.tryParse(p.normalize(p.absolute(this)).replaceAll(r'\\', '/'));\n  }\n}\n\nextension on Severity {\n  String? Function(String? value, {bool forScript}) toStyle() {\n    switch (this) {\n      case Severity.error:\n        return lightRed.wrap;\n      case Severity.warning:\n        return lightYellow.wrap;\n      case Severity.info:\n        return lightCyan.wrap;\n      case Severity.hint:\n        return darkGray.wrap;\n    }\n  }\n}\n\nextension on Diagnostic {\n  String prettify(String path, TextDocument document) {\n    final relativePath = p\n        .relative(path, from: Directory.current.path)\n        .replaceAll(r'\\', '/');\n    final style = severity.toStyle();\n    final text = document.getText(\n      range: Range(\n        start: Position(line: range.start.line, character: 0),\n        end: Position(line: range.start.line, character: 120),\n      ),\n    );\n    final highlight = StringBuffer();\n    for (var i = 0; i < range.start.character; i++) {\n      highlight.write(' ');\n    }\n    for (var i = range.start.character; i < range.end.character; i++) {\n      highlight.write('^');\n    }\n    return [\n      '''${styleBold.wrap(style('${severity.name}[$code]'))}: ${styleBold.wrap(message)}''',\n      ''' ${darkGray.wrap('-->')} ${darkGray.wrap(relativePath)}${darkGray.wrap(':${range.start.line + 1}')}''',\n      '''  ${darkGray.wrap('|')}''',\n      '''  ${darkGray.wrap('|')} $text''',\n      '''  ${darkGray.wrap('|')} ${style(highlight.toString())}''',\n      '''  ${darkGray.wrap('=')} ${hint.isNotEmpty ? '${styleBold.wrap('hint:')} $hint' : ''}''',\n      ''' ${lightBlue.wrap('docs:')} $description''',\n      '',\n    ].join('\\n');\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/bloc_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal blocBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_bloc.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2Jsb2MgfX17ey91c2VfZnJlZXplZH19e3sjdXNlX2VxdWF0YWJsZX19e3s+IGVxdWF0YWJsZV9ibG9jIH19e3svdXNlX2VxdWF0YWJsZX19e3sjdXNlX2Jhc2ljfX17ez4gYmFzaWNfYmxvYyB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_event.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2V2ZW50IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfZXZlbnQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19ldmVudCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2V2ZW50LmRhcnQnOwpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MgZXh0ZW5kcyBCbG9jPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50LCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlKCkpIHsKICAgIG9uPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50PigoZXZlbnQsIGVtaXQpIHsKICAgICAgLy8gVE9ETzogaW1wbGVtZW50IGV2ZW50IGhhbmRsZXIKICAgIH0pOwogIH0KfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCnNlYWxlZCBjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQoKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9ldmVudC5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fc3RhdGUuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1CbG9jIGV4dGVuZHMgQmxvYzx7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCwge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGU+IHsKICB7e25hbWUucGFzY2FsQ2FzZSgpfX1CbG9jKCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpKSB7CiAgICBvbjx7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudD4oKGV2ZW50LCBlbWl0KSB7CiAgICAgIC8vIFRPRE86IGltcGxlbWVudCBldmVudCBoYW5kbGVyCiAgICB9KTsKICB9Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCnNlYWxlZCBjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCBleHRlbmRzIEVxdWF0YWJsZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGV4dGVuZHMgRXF1YXRhYmxlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwoKICBAb3ZlcnJpZGUKICBMaXN0PE9iamVjdD4gZ2V0IHByb3BzID0+IFtdOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9ldmVudC5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fc3RhdGUuZGFydCc7CnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2Jsb2MuZnJlZXplZC5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MgZXh0ZW5kcyBCbG9jPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50LCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlLmluaXRpYWwoKSkgewogICAgb248e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQ+KChldmVudCwgZW1pdCkgewogICAgICAvLyBUT0RPOiBpbXBsZW1lbnQgZXZlbnQgaGFuZGxlcgogICAgfSk7CiAgfQp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IHdpdGggXyR7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCB7CiAgY29uc3QgZmFjdG9yeSB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudC5zdGFydGVkKCkgPSBfU3RhcnRlZDsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHdpdGggXyR7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3QgZmFjdG9yeSB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZS5pbml0aWFsKCkgPSBfSW5pdGlhbDsKfQo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogYmxvY19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy41LjQKCmRlcGVuZGVuY2llczoKICBtYXNvbjogXjAuMS4wCg==\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"bloc\",\n  \"description\":\n      \"Generate a new Bloc in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.4.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\": \"https://github.com/felangel/bloc/tree/master/bricks/bloc\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL2Jsb2NfZGFyay5wbmciIGFsdD0iQmxvYyIgLz4KPC9wPgoKPHAgYWxpZ249ImNlbnRlciI+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jL2FjdGlvbnMiPjxpbWcgc3JjPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYy93b3JrZmxvd3MvYnVpbGQvYmFkZ2Uuc3ZnIiBhbHQ9ImJ1aWxkIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vY29kZWNvdi5pby9naC9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9jb2RlY292LmlvL2doL2ZlbGFuZ2VsL0Jsb2MvYnJhbmNoL21hc3Rlci9ncmFwaC9iYWRnZS5zdmciIGFsdD0iY29kZWNvdiI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZ2l0aHViL3N0YXJzL2ZlbGFuZ2VsL2Jsb2Muc3ZnP3N0eWxlPWZsYXQmbG9nbz1naXRodWImY29sb3JCPWRlZXBwaW5rJmxhYmVsPXN0YXJzIiBhbHQ9IlN0YXIgb24gR2l0aHViIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvTUlUIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS9saWNlbnNlLU1JVC1wdXJwbGUuc3ZnIiBhbHQ9IkxpY2Vuc2U6IE1JVCI+PC9hPgo8YSBocmVmPSJodHRwczovL2Rpc2NvcmQuZ2cvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZGlzY29yZC82NDk3MDg3Nzg2MzEyMDA3Nzguc3ZnP2xvZ289ZGlzY29yZCZjb2xvcj1ibHVlIiBhbHQ9IkRpc2NvcmQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL3Rpbnl1cmwuY29tL2Jsb2MtbGlicmFyeSIgYWx0PSJCbG9jIExpYnJhcnkiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL21hc29uIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9lbmRwb2ludD91cmw9aHR0cHMlM0ElMkYlMkZ0aW55dXJsLmNvbSUyRm1hc29uLWJhZGdlIiBhbHQ9IlBvd2VyZWQgYnkgTWFzb24iPjwvYT4KPC9wPgoKR2VuZXJhdGUgYSBuZXcgQmxvYyBpbiBbRGFydF1bMV0uIEJ1aWx0IGZvciB0aGUgW2Jsb2Mgc3RhdGUgbWFuYWdlbWVudCBsaWJyYXJ5XVsyXS4KCiMjIFVzYWdlIPCfmoAKCmBgYHNoCm1hc29uIG1ha2UgYmxvYyAtLW5hbWUgY291bnRlciAtLXN0eWxlIGJhc2ljCmBgYAoKIyMgVmFyaWFibGVzIOKcqAoKfCBWYXJpYWJsZSB8IERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICB8IERlZmF1bHQgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVHlwZSAgICAgfAp8IC0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLSB8CnwgYG5hbWVgICAgfCBUaGUgbmFtZSBvZiB0aGUgYmxvYyBjbGFzcyAgfCBgY291bnRlcmAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IGBzdHJpbmdgIHwKfCBgc3R5bGVgICB8IFRoZSBzdHlsZSBvZiBibG9jIGdlbmVyYXRlZCB8IGBiYXNpYyAoYmFzaWMsIGVxdWF0YWJsZSwgZnJlZXplZClgIHwgYGVudW1gICAgfAoKIyMgT3V0cHV0IPCfk6YKCmBgYHNoCuKUnOKUgOKUgCBjb3VudGVyX2Jsb2MuZGFydArilJzilIDilIAgY291bnRlcl9ldmVudC5kYXJ0CuKUlOKUgOKUgCBjb3VudGVyX3N0YXRlLmRhcnQKYGBgCgpbMV06IGh0dHBzOi8vZGFydC5kZXYKWzJdOiBodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYwo=\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjQuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4zLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMy4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgc2VhbGVkYCBldmVudHMgdXNpbmcgRGFydCAzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjMrMgoKLSBkb2NzOiB1c2UgZGFyayBsb2dvIHZhcmlhbnQKCiMgMC4xLjMrMQoKLSBkb2NzOiBhZGp1c3QgbG9nbyBzaXplIGluIFJFQURNRQoKIyAwLjEuMwoKLSBkb2NzOiBhZGQgYmFkZ2VzIHRvIFJFQURNRQoKIyAwLjEuMgoKLSBkb2NzOiBtaW5vciBSRUFETUUgdXBkYXRlCgojIDAuMS4xCgotIHJlZmFjdG9yOiB1cGdyYWRlIHRvIHNob3J0aGFuZCBsYW1iZGEgc3ludGF4IChgbWFzb24gPj0gMC4xLjAtZGV2LjE1YCkKCiMgMC4xLjAKCi0gZmVhdDogaW5pdGlhbCByZWxlYXNlIHdpdGggc3VwcG9ydCBmb3IgYmFzaWMgYmxvYyBnZW5lcmF0aW9uCg==\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the bloc class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"What is the bloc name?\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of bloc generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the bloc style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/bundles.dart",
    "content": "import 'package:bloc_tools/src/commands/new/bundles/bloc_bundle.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/cubit_bundle.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/hydrated_bloc_bundle.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/hydrated_cubit_bundle.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/replay_bloc_bundle.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/replay_cubit_bundle.dart';\n\n/// All the supported bundles.\nfinal bundles = [\n  blocBundle,\n  cubitBundle,\n  hydratedBlocBundle,\n  hydratedCubitBundle,\n  replayBlocBundle,\n  replayCubitBundle,\n];\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/cubit_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal cubitBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_cubit.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2N1Yml0IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfY3ViaXQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19jdWJpdCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X3N0YXRlLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQgZXh0ZW5kcyBDdWJpdDx7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0KCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0IGV4dGVuZHMgQ3ViaXQ8e3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGU+IHsKICB7e25hbWUucGFzY2FsQ2FzZSgpfX1DdWJpdCgpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKSk7Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBleHRlbmRzIEVxdWF0YWJsZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmJsb2MvYmxvYy5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZnJlZXplZC5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0IGV4dGVuZHMgQ3ViaXQ8e3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGU+IHsKICB7e25hbWUucGFzY2FsQ2FzZSgpfX1DdWJpdCgpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpAZnJlZXplZApjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB3aXRoIF8ke3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgewogIGNvbnN0IGZhY3Rvcnkge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpID0gX0luaXRpYWw7Cn0K\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogY3ViaXRfaG9va3MKCmVudmlyb25tZW50OgogIHNkazogXjMuNS40CgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMAo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"cubit\",\n  \"description\":\n      \"Generate a new Cubit in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.3.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\": \"https://github.com/felangel/bloc/tree/master/bricks/cubit\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL2N1Yml0X2RhcmsucG5nIiBhbHQ9IkN1Yml0IiAvPgo8L3A+Cgo8cCBhbGlnbj0iY2VudGVyIj4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MvYWN0aW9ucyI+PGltZyBzcmM9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jL3dvcmtmbG93cy9idWlsZC9iYWRnZS5zdmciIGFsdD0iYnVpbGQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9jb2RlY292LmlvL2doL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2NvZGVjb3YuaW8vZ2gvZmVsYW5nZWwvQmxvYy9icmFuY2gvbWFzdGVyL2dyYXBoL2JhZGdlLnN2ZyIgYWx0PSJjb2RlY292Ij48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9naXRodWIvc3RhcnMvZmVsYW5nZWwvYmxvYy5zdmc/c3R5bGU9ZmxhdCZsb2dvPWdpdGh1YiZjb2xvckI9ZGVlcHBpbmsmbGFiZWw9c3RhcnMiIGFsdD0iU3RhciBvbiBHaXRodWIiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9NSVQiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL2xpY2Vuc2UtTUlULXB1cnBsZS5zdmciIGFsdD0iTGljZW5zZTogTUlUIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZGlzY29yZC5nZy9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9kaXNjb3JkLzY0OTcwODc3ODYzMTIwMDc3OC5zdmc/bG9nbz1kaXNjb3JkJmNvbG9yPWJsdWUiIGFsdD0iRGlzY29yZCI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vdGlueXVybC5jb20vYmxvYy1saWJyYXJ5IiBhbHQ9IkJsb2MgTGlicmFyeSI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvbWFzb24iPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2VuZHBvaW50P3VybD1odHRwcyUzQSUyRiUyRnRpbnl1cmwuY29tJTJGbWFzb24tYmFkZ2UiIGFsdD0iUG93ZXJlZCBieSBNYXNvbiI+PC9hPgo8L3A+CgpHZW5lcmF0ZSBhIG5ldyBDdWJpdCBpbiBbRGFydF1bMV0uIEJ1aWx0IGZvciB0aGUgW2Jsb2Mgc3RhdGUgbWFuYWdlbWVudCBsaWJyYXJ5XVsyXS4KCiMjIFVzYWdlIPCfmoAKCmBgYHNoCm1hc29uIG1ha2UgY3ViaXQgLS1uYW1lIGNvdW50ZXIgLS1zdHlsZSBiYXNpYwpgYGAKCiMjIFZhcmlhYmxlcyDinKgKCnwgVmFyaWFibGUgfCBEZXNjcmlwdGlvbiAgICAgICAgICAgICAgICAgIHwgRGVmYXVsdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUeXBlICAgICB8CnwgLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLSB8CnwgYG5hbWVgICAgfCBUaGUgbmFtZSBvZiB0aGUgY3ViaXQgY2xhc3MgIHwgYGNvdW50ZXJgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBgc3RyaW5nYCB8CnwgYHN0eWxlYCAgfCBUaGUgc3R5bGUgb2YgY3ViaXQgZ2VuZXJhdGVkIHwgYGJhc2ljIChiYXNpYywgZXF1YXRhYmxlLCBmcmVlemVkKWAgfCBgZW51bWAgICB8CgojIyBPdXRwdXQg8J+TpgoKYGBgc2gK4pSc4pSA4pSAIGNvdW50ZXJfY3ViaXQuZGFydArilJTilIDilIAgY291bnRlcl9zdGF0ZS5kYXJ0CmBgYAoKWzFdOiBodHRwczovL2RhcnQuZGV2ClsyXTogaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MK\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjMuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4yLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjMKCi0gZG9jczogYWRkIGJhZGdlcyB0byBSRUFETUUKLSBkb2NzOiB1c2UgZGFyayBsb2dvIHZhcmlhbnQKCiMgMC4xLjIKCi0gZG9jczogbWlub3IgUkVBRE1FIHVwZGF0ZQoKIyAwLjEuMQoKLSByZWZhY3RvcjogdXBncmFkZSB0byBzaG9ydGhhbmQgbGFtYmRhIHN5bnRheCAoYG1hc29uID49IDAuMS4wLWRldi4xNWApCgojIDAuMS4wCgotIGZlYXQ6IGluaXRpYWwgcmVsZWFzZSB3aXRoIHN1cHBvcnQgZm9yIGJhc2ljIGN1Yml0IGdlbmVyYXRpb24K\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the cubit class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"Please enter the cubit name.\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of cubit generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the cubit style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/hydrated_bloc_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal hydratedBlocBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_bloc.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2Jsb2MgfX17ey91c2VfZnJlZXplZH19e3sjdXNlX2VxdWF0YWJsZX19e3s+IGVxdWF0YWJsZV9ibG9jIH19e3svdXNlX2VxdWF0YWJsZX19e3sjdXNlX2Jhc2ljfX17ez4gYmFzaWNfYmxvYyB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_event.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2V2ZW50IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfZXZlbnQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19ldmVudCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmh5ZHJhdGVkX2Jsb2MvaHlkcmF0ZWRfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2V2ZW50LmRhcnQnOwpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MgZXh0ZW5kcyBIeWRyYXRlZEJsb2M8e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQsIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYygpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKSkgewogICAgb248e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQ+KChldmVudCwgZW1pdCkgewogICAgICAvLyBUT0RPOiBpbXBsZW1lbnQgZXZlbnQgaGFuZGxlcgogICAgfSk7CiAgfQoKICBAb3ZlcnJpZGUKICBNYXA8U3RyaW5nLCBkeW5hbWljPiB0b0pzb24oe3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgc3RhdGUpIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCB0b0pzb24KICB9CgogIEBvdmVycmlkZQogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGZyb21Kc29uKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGpzb24pIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCBmcm9tSnNvbgogIH0KfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCnNlYWxlZCBjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQoKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CmltcG9ydCAncGFja2FnZTpoeWRyYXRlZF9ibG9jL2h5ZHJhdGVkX2Jsb2MuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9ldmVudC5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fc3RhdGUuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1CbG9jIGV4dGVuZHMgSHlkcmF0ZWRCbG9jPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50LCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlKCkpIHsKICAgIG9uPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50PigoZXZlbnQsIGVtaXQpIHsKICAgICAgLy8gVE9ETzogaW1wbGVtZW50IGV2ZW50IGhhbmRsZXIKICAgIH0pOwogIH0KCiAgQG92ZXJyaWRlCiAgTWFwPFN0cmluZywgZHluYW1pYz4gdG9Kc29uKHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHN0YXRlKSB7CiAgICAvLyBUT0RPOiBpbXBsZW1lbnQgdG9Kc29uCiAgfQoKICBAb3ZlcnJpZGUKICB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBmcm9tSnNvbihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSB7CiAgICAvLyBUT0RPOiBpbXBsZW1lbnQgZnJvbUpzb24KICB9Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCnNlYWxlZCBjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCBleHRlbmRzIEVxdWF0YWJsZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGV4dGVuZHMgRXF1YXRhYmxlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwoKICBAb3ZlcnJpZGUKICBMaXN0PE9iamVjdD4gZ2V0IHByb3BzID0+IFtdOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpoeWRyYXRlZF9ibG9jL2h5ZHJhdGVkX2Jsb2MuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9ldmVudC5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fc3RhdGUuZGFydCc7CnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2Jsb2MuZnJlZXplZC5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MgZXh0ZW5kcyBIeWRyYXRlZEJsb2M8e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQsIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYygpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpKSB7CiAgICBvbjx7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudD4oKGV2ZW50LCBlbWl0KSB7CiAgICAgIC8vIFRPRE86IGltcGxlbWVudCBldmVudCBoYW5kbGVyCiAgICB9KTsKICB9CgogIEBvdmVycmlkZQogIE1hcDxTdHJpbmcsIGR5bmFtaWM+IHRvSnNvbih7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBzdGF0ZSkgewogICAgLy8gVE9ETzogaW1wbGVtZW50IHRvSnNvbgogIH0KCiAgQG92ZXJyaWRlCiAge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgZnJvbUpzb24oTWFwPFN0cmluZywgZHluYW1pYz4ganNvbikgewogICAgLy8gVE9ETzogaW1wbGVtZW50IGZyb21Kc29uCiAgfQp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IHdpdGggXyR7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudCB7CiAgY29uc3QgZmFjdG9yeSB7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudC5zdGFydGVkKCkgPSBfU3RhcnRlZDsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHdpdGggXyR7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3QgZmFjdG9yeSB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZS5pbml0aWFsKCkgPSBfSW5pdGlhbDsKfQo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogaHlkcmF0ZWRfYmxvY19ob29rcwoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy41LjQKCmRlcGVuZGVuY2llczoKICBtYXNvbjogXjAuMS4wCg==\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"hydrated_bloc\",\n  \"description\":\n      \"Generate a new HydratedBloc in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.4.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\":\n      \"https://github.com/felangel/bloc/tree/master/bricks/hydrated_bloc\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL2h5ZHJhdGVkX2Jsb2NfZGFyay5wbmciIGFsdD0iQmxvYyIgLz4KPC9wPgoKPHAgYWxpZ249ImNlbnRlciI+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jL2FjdGlvbnMiPjxpbWcgc3JjPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYy93b3JrZmxvd3MvYnVpbGQvYmFkZ2Uuc3ZnIiBhbHQ9ImJ1aWxkIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vY29kZWNvdi5pby9naC9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9jb2RlY292LmlvL2doL2ZlbGFuZ2VsL0Jsb2MvYnJhbmNoL21hc3Rlci9ncmFwaC9iYWRnZS5zdmciIGFsdD0iY29kZWNvdiI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZ2l0aHViL3N0YXJzL2ZlbGFuZ2VsL2Jsb2Muc3ZnP3N0eWxlPWZsYXQmbG9nbz1naXRodWImY29sb3JCPWRlZXBwaW5rJmxhYmVsPXN0YXJzIiBhbHQ9IlN0YXIgb24gR2l0aHViIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvTUlUIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS9saWNlbnNlLU1JVC1wdXJwbGUuc3ZnIiBhbHQ9IkxpY2Vuc2U6IE1JVCI+PC9hPgo8YSBocmVmPSJodHRwczovL2Rpc2NvcmQuZ2cvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZGlzY29yZC82NDk3MDg3Nzg2MzEyMDA3Nzguc3ZnP2xvZ289ZGlzY29yZCZjb2xvcj1ibHVlIiBhbHQ9IkRpc2NvcmQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL3Rpbnl1cmwuY29tL2Jsb2MtbGlicmFyeSIgYWx0PSJCbG9jIExpYnJhcnkiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL21hc29uIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9lbmRwb2ludD91cmw9aHR0cHMlM0ElMkYlMkZ0aW55dXJsLmNvbSUyRm1hc29uLWJhZGdlIiBhbHQ9IlBvd2VyZWQgYnkgTWFzb24iPjwvYT4KPC9wPgoKR2VuZXJhdGUgYSBuZXcgSHlkcmF0ZWRCbG9jIGluIFtEYXJ0XVsxXS4gQnVpbHQgZm9yIHRoZSBbYmxvYyBzdGF0ZSBtYW5hZ2VtZW50IGxpYnJhcnldWzJdLgoKIyMgVXNhZ2Ug8J+agAoKYGBgc2gKbWFzb24gbWFrZSBoeWRyYXRlZF9ibG9jIC0tbmFtZSBjb3VudGVyIC0tc3R5bGUgYmFzaWMKYGBgCgojIyBWYXJpYWJsZXMg4pyoCgp8IFZhcmlhYmxlIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgIHwgRGVmYXVsdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUeXBlICAgICB8CnwgLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tIHwKfCBgbmFtZWAgICB8IFRoZSBuYW1lIG9mIHRoZSBibG9jIGNsYXNzICB8IGBjb3VudGVyYCAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgYHN0cmluZ2AgfAp8IGBzdHlsZWAgIHwgVGhlIHN0eWxlIG9mIGJsb2MgZ2VuZXJhdGVkIHwgYGJhc2ljIChiYXNpYywgZXF1YXRhYmxlLCBmcmVlemVkKWAgfCBgZW51bWAgICB8CgojIyBPdXRwdXQg8J+TpgoKYGBgc2gK4pSc4pSA4pSAIGNvdW50ZXJfYmxvYy5kYXJ0CuKUnOKUgOKUgCBjb3VudGVyX2V2ZW50LmRhcnQK4pSU4pSA4pSAIGNvdW50ZXJfc3RhdGUuZGFydApgYGAKClsxXTogaHR0cHM6Ly9kYXJ0LmRldgpbMl06IGh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jCg==\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjQuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4zLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMy4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgc2VhbGVkYCBldmVudHMgdXNpbmcgRGFydCAzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjIKCi0gZG9jczogYWRkIGJhZGdlcyB0byBSRUFETUUKLSBkb2NzOiB1c2UgZGFyayBsb2dvIHZhcmlhbnQKCiMgMC4xLjEKCi0gZG9jczogbWlub3IgUkVBRE1FIHVwZGF0ZQoKIyAwLjEuMAoKLSBmZWF0OiBpbml0aWFsIHJlbGVhc2Ugd2l0aCBzdXBwb3J0IGZvciBiYXNpYyBoeWRyYXRlZCBibG9jIGdlbmVyYXRpb24K\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the bloc class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"Please enter the bloc name.\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of bloc generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the bloc style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/hydrated_cubit_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal hydratedCubitBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_cubit.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2N1Yml0IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfY3ViaXQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19jdWJpdCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmh5ZHJhdGVkX2Jsb2MvaHlkcmF0ZWRfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X3N0YXRlLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQgZXh0ZW5kcyBIeWRyYXRlZEN1Yml0PHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlKCkpOwoKICBAb3ZlcnJpZGUKICBNYXA8U3RyaW5nLCBkeW5hbWljPiB0b0pzb24oe3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgc3RhdGUpIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCB0b0pzb24KICB9CgogIEBvdmVycmlkZQogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGZyb21Kc29uKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGpzb24pIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCBmcm9tSnNvbgogIH0KfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CmltcG9ydCAncGFja2FnZTpoeWRyYXRlZF9ibG9jL2h5ZHJhdGVkX2Jsb2MuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0IGV4dGVuZHMgSHlkcmF0ZWRDdWJpdDx7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0KCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpKTsKCiAgQG92ZXJyaWRlCiAgTWFwPFN0cmluZywgZHluYW1pYz4gdG9Kc29uKHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHN0YXRlKSB7CiAgICAvLyBUT0RPOiBpbXBsZW1lbnQgdG9Kc29uCiAgfQoKICBAb3ZlcnJpZGUKICB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBmcm9tSnNvbihNYXA8U3RyaW5nLCBkeW5hbWljPiBqc29uKSB7CiAgICAvLyBUT0RPOiBpbXBsZW1lbnQgZnJvbUpzb24KICB9Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBleHRlbmRzIEVxdWF0YWJsZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpoeWRyYXRlZF9ibG9jL2h5ZHJhdGVkX2Jsb2MuZGFydCc7CgpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZnJlZXplZC5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0IGV4dGVuZHMgSHlkcmF0ZWRDdWJpdDx7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0KCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZS5pbml0aWFsKCkpOwoKICBAb3ZlcnJpZGUKICBNYXA8U3RyaW5nLCBkeW5hbWljPiB0b0pzb24oe3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgc3RhdGUpIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCB0b0pzb24KICB9CgogIEBvdmVycmlkZQogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGZyb21Kc29uKE1hcDxTdHJpbmcsIGR5bmFtaWM+IGpzb24pIHsKICAgIC8vIFRPRE86IGltcGxlbWVudCBmcm9tSnNvbgogIH0KfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpAZnJlZXplZApjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB3aXRoIF8ke3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgewogIGNvbnN0IGZhY3Rvcnkge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpID0gX0luaXRpYWw7Cn0K\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogaHlkcmF0ZWRfY3ViaXRfaG9va3MKCmVudmlyb25tZW50OgogIHNkazogXjMuNS40CgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMAo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"hydrated_cubit\",\n  \"description\":\n      \"Generate a new HydratedCubit in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.3.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\":\n      \"https://github.com/felangel/bloc/tree/master/bricks/hydrated_cubit\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL2h5ZHJhdGVkX2N1Yml0X2RhcmsucG5nIiBhbHQ9Ikh5ZHJhdGVkIEN1Yml0IiAvPgo8L3A+Cgo8cCBhbGlnbj0iY2VudGVyIj4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MvYWN0aW9ucyI+PGltZyBzcmM9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jL3dvcmtmbG93cy9idWlsZC9iYWRnZS5zdmciIGFsdD0iYnVpbGQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9jb2RlY292LmlvL2doL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2NvZGVjb3YuaW8vZ2gvZmVsYW5nZWwvQmxvYy9icmFuY2gvbWFzdGVyL2dyYXBoL2JhZGdlLnN2ZyIgYWx0PSJjb2RlY292Ij48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9naXRodWIvc3RhcnMvZmVsYW5nZWwvYmxvYy5zdmc/c3R5bGU9ZmxhdCZsb2dvPWdpdGh1YiZjb2xvckI9ZGVlcHBpbmsmbGFiZWw9c3RhcnMiIGFsdD0iU3RhciBvbiBHaXRodWIiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9NSVQiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL2xpY2Vuc2UtTUlULXB1cnBsZS5zdmciIGFsdD0iTGljZW5zZTogTUlUIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZGlzY29yZC5nZy9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9kaXNjb3JkLzY0OTcwODc3ODYzMTIwMDc3OC5zdmc/bG9nbz1kaXNjb3JkJmNvbG9yPWJsdWUiIGFsdD0iRGlzY29yZCI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vdGlueXVybC5jb20vYmxvYy1saWJyYXJ5IiBhbHQ9IkJsb2MgTGlicmFyeSI+PC9hPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvbWFzb24iPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2VuZHBvaW50P3VybD1odHRwcyUzQSUyRiUyRnRpbnl1cmwuY29tJTJGbWFzb24tYmFkZ2UiIGFsdD0iUG93ZXJlZCBieSBNYXNvbiI+PC9hPgo8L3A+CgpHZW5lcmF0ZSBhIG5ldyBIeWRyYXRlZEN1Yml0IGluIFtEYXJ0XVsxXS4gQnVpbHQgZm9yIHRoZSBbYmxvYyBzdGF0ZSBtYW5hZ2VtZW50IGxpYnJhcnldWzJdLgoKIyMgVXNhZ2Ug8J+agAoKYGBgc2gKbWFzb24gbWFrZSBoeWRyYXRlZF9jdWJpdCAtLW5hbWUgY291bnRlciAtLXN0eWxlIGJhc2ljCmBgYAoKIyMgVmFyaWFibGVzIOKcqAoKfCBWYXJpYWJsZSB8IERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgfCBEZWZhdWx0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFR5cGUgICAgIHwKfCAtLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tIHwKfCBgbmFtZWAgICB8IFRoZSBuYW1lIG9mIHRoZSBjdWJpdCBjbGFzcyAgfCBgY291bnRlcmAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IGBzdHJpbmdgIHwKfCBgc3R5bGVgICB8IFRoZSBzdHlsZSBvZiBjdWJpdCBnZW5lcmF0ZWQgfCBgYmFzaWMgKGJhc2ljLCBlcXVhdGFibGUsIGZyZWV6ZWQpYCB8IGBlbnVtYCAgIHwKCiMjIE91dHB1dCDwn5OmCgpgYGBzaArilJzilIDilIAgY291bnRlcl9jdWJpdC5kYXJ0CuKUlOKUgOKUgCBjb3VudGVyX3N0YXRlLmRhcnQKYGBgCgpbMV06IGh0dHBzOi8vZGFydC5kZXYKWzJdOiBodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYwo=\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjMuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4yLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjMKCi0gZml4OiBwYXJ0IGFuZCBpbXBvcnRzCgojIDAuMS4yCgotIGRvY3M6IGFkZCBiYWRnZXMgdG8gUkVBRE1FCi0gZG9jczogdXNlIGRhcmsgbG9nbyB2YXJpYW50CgojIDAuMS4xCgotIGRvY3M6IG1pbm9yIFJFQURNRSB1cGRhdGUKCiMgMC4xLjAKCi0gZmVhdDogaW5pdGlhbCByZWxlYXNlIHdpdGggc3VwcG9ydCBmb3IgYmFzaWMgaHlkcmF0ZWQgY3ViaXQgZ2VuZXJhdGlvbgo=\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the cubit class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"Please enter the cubit name.\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of cubit generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the cubit style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/replay_bloc_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal replayBlocBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_bloc.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2Jsb2MgfX17ey91c2VfZnJlZXplZH19e3sjdXNlX2VxdWF0YWJsZX19e3s+IGVxdWF0YWJsZV9ibG9jIH19e3svdXNlX2VxdWF0YWJsZX19e3sjdXNlX2Jhc2ljfX17ez4gYmFzaWNfYmxvYyB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_event.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2V2ZW50IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfZXZlbnQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19ldmVudCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOnJlcGxheV9ibG9jL3JlcGxheV9ibG9jLmRhcnQnOwoKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fZXZlbnQuZGFydCc7CnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X3N0YXRlLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYyBleHRlbmRzIFJlcGxheUJsb2M8e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQsIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYygpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKSkgewogICAgb248e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQ+KChldmVudCwgZW1pdCkgewogICAgICAvLyBUT0RPOiBpbXBsZW1lbnQgZXZlbnQgaGFuZGxlcgogICAgfSk7CiAgfQp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IGV4dGVuZHMgUmVwbGF5RXZlbnQgewogIGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50KCk7Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CmltcG9ydCAncGFja2FnZTpyZXBsYXlfYmxvYy9yZXBsYXlfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2V2ZW50LmRhcnQnOwpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MgZXh0ZW5kcyBSZXBsYXlCbG9jPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50LCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUJsb2MoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlKCkpIHsKICAgIG9uPHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50PigoZXZlbnQsIGVtaXQpIHsKICAgICAgLy8gVE9ETzogaW1wbGVtZW50IGV2ZW50IGhhbmRsZXIKICAgIH0pOwogIH0KfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IGV4dGVuZHMgUmVwbGF5RXZlbnQgd2l0aCBFcXVhdGFibGVNaXhpbiB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIGV4dGVuZHMgRXF1YXRhYmxlIHsKICBjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpOwoKICBAb3ZlcnJpZGUKICBMaXN0PE9iamVjdD4gZ2V0IHByb3BzID0+IFtdOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_bloc }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpyZXBsYXlfYmxvYy9yZXBsYXlfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X2V2ZW50LmRhcnQnOwpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9zdGF0ZS5kYXJ0JzsKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5mcmVlemVkLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYyBleHRlbmRzIFJlcGxheUJsb2M8e3tuYW1lLnBhc2NhbENhc2UoKX19RXZlbnQsIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19QmxvYygpIDogc3VwZXIoY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpKSB7CiAgICBvbjx7e25hbWUucGFzY2FsQ2FzZSgpfX1FdmVudD4oKGV2ZW50LCBlbWl0KSB7CiAgICAgIC8vIFRPRE86IGltcGxlbWVudCBldmVudCBoYW5kbGVyCiAgICB9KTsKICB9Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_event }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IGV4dGVuZHMgUmVwbGF5RXZlbnQgd2l0aCBfJHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50IHsKICBjb25zdCBmYWN0b3J5IHt7bmFtZS5wYXNjYWxDYXNlKCl9fUV2ZW50LnN0YXJ0ZWQoKSA9IF9TdGFydGVkOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fYmxvYy5kYXJ0JzsKCkBmcmVlemVkCmNsYXNzIHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlIHdpdGggXyR7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3QgZmFjdG9yeSB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZS5pbml0aWFsKCkgPSBfSW5pdGlhbDsKfQo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogcmVwbGF5X2Jsb2NfaG9va3MKCmVudmlyb25tZW50OgogIHNkazogXjMuNS40CgpkZXBlbmRlbmNpZXM6CiAgbWFzb246IF4wLjEuMAo=\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"replay_bloc\",\n  \"description\":\n      \"Generate a new ReplayBloc in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.3.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\":\n      \"https://github.com/felangel/bloc/tree/master/bricks/replay_bloc\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL3JlcGxheV9ibG9jX2RhcmsucG5nIiBhbHQ9IkJsb2MiIC8+CjwvcD4KCjxwIGFsaWduPSJjZW50ZXIiPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYy9hY3Rpb25zIj48aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2Mvd29ya2Zsb3dzL2J1aWxkL2JhZGdlLnN2ZyIgYWx0PSJidWlsZCI+PC9hPgo8YSBocmVmPSJodHRwczovL2NvZGVjb3YuaW8vZ2gvZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vY29kZWNvdi5pby9naC9mZWxhbmdlbC9CbG9jL2JyYW5jaC9tYXN0ZXIvZ3JhcGgvYmFkZ2Uuc3ZnIiBhbHQ9ImNvZGVjb3YiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2dpdGh1Yi9zdGFycy9mZWxhbmdlbC9ibG9jLnN2Zz9zdHlsZT1mbGF0JmxvZ289Z2l0aHViJmNvbG9yQj1kZWVwcGluayZsYWJlbD1zdGFycyIgYWx0PSJTdGFyIG9uIEdpdGh1YiI+PC9hPgo8YSBocmVmPSJodHRwczovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL01JVCI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbGljZW5zZS1NSVQtcHVycGxlLnN2ZyIgYWx0PSJMaWNlbnNlOiBNSVQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9kaXNjb3JkLmdnL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2Rpc2NvcmQvNjQ5NzA4Nzc4NjMxMjAwNzc4LnN2Zz9sb2dvPWRpc2NvcmQmY29sb3I9Ymx1ZSIgYWx0PSJEaXNjb3JkIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly90aW55dXJsLmNvbS9ibG9jLWxpYnJhcnkiIGFsdD0iQmxvYyBMaWJyYXJ5Ij48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9tYXNvbiI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZW5kcG9pbnQ/dXJsPWh0dHBzJTNBJTJGJTJGdGlueXVybC5jb20lMkZtYXNvbi1iYWRnZSIgYWx0PSJQb3dlcmVkIGJ5IE1hc29uIj48L2E+CjwvcD4KCkdlbmVyYXRlIGEgbmV3IFJlcGxheUJsb2MgaW4gW0RhcnRdWzFdLiBCdWlsdCBmb3IgdGhlIFtibG9jIHN0YXRlIG1hbmFnZW1lbnQgbGlicmFyeV1bMl0uCgojIyBVc2FnZSDwn5qACgpgYGBzaAptYXNvbiBtYWtlIHJlcGxheV9ibG9jIC0tbmFtZSBjb3VudGVyIC0tc3R5bGUgYmFzaWMKYGBgCgojIyBWYXJpYWJsZXMg4pyoCgp8IFZhcmlhYmxlIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgIHwgRGVmYXVsdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUeXBlICAgICB8CnwgLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tIHwKfCBgbmFtZWAgICB8IFRoZSBuYW1lIG9mIHRoZSBibG9jIGNsYXNzICB8IGBjb3VudGVyYCAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgYHN0cmluZ2AgfAp8IGBzdHlsZWAgIHwgVGhlIHN0eWxlIG9mIGJsb2MgZ2VuZXJhdGVkIHwgYGJhc2ljIChiYXNpYywgZXF1YXRhYmxlLCBmcmVlemVkKWAgfCBgZW51bWAgICB8CgojIyBPdXRwdXQg8J+TpgoKYGBgc2gK4pSc4pSA4pSAIGNvdW50ZXJfYmxvYy5kYXJ0CuKUnOKUgOKUgCBjb3VudGVyX2V2ZW50LmRhcnQK4pSU4pSA4pSAIGNvdW50ZXJfc3RhdGUuZGFydApgYGAKClsxXTogaHR0cHM6Ly9kYXJ0LmRldgpbMl06IGh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jCg==\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjMuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4yLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjMKCi0gZml4OiBhZGQgbWlzc2luZyBgZXh0ZW5kcyBSZXBsYXlFdmVudGAKCiMgMC4xLjIKCi0gZG9jczogYWRkIGJhZGdlcyB0byBSRUFETUUKLSBkb2NzOiB1c2UgZGFyayBsb2dvIHZhcmlhbnQKCiMgMC4xLjEKCi0gZG9jczogbWlub3IgUkVBRE1FIHVwZGF0ZQoKIyAwLjEuMAoKLSBmZWF0OiBpbml0aWFsIHJlbGVhc2Ugd2l0aCBzdXBwb3J0IGZvciBiYXNpYyByZXBsYXkgYmxvYyBnZW5lcmF0aW9uCg==\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the bloc class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"Please enter the bloc name.\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of bloc generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the bloc style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/bundles/replay_cubit_bundle.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal\n\nimport 'package:mason/mason.dart';\n\nfinal replayCubitBundle = MasonBundle.fromJson(<String, dynamic>{\n  \"files\": [\n    {\n      \"path\": \"{{name.snakeCase()}}_cubit.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX2N1Yml0IH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfY3ViaXQgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19jdWJpdCB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{name.snakeCase()}}_state.dart\",\n      \"data\":\n          \"e3sjdXNlX2ZyZWV6ZWR9fXt7PiBmcmVlemVkX3N0YXRlIH19e3svdXNlX2ZyZWV6ZWR9fXt7I3VzZV9lcXVhdGFibGV9fXt7PiBlcXVhdGFibGVfc3RhdGUgfX17ey91c2VfZXF1YXRhYmxlfX17eyN1c2VfYmFzaWN9fXt7PiBiYXNpY19zdGF0ZSB9fXt7L3VzZV9iYXNpY319\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOnJlcGxheV9ibG9jL3JlcGxheV9ibG9jLmRhcnQnOwoKcGFydCAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fc3RhdGUuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1DdWJpdCBleHRlbmRzIFJlcGxheUN1Yml0PHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlPiB7CiAge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQoKSA6IHN1cGVyKGNvbnN0IHt7bmFtZS5wYXNjYWxDYXNlKCl9fVN0YXRlKCkpOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ basic_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmVxdWF0YWJsZS9lcXVhdGFibGUuZGFydCc7CmltcG9ydCAncGFja2FnZTpyZXBsYXlfYmxvYy9yZXBsYXlfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X3N0YXRlLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQgZXh0ZW5kcyBSZXBsYXlDdWJpdDx7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0KCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSgpKTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ equatable_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSBleHRlbmRzIEVxdWF0YWJsZSB7CiAgY29uc3Qge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUoKTsKCiAgQG92ZXJyaWRlCiAgTGlzdDxPYmplY3Q+IGdldCBwcm9wcyA9PiBbXTsKfQo=\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_cubit }}\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOmZyZWV6ZWRfYW5ub3RhdGlvbi9mcmVlemVkX2Fubm90YXRpb24uZGFydCc7CmltcG9ydCAncGFja2FnZTpyZXBsYXlfYmxvYy9yZXBsYXlfYmxvYy5kYXJ0JzsKCnBhcnQgJ3t7bmFtZS5zbmFrZUNhc2UoKX19X3N0YXRlLmRhcnQnOwpwYXJ0ICd7e25hbWUuc25ha2VDYXNlKCl9fV9jdWJpdC5mcmVlemVkLmRhcnQnOwoKY2xhc3Mge3tuYW1lLnBhc2NhbENhc2UoKX19Q3ViaXQgZXh0ZW5kcyBSZXBsYXlDdWJpdDx7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZT4gewogIHt7bmFtZS5wYXNjYWxDYXNlKCl9fUN1Yml0KCkgOiBzdXBlcihjb25zdCB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZS5pbml0aWFsKCkpOwp9Cg==\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"{{~ freezed_state }}\",\n      \"data\":\n          \"cGFydCBvZiAne3tuYW1lLnNuYWtlQ2FzZSgpfX1fY3ViaXQuZGFydCc7CgpAZnJlZXplZApjbGFzcyB7e25hbWUucGFzY2FsQ2FzZSgpfX1TdGF0ZSB3aXRoIF8ke3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUgewogIGNvbnN0IGZhY3Rvcnkge3tuYW1lLnBhc2NhbENhc2UoKX19U3RhdGUuaW5pdGlhbCgpID0gX0luaXRpYWw7Cn0K\",\n      \"type\": \"text\",\n    },\n  ],\n  \"hooks\": [\n    {\n      \"path\": \"pre_gen.dart\",\n      \"data\":\n          \"aW1wb3J0ICdwYWNrYWdlOm1hc29uL21hc29uLmRhcnQnOwoKRnV0dXJlPHZvaWQ+IHJ1bihIb29rQ29udGV4dCBjb250ZXh0KSBhc3luYyB7CiAgZmluYWwgc3R5bGUgPSBjb250ZXh0LnZhcnNbJ3N0eWxlJ107CiAgY29udGV4dC52YXJzID0gewogICAgLi4uY29udGV4dC52YXJzLAogICAgJ3VzZV9iYXNpYyc6IHN0eWxlID09ICdiYXNpYycsCiAgICAndXNlX2VxdWF0YWJsZSc6IHN0eWxlID09ICdlcXVhdGFibGUnLAogICAgJ3VzZV9mcmVlemVkJzogc3R5bGUgPT0gJ2ZyZWV6ZWQnLAogIH07Cn0K\",\n      \"type\": \"text\",\n    },\n    {\n      \"path\": \"pubspec.yaml\",\n      \"data\":\n          \"bmFtZTogcmVwbGF5X2N1Yml0X2hvb2tzCgplbnZpcm9ubWVudDoKICBzZGs6IF4zLjUuNAoKZGVwZW5kZW5jaWVzOgogIG1hc29uOiBeMC4xLjAK\",\n      \"type\": \"text\",\n    },\n  ],\n  \"name\": \"replay_cubit\",\n  \"description\":\n      \"Generate a new ReplayCubit in Dart. Built for the bloc state management library.\",\n  \"version\": \"0.3.0\",\n  \"environment\": {\"mason\": \"^0.1.0\"},\n  \"repository\":\n      \"https://github.com/felangel/bloc/tree/master/bricks/replay_cubit\",\n  \"readme\": {\n    \"path\": \"README.md\",\n    \"data\":\n        \"PHAgYWxpZ249ImNlbnRlciI+CjxpbWcgc3R5bGU9ImhlaWdodDoxMDBweCIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZmVsYW5nZWwvYmxvYy9tYXN0ZXIvYXNzZXRzL2xvZ29zL3JlcGxheV9jdWJpdF9kYXJrLnBuZyIgYWx0PSJSZXBsYXkgQ3ViaXQiIC8+CjwvcD4KCjxwIGFsaWduPSJjZW50ZXIiPgo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvYmxvYy9hY3Rpb25zIj48aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2Mvd29ya2Zsb3dzL2J1aWxkL2JhZGdlLnN2ZyIgYWx0PSJidWlsZCI+PC9hPgo8YSBocmVmPSJodHRwczovL2NvZGVjb3YuaW8vZ2gvZmVsYW5nZWwvYmxvYyI+PGltZyBzcmM9Imh0dHBzOi8vY29kZWNvdi5pby9naC9mZWxhbmdlbC9CbG9jL2JyYW5jaC9tYXN0ZXIvZ3JhcGgvYmFkZ2Uuc3ZnIiBhbHQ9ImNvZGVjb3YiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2dpdGh1Yi9zdGFycy9mZWxhbmdlbC9ibG9jLnN2Zz9zdHlsZT1mbGF0JmxvZ289Z2l0aHViJmNvbG9yQj1kZWVwcGluayZsYWJlbD1zdGFycyIgYWx0PSJTdGFyIG9uIEdpdGh1YiI+PC9hPgo8YSBocmVmPSJodHRwczovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL01JVCI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2UvbGljZW5zZS1NSVQtcHVycGxlLnN2ZyIgYWx0PSJMaWNlbnNlOiBNSVQiPjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9kaXNjb3JkLmdnL2Jsb2MiPjxpbWcgc3JjPSJodHRwczovL2ltZy5zaGllbGRzLmlvL2Rpc2NvcmQvNjQ5NzA4Nzc4NjMxMjAwNzc4LnN2Zz9sb2dvPWRpc2NvcmQmY29sb3I9Ymx1ZSIgYWx0PSJEaXNjb3JkIj48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9ibG9jIj48aW1nIHNyYz0iaHR0cHM6Ly90aW55dXJsLmNvbS9ibG9jLWxpYnJhcnkiIGFsdD0iQmxvYyBMaWJyYXJ5Ij48L2E+CjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9mZWxhbmdlbC9tYXNvbiI+PGltZyBzcmM9Imh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vZW5kcG9pbnQ/dXJsPWh0dHBzJTNBJTJGJTJGdGlueXVybC5jb20lMkZtYXNvbi1iYWRnZSIgYWx0PSJQb3dlcmVkIGJ5IE1hc29uIj48L2E+CjwvcD4KCkdlbmVyYXRlIGEgbmV3IFJlcGxheUN1Yml0IGluIFtEYXJ0XVsxXS4gQnVpbHQgZm9yIHRoZSBbYmxvYyBzdGF0ZSBtYW5hZ2VtZW50IGxpYnJhcnldWzJdLgoKIyMgVXNhZ2Ug8J+agAoKYGBgc2gKbWFzb24gbWFrZSByZXBsYXlfY3ViaXQgLS1uYW1lIGNvdW50ZXIgLS1zdHlsZSBiYXNpYwpgYGAKCiMjIFZhcmlhYmxlcyDinKgKCnwgVmFyaWFibGUgfCBEZXNjcmlwdGlvbiAgICAgICAgICAgICAgICAgIHwgRGVmYXVsdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBUeXBlICAgICB8CnwgLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLSB8CnwgYG5hbWVgICAgfCBUaGUgbmFtZSBvZiB0aGUgY3ViaXQgY2xhc3MgIHwgYGNvdW50ZXJgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBgc3RyaW5nYCB8CnwgYHN0eWxlYCAgfCBUaGUgc3R5bGUgb2YgY3ViaXQgZ2VuZXJhdGVkIHwgYGJhc2ljIChiYXNpYywgZXF1YXRhYmxlLCBmcmVlemVkKWAgfCBgZW51bWAgICB8CgojIyBPdXRwdXQg8J+TpgoKYGBgc2gK4pSc4pSA4pSAIGNvdW50ZXJfY3ViaXQuZGFydArilJTilIDilIAgY291bnRlcl9zdGF0ZS5kYXJ0CmBgYAoKWzFdOiBodHRwczovL2RhcnQuZGV2ClsyXTogaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL2Jsb2MK\",\n    \"type\": \"text\",\n  },\n  \"changelog\": {\n    \"path\": \"CHANGELOG.md\",\n    \"data\":\n        \"IyAwLjMuMAoKLSBjaG9yZShkZXBzKTogdXBncmFkZSB0byBgbWFzb24gXjAuMS4wYAotIGNob3JlKGRlcHMpOiB1cGdyYWRlIGhvb2tzIHRvIGBkYXJ0IF4zLjUuNGAKCiMgMC4yLjEKCi0gY2hvcmU6IHVwZGF0ZSBjb3B5cmlnaHQgeWVhcgotIGNob3JlOiB1cGRhdGUgbG9nbyBpbWFnZSByZWZzCgojIDAuMi4wCgotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZXF1YXRhYmxlYAotIGZlYXQ6IGFkZCBzdXBwb3J0IGZvciBgZnJlZXplZGAKCiMgMC4xLjMKCi0gZml4OiBwYXJ0IGFuZCBpbXBvcnRzCgojIDAuMS4yCgotIGRvY3M6IGFkZCBiYWRnZXMgdG8gUkVBRE1FCi0gZG9jczogdXNlIGRhcmsgbG9nbyB2YXJpYW50CgojIDAuMS4xCgotIGRvY3M6IG1pbm9yIFJFQURNRSB1cGRhdGUKCiMgMC4xLjAKCi0gZmVhdDogaW5pdGlhbCByZWxlYXNlIHdpdGggc3VwcG9ydCBmb3IgYmFzaWMgcmVwbGF5IGN1Yml0IGdlbmVyYXRpb24K\",\n    \"type\": \"text\",\n  },\n  \"license\": {\n    \"path\": \"LICENSE\",\n    \"data\":\n        \"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNCBGZWxpeCBBbmdlbG92CgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5Cm9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsCmluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMKdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbApjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMKZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IKSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksCkZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSCkxJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sCk9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFClNPRlRXQVJFLgo=\",\n    \"type\": \"text\",\n  },\n  \"vars\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the cubit class.\",\n      \"default\": \"counter\",\n      \"prompt\": \"Please enter the cubit name.\",\n    },\n    \"style\": {\n      \"type\": \"enum\",\n      \"description\": \"The style of cubit generated.\",\n      \"default\": \"basic\",\n      \"prompt\": \"What is the cubit style?\",\n      \"values\": [\"basic\", \"equatable\", \"freezed\"],\n    },\n  },\n});\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/commands/new/new_command.dart",
    "content": "import 'dart:io';\n\nimport 'package:args/args.dart';\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/bundles.dart';\nimport 'package:mason/mason.dart';\n\n/// Signature for [MasonGenerator.fromBundle].\ntypedef MasonGeneratorFromBundle = Future<MasonGenerator> Function(MasonBundle);\n\n/// Signature for [MasonGenerator.fromBrick].\ntypedef MasonGeneratorFromBrick = Future<MasonGenerator> Function(Brick);\n\n/// {@template new_command}\n/// The `bloc new <template>` command generates code from various templates.\n/// {@endtemplate}\nclass NewCommand extends Command<int> {\n  /// {@macro new_command}\n  NewCommand({required Logger logger}) {\n    for (final bundle in bundles) {\n      addSubcommand(GeneratorCommand(bundle: bundle, logger: logger));\n    }\n  }\n\n  @override\n  String get summary => '$invocation\\n$description';\n\n  @override\n  String get description => 'Generate new bloc components.';\n\n  @override\n  String get name => 'new';\n}\n\n/// {@template generator_subcommand}\n/// A generic command for generating code from a brick or bundle.\n/// {@endtemplate}\nclass GeneratorCommand extends Command<int> {\n  /// {@macro generator_command}\n  GeneratorCommand({\n    required this.logger,\n    required this.bundle,\n    MasonGeneratorFromBrick? generatorFromBrick,\n    MasonGeneratorFromBundle? generatorFromBundle,\n  }) : _generatorFromBrick = generatorFromBrick ?? MasonGenerator.fromBrick,\n       _generatorFromBundle = generatorFromBundle ?? MasonGenerator.fromBundle {\n    argParser\n      ..addOptionsForBundle(bundle)\n      ..addOption(\n        'output-directory',\n        abbr: 'o',\n        help: 'The desired output directory.',\n      );\n  }\n\n  /// The logger user to notify the user of the command's progress.\n  final Logger logger;\n\n  final MasonGeneratorFromBundle _generatorFromBundle;\n  final MasonGeneratorFromBrick _generatorFromBrick;\n\n  /// The desired output [Directory].\n  /// Specified via --output-directory and defaults to `.`.\n  Directory get outputDirectory {\n    return Directory(argResults?['output-directory'] as String? ?? '.');\n  }\n\n  /// The bundle used for generation.\n  final MasonBundle bundle;\n\n  @override\n  String get name => bundle.name;\n\n  @override\n  String get description => bundle.description;\n\n  Map<String, dynamic> get _vars => {\n    for (final entry in bundle.vars.entries) entry.key: argResults![entry.key],\n  };\n\n  @override\n  Future<int> run() async {\n    final generator = await _buildGenerator();\n    final generateProgress = logger.progress('Generating');\n    final target = DirectoryGeneratorTarget(outputDirectory);\n    var vars = _vars;\n    await generator.hooks.preGen(vars: vars, onVarsChanged: (v) => vars = v);\n    final files = await generator.generate(target, vars: vars, logger: logger);\n    generateProgress.complete('Generated ${files.length} file(s)');\n    logger.flush((message) => logger.info(darkGray.wrap(message)));\n\n    return ExitCode.success.code;\n  }\n\n  Future<MasonGenerator> _buildGenerator() async {\n    try {\n      final brick = Brick.version(\n        name: bundle.name,\n        version: '^${bundle.version}',\n      );\n      logger.detail(\n        '''Building generator from brick: ${brick.name} ${brick.location.version}''',\n      );\n      return await _generatorFromBrick(brick);\n    } on Exception catch (error) {\n      logger.detail('Building generator from brick failed: $error');\n    }\n    logger.detail(\n      '''Building generator from bundle ${bundle.name} ${bundle.version}''',\n    );\n    return _generatorFromBundle(bundle);\n  }\n}\n\nextension on ArgParser {\n  void addOptionsForBundle(MasonBundle bundle) {\n    for (final entry in bundle.vars.entries) {\n      final name = entry.key;\n      final properties = entry.value;\n      final type = properties.type;\n      switch (type) {\n        case BrickVariableType.array:\n        case BrickVariableType.list:\n          addMultiOption(\n            name,\n            help: properties.description,\n            allowed: properties.values,\n            defaultsTo: properties.defaultValues as Iterable<String>?,\n          );\n        case BrickVariableType.enumeration:\n          addOption(\n            name,\n            help: properties.description,\n            mandatory: properties.defaultValue == null,\n            allowed: properties.values,\n            defaultsTo: properties.defaultValue as String?,\n          );\n        case BrickVariableType.number:\n        case BrickVariableType.string:\n          addOption(\n            name,\n            help: properties.description,\n            mandatory: properties.defaultValue == null,\n            defaultsTo: properties.defaultValue?.toString(),\n          );\n        case BrickVariableType.boolean:\n          addFlag(\n            name,\n            help: properties.description,\n            defaultsTo: properties.defaultValue as bool?,\n          );\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/lsp/language_server.dart",
    "content": "import 'dart:io';\n\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_tools/src/lsp/text_documents.dart';\nimport 'package:lsp_server_ce/lsp_server_ce.dart' as lsp;\nimport 'package:path/path.dart' as p;\n\n/// {@template language_server}\n/// The bloc language server which uses the language server protocol\n/// to report diagnostics.\n/// {@endtemplate}\nclass LanguageServer {\n  /// {@macro language_server}\n  LanguageServer({lsp.Connection? connection, Linter? linter})\n    : _connection = connection ?? lsp.Connection(stdin, stdout),\n      _linter = linter ?? const Linter();\n\n  final lsp.Connection _connection;\n  final Linter _linter;\n\n  Uri? _root;\n\n  void _reportDiagnostics({required Uri uri, String? content}) {\n    final diagnostics = _linter.analyze(uri: uri, content: content);\n    for (final entry in diagnostics.entries) {\n      _connection.sendDiagnostics(\n        lsp.PublishDiagnosticsParams(\n          diagnostics: entry.value.map((d) => d.toLsp()).toList(),\n          uri: p.toUri(entry.key),\n        ),\n      );\n    }\n  }\n\n  /// Starts listening to the underlying connection.\n  /// Returns a [Future] that will complete when the connection is closed or\n  /// when it has an error. This method should not be called multiple times.\n  Future<void> listen() async {\n    _connection.onInitialize((params) async {\n      _root = params.rootUri;\n      return lsp.InitializeResult(\n        capabilities: lsp.ServerCapabilities(\n          textDocumentSync: const lsp.Either2.t1(\n            lsp.TextDocumentSyncKind.Incremental,\n          ),\n        ),\n      );\n    });\n\n    TextDocuments(\n      _connection,\n      onDidChangeContent: (params) async {\n        _reportDiagnostics(\n          uri: params.document.uri,\n          content: params.document.getText(),\n        );\n      },\n      onDidSave: (params) async {\n        if (p.basename(params.document.uri.path) == 'analysis_options.yaml') {\n          _reportDiagnostics(\n            uri: File(p.fromUri(params.document.uri)).parent.uri,\n          );\n        }\n      },\n    );\n\n    _connection\n      ..onInitialized((params) async {\n        if (_root != null) _reportDiagnostics(uri: _root!);\n      })\n      ..onExit(_connection.close);\n\n    await _connection.listen();\n  }\n}\n\nextension on Diagnostic {\n  lsp.Diagnostic toLsp() {\n    return lsp.Diagnostic(\n      message: message,\n      code: code,\n      source: source,\n      codeDescription: lsp.CodeDescription(href: Uri.parse(description)),\n      severity: lsp.DiagnosticSeverity.fromJson(severity.index + 1),\n      range: lsp.Range(\n        start: lsp.Position(\n          character: range.start.character,\n          line: range.start.line,\n        ),\n        end: lsp.Position(character: range.end.character, line: range.end.line),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/lsp/text_document.dart",
    "content": "import 'dart:math';\n\nimport 'package:lsp_server_ce/lsp_server_ce.dart';\n\n/// The newline symbol (\\n).\nconst lineFeed = 10;\n\n/// The carriage return symbol (\\r).\nconst carriageReturn = 13;\n\n/// {@template text_document}\n/// A simplified, Dart representation of VSCode's Language Server\n/// [TextDocument](https://github.com/microsoft/vscode-languageserver-node/blob/main/textDocument/src/main.ts).\n/// {@endtemplate}\nclass TextDocument {\n  /// {@macro text_document}\n  TextDocument(this._uri, this._languageId, this._version, this._content);\n\n  final Uri _uri;\n  final String _languageId;\n  int _version;\n  String _content;\n  List<int>? _lineOffsets;\n\n  /// The associated URI for this document. Most documents have the file scheme,\n  /// indicating that they represent files on disk. However, some documents may\n  /// have other schemes indicating that they are not available on disk.\n  Uri get uri => _uri;\n\n  /// The identifier of the language associated with this document.\n  String get languageId => _languageId;\n\n  /// The version number of this document (it will increase after each change,\n  /// including undo/redo).\n  int get version => _version;\n\n  /// The number of lines in this document.\n  int get lineCount => _getLineOffsets().length;\n\n  /// Get the text of this document. Provide a [Range] to get a substring.\n  String getText({Range? range}) {\n    if (range != null) {\n      final start = offsetAt(range.start);\n      final end = offsetAt(range.end);\n      return _content.substring(start, end);\n    }\n    return _content;\n  }\n\n  /// Convert a [Position] to a zero-based offset.\n  int offsetAt(Position position) {\n    final lineOffsets = _getLineOffsets();\n    if (position.line >= lineOffsets.length) {\n      return _content.length;\n    } else if (position.line < 0) {\n      return 0;\n    }\n\n    final lineOffset = lineOffsets[position.line];\n    if (position.character <= 0) {\n      return lineOffset;\n    }\n\n    final nextLineOffset =\n        (position.line + 1 < lineOffsets.length)\n            ? lineOffsets[position.line + 1]\n            : _content.length;\n    final offset = min(lineOffset + position.character, nextLineOffset);\n\n    return _ensureBeforeEndOfLine(offset: offset, lineOffset: lineOffset);\n  }\n\n  /// Converts a zero-based offset to a [Position].\n  Position positionAt(int offset) {\n    // ignore: parameter_assignments\n    offset = max(min(offset, _content.length), 0);\n    final lineOffsets = _getLineOffsets();\n    var low = 0;\n    var high = lineOffsets.length;\n    if (high == 0) return Position(character: offset, line: 0);\n\n    while (low < high) {\n      final mid = ((low + high) / 2).floor();\n      if (lineOffsets[mid] > offset) {\n        high = mid;\n      } else {\n        low = mid + 1;\n      }\n    }\n\n    final line = low - 1;\n    // ignore: parameter_assignments\n    offset = _ensureBeforeEndOfLine(\n      offset: offset,\n      lineOffset: lineOffsets[line],\n    );\n\n    return Position(character: offset - lineOffsets[line], line: line);\n  }\n\n  /// Updates this text document by modifying its content.\n  void update(List<TextDocumentContentChangeEvent> changes, int version) {\n    _version = version;\n    for (final c in changes) {\n      final change = c.map((v) => v, (v) => v);\n      if (change is TextDocumentContentChangeEvent1) {\n        // Incremental sync.\n        final range = getWellformedRange(change.range);\n        final text = change.text;\n\n        final startOffset = offsetAt(range.start);\n        final endOffset = offsetAt(range.end);\n\n        // Update content.\n        _content =\n            _content.substring(0, startOffset) +\n            text +\n            _content.substring(endOffset, _content.length);\n\n        // Update offsets without recomputing for the whole document.\n        final startLine = max(range.start.line, 0);\n        final endLine = max(range.end.line, 0);\n        final lineOffsets = _lineOffsets!;\n        final addedLineOffsets = _computeLineOffsets(\n          text,\n          isAtLineStart: false,\n          textOffset: startOffset,\n        );\n\n        if (endLine - startLine == addedLineOffsets.length) {\n          for (var i = 0, len = addedLineOffsets.length; i < len; i++) {\n            lineOffsets[i + startLine + 1] = addedLineOffsets[i];\n          }\n        } else {\n          // Avoid going outside the range on weird range inputs.\n          lineOffsets.replaceRange(\n            min(startLine + 1, lineOffsets.length),\n            min(endLine + 1, lineOffsets.length),\n            addedLineOffsets,\n          );\n        }\n\n        final diff = text.length - (endOffset - startOffset);\n        if (diff != 0) {\n          for (\n            var i = startLine + 1 + addedLineOffsets.length,\n                len = lineOffsets.length;\n            i < len;\n            i++\n          ) {\n            lineOffsets[i] = lineOffsets[i] + diff;\n          }\n        }\n      } else if (change is TextDocumentContentChangeEvent2) {\n        // Full sync.\n        _content = change.text;\n        _lineOffsets = null;\n      }\n    }\n  }\n\n  List<int> _getLineOffsets() {\n    _lineOffsets ??= _computeLineOffsets(_content, isAtLineStart: true);\n    return _lineOffsets!;\n  }\n\n  List<int> _computeLineOffsets(\n    String content, {\n    required bool isAtLineStart,\n    int textOffset = 0,\n  }) {\n    final result = isAtLineStart ? [textOffset] : <int>[];\n\n    for (var i = 0; i < content.length; i++) {\n      final char = content.codeUnitAt(i);\n      if (_isEndOfLine(char)) {\n        if (char == carriageReturn) {\n          final nextCharIsLineFeed =\n              i + 1 < content.length && content.codeUnitAt(i + 1) == lineFeed;\n          if (nextCharIsLineFeed) {\n            i++;\n          }\n        }\n        result.add(textOffset + i + 1);\n      }\n    }\n\n    return result;\n  }\n\n  bool _isEndOfLine(int char) {\n    return char == lineFeed || char == carriageReturn;\n  }\n\n  int _ensureBeforeEndOfLine({required int offset, required int lineOffset}) {\n    while (offset > lineOffset &&\n        _isEndOfLine(_content.codeUnitAt(offset - 1))) {\n      offset--;\n    }\n    return offset;\n  }\n\n  /// Returns a wellformed range from the provided [range].\n  Range getWellformedRange(Range range) {\n    final start = range.start;\n    final end = range.end;\n    final isStartLineAfterEndLine = start.line > end.line;\n    final isStartCharAfterEndChar =\n        start.line == end.line && start.character > end.character;\n    final shouldSwap = isStartLineAfterEndLine || isStartCharAfterEndChar;\n    if (shouldSwap) return Range(start: end, end: start);\n    return range;\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/lsp/text_documents.dart",
    "content": "// ignore_for_file: public_member_api_docs\n\nimport 'package:bloc_tools/src/lsp/text_document.dart';\nimport 'package:lsp_server_ce/lsp_server_ce.dart';\n\nclass TextDocumentChangeEvent {\n  const TextDocumentChangeEvent(this.document);\n  final TextDocument document;\n}\n\n/// A simplified, Dart representation of VSCode's Language Server\n/// [TextDocuments](https://github.com/microsoft/vscode-languageserver-node/blob/main/server/src/common/textDocuments.ts)\nclass TextDocuments {\n  TextDocuments(\n    Connection connection, {\n    Future<void> Function(TextDocumentChangeEvent)? onDidChangeContent,\n    Future<void> Function(TextDocumentChangeEvent)? onDidSave,\n  }) {\n    connection\n      ..onDidOpenTextDocument((event) async {\n        final document = TextDocument(\n          event.textDocument.uri,\n          event.textDocument.languageId,\n          event.textDocument.version,\n          event.textDocument.text,\n        );\n        _syncedDocuments[document.uri] = document;\n\n        if (onDidChangeContent != null) {\n          await onDidChangeContent(TextDocumentChangeEvent(document));\n        }\n      })\n      ..onDidChangeTextDocument((event) async {\n        final document = event.textDocument;\n        final changes = event.contentChanges;\n        if (changes.isEmpty) return;\n\n        final version = document.version;\n        final syncedDocument = get(document.uri);\n        if (syncedDocument == null) return;\n\n        syncedDocument.update(changes, version);\n\n        if (onDidChangeContent != null) {\n          await onDidChangeContent(TextDocumentChangeEvent(syncedDocument));\n        }\n      })\n      ..onDidSaveTextDocument((event) async {\n        final document = _syncedDocuments[event.textDocument.uri];\n        if (document != null && onDidSave != null) {\n          await onDidSave(TextDocumentChangeEvent(document));\n        }\n      });\n  }\n\n  final Map<Uri, TextDocument> _syncedDocuments = {};\n\n  /// Get a synced [TextDocument] for the provided [uri].\n  /// Returns `null` is none exists.\n  TextDocument? get(Uri uri) => _syncedDocuments[uri];\n}\n"
  },
  {
    "path": "packages/bloc_tools/lib/src/version.dart",
    "content": "// Generated code. Do not modify.\nconst packageVersion = '0.1.0-dev.23';\n"
  },
  {
    "path": "packages/bloc_tools/pubspec.yaml",
    "content": "name: bloc_tools\ndescription: Tools for building applications using the bloc state management library.\nversion: 0.1.0-dev.23\nrepository: https://github.com/felangel/bloc/tree/master/packages/bloc_tools\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://github.com/felangel/bloc\ndocumentation: https://bloclibrary.dev\ntopics: [bloc, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=3.7.0 <4.0.0\"\n\ndependencies:\n  args: ^2.1.0\n  bloc_lint: ^0.4.0\n  lsp_server_ce: ^0.4.0\n  mason: ^0.1.0\n  path: ^1.9.1\n  pub_updater: ^0.5.0\n\ndev_dependencies:\n  build_runner: ^2.0.0\n  build_verify: ^3.0.0\n  build_version: ^2.0.0\n  mocktail: ^1.0.0\n  test: ^1.17.0\n\nexecutables:\n  bloc:\n\nscreenshots:\n  - description: The bloc tools package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/bloc_tools/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc_lint:\n    path: ../bloc_lint\n"
  },
  {
    "path": "packages/bloc_tools/test/ensure_build_test.dart",
    "content": "@Tags(['pull-request-only'])\nlibrary ensure_build_test;\n\nimport 'package:build_verify/build_verify.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  test('ensure_build', () {\n    expectBuildClean(packageRelativeDirectory: 'packages/bloc_tools');\n  });\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/command_runner_test.dart",
    "content": "// ignore_for_file: no_adjacent_strings_in_list\nimport 'dart:async';\nimport 'dart:io';\n\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_tools/src/command_runner.dart';\nimport 'package:bloc_tools/src/lsp/language_server.dart';\nimport 'package:bloc_tools/src/version.dart';\nimport 'package:mason/mason.dart'\n    show ExitCode, Logger, Progress, lightCyan, lightYellow;\nimport 'package:mocktail/mocktail.dart';\nimport 'package:pub_updater/pub_updater.dart';\nimport 'package:test/test.dart';\n\nclass _MockLogger extends Mock implements Logger {}\n\nclass _MockProgress extends Mock implements Progress {}\n\nclass _MockPubUpdater extends Mock implements PubUpdater {}\n\nclass _MockLanguageServer extends Mock implements LanguageServer {}\n\nconst expectedUsage = [\n  'Command Line Tools for the Bloc Library.\\n'\n      '\\n'\n      'Usage: bloc <command> [arguments]\\n'\n      '\\n'\n      'Global options:\\n'\n      '-h, --help       Print this usage information.\\n'\n      '    --version    Print the current version.\\n'\n      '\\n'\n      'Available commands:\\n'\n      '  lint   bloc lint [arguments]\\n'\n      '         Lint Dart source code.\\n'\n      '  new    bloc new <subcommand> [arguments]\\n'\n      '         Generate new bloc components.\\n'\n      '\\n'\n      'Run \"bloc help <command>\" for more information about a command.',\n];\n\nfinal updatePrompt = '''\n+------------------------------------------------------------------------------------+\n|                                                                                    |\n|                    ${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \\u2192 ${lightCyan.wrap(latestVersion)}                     |\n|  ${lightYellow.wrap('Changelog:')} ${lightCyan.wrap('https://github.com/felangel/bloc/releases/tag/$packageName-v$latestVersion')}  |\n|                                                                                    |\n+------------------------------------------------------------------------------------+\n''';\n\nconst latestVersion = '0.0.0';\n\nvoid main() {\n  group('BlocToolsCommandRunner', () {\n    late List<String> printLogs;\n    late Logger logger;\n    late PubUpdater pubUpdater;\n    late LanguageServer languageServer;\n    late BlocToolsCommandRunner commandRunner;\n\n    void Function() overridePrint(void Function() fn) {\n      return () {\n        final spec = ZoneSpecification(\n          print: (_, _, _, String msg) {\n            printLogs.add(msg);\n          },\n        );\n        return Zone.current.fork(specification: spec).run<void>(fn);\n      };\n    }\n\n    setUp(() {\n      printLogs = [];\n      logger = _MockLogger();\n      pubUpdater = _MockPubUpdater();\n      languageServer = _MockLanguageServer();\n\n      when(\n        () => pubUpdater.getLatestVersion(any()),\n      ).thenAnswer((_) async => packageVersion);\n      when(() => pubUpdater.update(packageName: packageName)).thenAnswer(\n        (_) async => ProcessResult(0, ExitCode.success.code, null, null),\n      );\n      when(languageServer.listen).thenAnswer((_) async {});\n\n      commandRunner = BlocToolsCommandRunner(\n        logger: logger,\n        pubUpdater: pubUpdater,\n        languageServerBuilder: () => languageServer,\n      );\n    });\n\n    test('can be instantiated without an explicit logger instance', () {\n      final commandRunner = BlocToolsCommandRunner();\n      expect(commandRunner, isNotNull);\n    });\n\n    group('run', () {\n      test('prompts for update when newer version exists', () async {\n        when(\n          () => pubUpdater.getLatestVersion(any()),\n        ).thenAnswer((_) async => latestVersion);\n\n        when(() => logger.confirm(any())).thenReturn(false);\n\n        final result = await commandRunner.run(['--version']);\n        expect(result, equals(ExitCode.success.code));\n        verify(() => logger.info(updatePrompt)).called(1);\n        verify(() => logger.confirm('Would you like to update?')).called(1);\n      });\n\n      test('skips update check when running language-server', () async {\n        when(\n          () => pubUpdater.getLatestVersion(any()),\n        ).thenAnswer((_) async => latestVersion);\n\n        when(() => logger.confirm(any())).thenReturn(false);\n\n        final result = await commandRunner.run(['language-server']);\n        expect(result, equals(ExitCode.success.code));\n        verifyNever(() => logger.info(updatePrompt));\n        verifyNever(() => logger.confirm('Would you like to update?'));\n      });\n\n      test('handles pub update errors gracefully', () async {\n        when(\n          () => pubUpdater.getLatestVersion(any()),\n        ).thenThrow(Exception('oops'));\n\n        final result = await commandRunner.run(['--version']);\n        expect(result, equals(ExitCode.success.code));\n        verifyNever(() => logger.info(updatePrompt));\n      });\n\n      test('updates on \"y\" response when newer version exists', () async {\n        final progress = _MockProgress();\n        when(\n          () => pubUpdater.getLatestVersion(any()),\n        ).thenAnswer((_) async => latestVersion);\n\n        when(() => logger.confirm(any())).thenReturn(true);\n        when(() => logger.progress(any())).thenReturn(progress);\n\n        final result = await commandRunner.run(['--version']);\n        expect(result, equals(ExitCode.success.code));\n        verify(() => logger.progress('Updating to $latestVersion')).called(1);\n      });\n\n      test('handles FormatException', () async {\n        const exception = FormatException('oops!');\n        var isFirstInvocation = true;\n        when(() => logger.info(any())).thenAnswer((_) {\n          if (isFirstInvocation) {\n            isFirstInvocation = false;\n            throw exception;\n          }\n        });\n        final result = await commandRunner.run(['--version']);\n        expect(result, equals(ExitCode.usage.code));\n        verify(() => logger.err(exception.message)).called(1);\n        verify(() => logger.info(commandRunner.usage)).called(1);\n      });\n\n      test('handles UsageException', () async {\n        final exception = UsageException('oops!', commandRunner.usage);\n        var isFirstInvocation = true;\n        when(() => logger.info(any())).thenAnswer((_) {\n          if (isFirstInvocation) {\n            isFirstInvocation = false;\n            throw exception;\n          }\n        });\n        final result = await commandRunner.run(['--version']);\n        expect(result, equals(ExitCode.usage.code));\n        verify(() => logger.err(exception.message)).called(1);\n        verify(() => logger.info(commandRunner.usage)).called(1);\n      });\n\n      test(\n        'handles no command',\n        overridePrint(() async {\n          final result = await commandRunner.run([]);\n          expect(printLogs, equals(expectedUsage));\n          expect(result, equals(ExitCode.success.code));\n        }),\n      );\n\n      group('--help', () {\n        test(\n          'outputs usage',\n          overridePrint(() async {\n            final result = await commandRunner.run(['--help']);\n            expect(printLogs, equals(expectedUsage));\n            expect(result, equals(ExitCode.success.code));\n\n            printLogs.clear();\n\n            final resultAbbr = await commandRunner.run(['-h']);\n            expect(printLogs, equals(expectedUsage));\n            expect(resultAbbr, equals(ExitCode.success.code));\n          }),\n        );\n      });\n\n      group('--version', () {\n        test('outputs current version', () async {\n          final result = await commandRunner.run(['--version']);\n          expect(result, equals(ExitCode.success.code));\n          verify(() => logger.info(packageVersion)).called(1);\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/commands/language_server/language_server_command_test.dart",
    "content": "import 'package:args/command_runner.dart';\nimport 'package:bloc_tools/src/commands/language_server/language_server_command.dart';\nimport 'package:bloc_tools/src/lsp/language_server.dart';\nimport 'package:mason/mason.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nclass _MockLanguageServer extends Mock implements LanguageServer {}\n\nvoid main() {\n  group('bloc language-server', () {\n    late LanguageServer languageServer;\n    late LanguageServerCommand command;\n    late CommandRunner<int> runner;\n\n    setUp(() {\n      languageServer = _MockLanguageServer();\n      command = LanguageServerCommand(\n        languageServerBuilder: () => languageServer,\n      );\n      runner = _TestCommandRunner(command: command);\n    });\n\n    test('has correct metadata', () {\n      expect(command.name, equals('language-server'));\n      expect(command.description, equals('Start the bloc language server.'));\n      expect(command.hidden, isTrue);\n      expect(\n        command.summary,\n        equals(\n          ' language-server [arguments]\\n'\n          'Start the bloc language server.',\n        ),\n      );\n    });\n\n    test('runs correctly', () async {\n      when(() => languageServer.listen()).thenAnswer((_) async {});\n      await expectLater(\n        runner.run(['language-server']),\n        completion(equals(ExitCode.success.code)),\n      );\n    });\n  });\n}\n\nclass _TestCommandRunner extends CommandRunner<int> {\n  _TestCommandRunner({required Command<int> command}) : super('', '') {\n    addCommand(command);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/commands/lint/lint_command_test.dart",
    "content": "import 'dart:io';\n\nimport 'package:args/command_runner.dart';\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_tools/src/commands/lint/lint_command.dart';\nimport 'package:mason/mason.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:path/path.dart' as path;\nimport 'package:test/test.dart';\n\nclass _MockLinter extends Mock implements Linter {}\n\nclass _MockLogger extends Mock implements Logger {}\n\nvoid main() {\n  group('bloc lint', () {\n    late Linter linter;\n    late Logger logger;\n    late LintCommand command;\n    late CommandRunner<int> runner;\n\n    setUpAll(() {\n      registerFallbackValue(Uri());\n    });\n\n    setUp(() {\n      linter = _MockLinter();\n      logger = _MockLogger();\n      command = LintCommand(linter: linter, logger: logger);\n      runner = _TestCommandRunner(command: command);\n    });\n\n    test('has correct metadata', () {\n      expect(command.name, equals('lint'));\n      expect(command.description, equals('Lint Dart source code.'));\n      expect(\n        command.summary,\n        equals(\n          ' lint [arguments]\\n'\n          'Lint Dart source code.',\n        ),\n      );\n    });\n\n    test('exits with usage error when no files were specified', () async {\n      await expectLater(\n        runner.run(['lint']),\n        completion(equals(ExitCode.usage.code)),\n      );\n      verify(\n        () => logger.info(any(that: contains('No files specified.'))),\n      ).called(1);\n    });\n\n    test('exits with usage error when no diagnostics were reported', () async {\n      when(() => linter.analyze(uri: any(named: 'uri'))).thenAnswer((_) => {});\n      await expectLater(\n        runner.run(['lint', 'main.dart']),\n        completion(equals(ExitCode.usage.code)),\n      );\n      verify(\n        () => logger.info(any(that: contains('No files found.'))),\n      ).called(1);\n    });\n\n    test('canonicalizes paths before reporting diagnostics', () async {\n      when(() => linter.analyze(uri: any(named: 'uri'))).thenAnswer((_) => {});\n      await expectLater(\n        runner.run(['lint', '.']),\n        completion(equals(ExitCode.usage.code)),\n      );\n      verify(\n        () => linter.analyze(uri: Uri.parse(Directory.current.absolute.path)),\n      ).called(1);\n    });\n\n    test('exits with success when no issues are detected', () async {\n      final tempDir = Directory.systemTemp.createTempSync();\n      final file = File(path.join(tempDir.path, 'main.dart'))\n        ..writeAsStringSync('void main() {}');\n      when(() => linter.analyze(uri: any(named: 'uri'))).thenAnswer(\n        (invocation) => {(invocation.namedArguments[#uri] as Uri).path: []},\n      );\n      await expectLater(\n        runner.run(['lint', file.path]),\n        completion(equals(ExitCode.success.code)),\n      );\n      verify(\n        () => logger.info('''\n0 issues found\nAnalyzed 1 file'''),\n      ).called(1);\n    });\n\n    test('exits with error when multiple issues are detected', () async {\n      final tempDir = Directory.systemTemp.createTempSync();\n      final file = File(path.join(tempDir.path, 'main.dart'))\n        ..writeAsStringSync('void main() {}');\n      when(() => linter.analyze(uri: any(named: 'uri'))).thenAnswer(\n        (invocation) => {\n          (invocation.namedArguments[#uri] as Uri).path: [\n            const Diagnostic(\n              range: Range(\n                start: Position(line: 0, character: 0),\n                end: Position(line: 0, character: 4),\n              ),\n              source: 'error source',\n              message: 'error message',\n              description: 'error description',\n              code: 'error code',\n              severity: Severity.error,\n            ),\n            const Diagnostic(\n              range: Range(\n                start: Position(line: 1, character: 0),\n                end: Position(line: 1, character: 42),\n              ),\n              source: 'hint source',\n              message: 'hint message',\n              description: 'hint description',\n              code: 'hint code',\n              severity: Severity.hint,\n            ),\n            const Diagnostic(\n              range: Range(\n                start: Position(line: 2, character: 10),\n                end: Position(line: 2, character: 20),\n              ),\n              source: 'info source',\n              message: 'info message',\n              description: 'info description',\n              code: 'info code',\n              severity: Severity.info,\n            ),\n            const Diagnostic(\n              range: Range(\n                start: Position(line: 3, character: 30),\n                end: Position(line: 3, character: 78),\n              ),\n              source: 'warning source',\n              message: 'warning message',\n              description: 'warning description',\n              code: 'warning code',\n              severity: Severity.warning,\n            ),\n          ],\n        },\n      );\n      await expectLater(runner.run(['lint', file.path]), completion(equals(1)));\n      verify(\n        () => logger.info('''\n4 issues found\nAnalyzed 1 file'''),\n      ).called(1);\n    });\n  });\n}\n\nclass _TestCommandRunner extends CommandRunner<int> {\n  _TestCommandRunner({required Command<int> command}) : super('', '') {\n    addCommand(command);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/commands/new/new_test.dart",
    "content": "import 'package:args/command_runner.dart';\nimport 'package:bloc_tools/src/commands/commands.dart';\nimport 'package:bloc_tools/src/commands/new/bundles/bundles.dart' show bundles;\nimport 'package:mason/mason.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nclass _MockLogger extends Mock implements Logger {}\n\nclass _MockProgress extends Mock implements Progress {}\n\nclass _MockMasonBundle extends Mock implements MasonBundle {}\n\nclass _MockMasonGenerator extends Mock implements MasonGenerator {}\n\nclass _MockGeneratorHooks extends Mock implements GeneratorHooks {}\n\nclass _FakeGeneratorTarget extends Fake implements GeneratorTarget {}\n\nvoid main() {\n  group('bloc new', () {\n    late Logger logger;\n    late NewCommand command;\n\n    setUp(() {\n      logger = _MockLogger();\n      command = NewCommand(logger: logger);\n    });\n\n    test('has correct name, description, and subcommands', () {\n      expect(command.name, equals('new'));\n      expect(command.description, equals('Generate new bloc components.'));\n      expect(\n        command.subcommands,\n        isA<Map<String, Command<int>>>()\n            .having(\n              (commands) => commands.keys,\n              'names',\n              equals(bundles.map((b) => b.name)),\n            )\n            .having(\n              (commands) => commands.values.map((c) => c.description),\n              'descriptions',\n              equals(bundles.map((b) => b.description)),\n            ),\n      );\n    });\n  });\n\n  group(GeneratorCommand, () {\n    late MasonBundle bundle;\n    late MasonGenerator generator;\n    late GeneratorHooks hooks;\n    late Progress progress;\n    late Logger logger;\n    late GeneratorCommand command;\n\n    setUpAll(() {\n      registerFallbackValue(_FakeGeneratorTarget());\n    });\n\n    setUp(() {\n      bundle = _MockMasonBundle();\n      generator = _MockMasonGenerator();\n      hooks = _MockGeneratorHooks();\n      logger = _MockLogger();\n      progress = _MockProgress();\n\n      when(() => bundle.name).thenReturn('name');\n      when(() => bundle.description).thenReturn('description');\n      when(() => bundle.version).thenReturn('1.0.0+1');\n      when(() => bundle.vars).thenReturn({\n        'array': const BrickVariableProperties.array(\n          description: 'array description',\n          values: ['a', 'b', 'c'],\n          defaultValues: ['a'],\n        ),\n        'list': const BrickVariableProperties.list(\n          description: 'list description',\n          separator: ',',\n        ),\n        'enum': const BrickVariableProperties.enumeration(\n          description: 'enum description',\n          values: ['a', 'b', 'c'],\n          defaultValue: 'a',\n        ),\n        'number': const BrickVariableProperties.number(\n          description: 'number description',\n          defaultValue: 42,\n        ),\n        'string': const BrickVariableProperties.string(\n          description: 'string description',\n          defaultValue: 'hello',\n        ),\n        'bool': const BrickVariableProperties.boolean(\n          description: 'bool description',\n          defaultValue: true,\n        ),\n      });\n      when(() => logger.progress(any())).thenReturn(progress);\n      when(() => generator.hooks).thenReturn(hooks);\n      when(\n        () => generator.generate(\n          any(),\n          vars: any(named: 'vars'),\n          logger: any(named: 'logger'),\n        ),\n      ).thenAnswer((_) async => []);\n      when(\n        () => hooks.preGen(\n          vars: any(named: 'vars'),\n          onVarsChanged: any(named: 'onVarsChanged'),\n        ),\n      ).thenAnswer((_) async {});\n      when(\n        () => hooks.postGen(\n          vars: any(named: 'vars'),\n          onVarsChanged: any(named: 'onVarsChanged'),\n        ),\n      ).thenAnswer((_) async {});\n\n      command = GeneratorCommand(\n        logger: logger,\n        bundle: bundle,\n        generatorFromBrick: (_) async => generator,\n        generatorFromBundle: (_) async => generator,\n      );\n    });\n\n    group('--output-directory', () {\n      test('defaults to \".\"', () {\n        expect(command.outputDirectory.path, equals('.'));\n      });\n    });\n\n    group('run', () {\n      test('completes using hosted brick', () async {\n        var generatorFromBrickCallCount = 0;\n        final command = GeneratorCommand(\n          logger: logger,\n          bundle: bundle,\n          generatorFromBrick: (_) async {\n            generatorFromBrickCallCount++;\n            return generator;\n          },\n        );\n        final runner = _TestCommandRunner(command: command);\n        await expectLater(\n          runner.run([command.name]),\n          completion(equals(ExitCode.success.code)),\n        );\n        expect(generatorFromBrickCallCount, equals(1));\n      });\n\n      test('falls back to bundle', () async {\n        var generatorFromBrickCallCount = 0;\n        var generatorFromBundleCallCount = 0;\n        final command = GeneratorCommand(\n          logger: logger,\n          bundle: bundle,\n          generatorFromBrick: (_) async {\n            generatorFromBrickCallCount++;\n            throw Exception('oops');\n          },\n          generatorFromBundle: (_) async {\n            generatorFromBundleCallCount++;\n            return generator;\n          },\n        );\n        final runner = _TestCommandRunner(command: command);\n        await expectLater(\n          runner.run([command.name]),\n          completion(equals(ExitCode.success.code)),\n        );\n        expect(generatorFromBrickCallCount, equals(1));\n        expect(generatorFromBundleCallCount, equals(1));\n      });\n    });\n  });\n}\n\nclass _TestCommandRunner extends CommandRunner<int> {\n  _TestCommandRunner({required GeneratorCommand command}) : super('', '') {\n    addCommand(command);\n  }\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/lsp/language_server_test.dart",
    "content": "// ignore_for_file: avoid_private_typedef_functions\n\nimport 'dart:async';\n\nimport 'package:bloc_lint/bloc_lint.dart';\nimport 'package:bloc_tools/src/lsp/language_server.dart';\nimport 'package:lsp_server_ce/lsp_server_ce.dart'\n    hide Diagnostic, Position, Range;\nimport 'package:lsp_server_ce/lsp_server_ce.dart' as lsp;\nimport 'package:mocktail/mocktail.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nclass _MockLinter extends Mock implements Linter {}\n\nclass _MockConnection extends Mock implements Connection {}\n\nclass _FakePublishDiagnosticsParams extends Fake\n    implements PublishDiagnosticsParams {}\n\ntypedef _OnInitializeHandler =\n    Future<InitializeResult> Function(InitializeParams);\n\ntypedef _OnInitializedHandler = Future<dynamic> Function(InitializedParams);\n\ntypedef _OnDidOpenTextDocumentHandler =\n    Future<dynamic> Function(DidOpenTextDocumentParams);\n\ntypedef _OnDidChangeTextDocumentHandler =\n    Future<dynamic> Function(DidChangeTextDocumentParams);\n\ntypedef _OnDidSaveTextDocumentHandler =\n    Future<dynamic> Function(DidSaveTextDocumentParams);\n\nvoid main() {\n  group(LanguageServer, () {\n    final rootUri = Uri.parse('file:///c:/users/dash/main.dart');\n    late Connection connection;\n    late Linter linter;\n    late LanguageServer languageServer;\n\n    setUpAll(() {\n      registerFallbackValue(_FakePublishDiagnosticsParams());\n      registerFallbackValue(Uri());\n    });\n\n    setUp(() {\n      connection = _MockConnection();\n      linter = _MockLinter();\n      languageServer = LanguageServer(connection: connection, linter: linter);\n    });\n\n    test('can be instantiated with default args', () {\n      expect(LanguageServer.new, returnsNormally);\n    });\n\n    group('listen', () {\n      setUp(() {\n        when(() => connection.listen()).thenAnswer((_) async {});\n        when(() => connection.close()).thenAnswer((_) async {});\n\n        const diagnostic = Diagnostic(\n          range: Range(\n            start: Position(line: 1, character: 1),\n            end: Position(line: 1, character: 42),\n          ),\n          code: 'code',\n          description: 'https://flutter.dev',\n          message: 'message',\n          severity: Severity.warning,\n          source: 'source',\n          hint: 'hint',\n        );\n\n        when(\n          () => linter.analyze(\n            uri: any(named: 'uri'),\n            content: any(named: 'content'),\n          ),\n        ).thenAnswer((invocation) {\n          final uri = invocation.namedArguments[#uri] as Uri;\n          return <String, List<Diagnostic>>{\n            p.fromUri(uri): [diagnostic],\n          };\n        });\n      });\n\n      test('onInitialize returns correct result', () async {\n        unawaited(languageServer.listen());\n        final handler =\n            verify(() => connection.onInitialize(captureAny())).captured.single\n                as _OnInitializeHandler;\n\n        final params = InitializeParams(\n          capabilities: ClientCapabilities(),\n          rootUri: rootUri,\n        );\n\n        await expectLater(\n          handler(params),\n          completion(\n            isA<InitializeResult>().having(\n              (r) => r.capabilities,\n              'capabilites',\n              isA<ServerCapabilities>().having(\n                (c) => c.textDocumentSync,\n                'textDocumentSync',\n                equals(\n                  const Either2<\n                    TextDocumentSyncKind,\n                    TextDocumentSyncOptions\n                  >.t1(TextDocumentSyncKind.Incremental),\n                ),\n              ),\n            ),\n          ),\n        );\n      });\n\n      test('onInitialized does nothing when rootUri is null', () async {\n        unawaited(languageServer.listen());\n        final handler =\n            verify(() => connection.onInitialized(captureAny())).captured.single\n                as _OnInitializedHandler;\n        await expectLater(handler(InitializedParams()), completes);\n        verifyNever(() => connection.sendDiagnostics(any()));\n      });\n\n      test('onInitialized reports diagnostics for rootUri', () async {\n        unawaited(languageServer.listen());\n        final onInitializeHandler =\n            verify(() => connection.onInitialize(captureAny())).captured.single\n                as _OnInitializeHandler;\n\n        await expectLater(\n          onInitializeHandler(\n            InitializeParams(\n              capabilities: ClientCapabilities(),\n              rootUri: rootUri,\n            ),\n          ),\n          completes,\n        );\n\n        final onInitializedHandler =\n            verify(() => connection.onInitialized(captureAny())).captured.single\n                as _OnInitializedHandler;\n        await expectLater(onInitializedHandler(InitializedParams()), completes);\n        verify(\n          () => connection.sendDiagnostics(\n            any(\n              that: isA<PublishDiagnosticsParams>()\n                  .having((p) => p.uri, 'uri', p.toUri(p.fromUri(rootUri)))\n                  .having((p) => p.diagnostics, 'diagnostics', [\n                    lsp.Diagnostic(\n                      range: lsp.Range(\n                        start: lsp.Position(line: 1, character: 1),\n                        end: lsp.Position(line: 1, character: 42),\n                      ),\n                      code: 'code',\n                      codeDescription: lsp.CodeDescription(\n                        href: Uri.parse('https://flutter.dev'),\n                      ),\n                      message: 'message',\n                      severity: DiagnosticSeverity.Warning,\n                      source: 'source',\n                    ),\n                  ]),\n            ),\n          ),\n        ).called(1);\n      });\n\n      test('onDidChangeContent reports diagnostics', () async {\n        unawaited(languageServer.listen());\n        final onDidOpenTextDocumentHandler =\n            verify(\n                  () => connection.onDidOpenTextDocument(captureAny()),\n                ).captured.single\n                as _OnDidOpenTextDocumentHandler;\n        final didOpenTextDocumentParams = DidOpenTextDocumentParams(\n          textDocument: TextDocumentItem(\n            languageId: 'dart',\n            text: 'void main() {}',\n            uri: p.toUri(p.fromUri(rootUri)),\n            version: 1,\n          ),\n        );\n\n        await onDidOpenTextDocumentHandler(didOpenTextDocumentParams);\n\n        verify(\n          () => connection.sendDiagnostics(\n            any(\n              that: isA<PublishDiagnosticsParams>()\n                  .having(\n                    (p) => p.uri,\n                    'uri',\n                    didOpenTextDocumentParams.textDocument.uri,\n                  )\n                  .having((p) => p.diagnostics, 'diagnostics', [\n                    lsp.Diagnostic(\n                      range: lsp.Range(\n                        start: lsp.Position(line: 1, character: 1),\n                        end: lsp.Position(line: 1, character: 42),\n                      ),\n                      code: 'code',\n                      codeDescription: lsp.CodeDescription(\n                        href: Uri.parse('https://flutter.dev'),\n                      ),\n                      message: 'message',\n                      severity: DiagnosticSeverity.Warning,\n                      source: 'source',\n                    ),\n                  ]),\n            ),\n          ),\n        ).called(1);\n\n        final onDidChangeTextDocumentHandler =\n            verify(\n                  () => connection.onDidChangeTextDocument(captureAny()),\n                ).captured.single\n                as _OnDidChangeTextDocumentHandler;\n        final didChangeTextDocumentParams = DidChangeTextDocumentParams(\n          textDocument: VersionedTextDocumentIdentifier(\n            uri: p.toUri(p.fromUri(rootUri)),\n            version: 1,\n          ),\n          contentChanges: [\n            lsp.Either2<\n              TextDocumentContentChangeEvent1,\n              TextDocumentContentChangeEvent2\n            >.t2(\n              TextDocumentContentChangeEvent2(\n                text: 'void main() => print(\"hello\");',\n              ),\n            ),\n          ],\n        );\n\n        await onDidChangeTextDocumentHandler(didChangeTextDocumentParams);\n        verify(() => connection.sendDiagnostics(any())).called(1);\n      });\n\n      test(\n        'onDidSave does nothing when file is not analysis_options.yaml',\n        () async {\n          unawaited(languageServer.listen());\n          final onDidOpenTextDocumentHandler =\n              verify(\n                    () => connection.onDidOpenTextDocument(captureAny()),\n                  ).captured.single\n                  as _OnDidOpenTextDocumentHandler;\n          final didOpenParams = DidOpenTextDocumentParams(\n            textDocument: TextDocumentItem(\n              languageId: 'dart',\n              text: 'void main() {}',\n              uri: Uri.parse('file://main.dart'),\n              version: 1,\n            ),\n          );\n\n          await onDidOpenTextDocumentHandler(didOpenParams);\n          verify(() => connection.sendDiagnostics(any())).called(1);\n\n          final onDidSaveTextDocumentHandler =\n              verify(\n                    () => connection.onDidSaveTextDocument(captureAny()),\n                  ).captured.single\n                  as _OnDidSaveTextDocumentHandler;\n          final didSaveParams = DidSaveTextDocumentParams(\n            textDocument: TextDocumentIdentifier(\n              uri: didOpenParams.textDocument.uri,\n            ),\n          );\n\n          await onDidSaveTextDocumentHandler(didSaveParams);\n\n          verifyNever(() => connection.sendDiagnostics(any()));\n        },\n      );\n\n      test('onDidSave reports diagnostics in parent dir '\n          'when file is analysis_options.yaml', () async {\n        unawaited(languageServer.listen());\n        final onDidOpenTextDocumentHandler =\n            verify(\n                  () => connection.onDidOpenTextDocument(captureAny()),\n                ).captured.single\n                as _OnDidOpenTextDocumentHandler;\n        final didOpenParams = DidOpenTextDocumentParams(\n          textDocument: TextDocumentItem(\n            languageId: 'yaml',\n            text: 'include: \"package:bloc_lint/recommended.yaml\";',\n            uri: Uri.parse('file://foo/analysis_options.yaml'),\n            version: 1,\n          ),\n        );\n\n        await onDidOpenTextDocumentHandler(didOpenParams);\n        verify(() => connection.sendDiagnostics(any())).called(1);\n\n        final onDidSaveTextDocumentHandler =\n            verify(\n                  () => connection.onDidSaveTextDocument(captureAny()),\n                ).captured.single\n                as _OnDidSaveTextDocumentHandler;\n        final didSaveParams = DidSaveTextDocumentParams(\n          textDocument: TextDocumentIdentifier(\n            uri: didOpenParams.textDocument.uri,\n          ),\n        );\n\n        await onDidSaveTextDocumentHandler(didSaveParams);\n\n        verify(() => connection.sendDiagnostics(any())).called(1);\n      });\n\n      test('onExit closes the connection', () async {\n        unawaited(languageServer.listen());\n        final onExitHandler =\n            verify(() => connection.onExit(captureAny())).captured.single\n                as Future<dynamic> Function();\n        await expectLater(onExitHandler(), completes);\n        verify(() => connection.close()).called(1);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/bloc_tools/test/src/lsp/text_document_test.dart",
    "content": "// ignore_for_file: cascade_invocations\n\nimport 'package:bloc_tools/src/lsp/text_document.dart';\nimport 'package:lsp_server_ce/lsp_server_ce.dart' as lsp;\nimport 'package:test/test.dart';\n\nlsp.Position at({required int line, required int char}) {\n  return lsp.Position(character: char, line: line);\n}\n\nlsp.Position position(int line, int char) {\n  return at(line: line, char: char);\n}\n\nlsp.Range range(int startLine, int startChar, int endLine, int endChar) {\n  return lsp.Range(\n    start: at(line: startLine, char: startChar),\n    end: at(line: endLine, char: endChar),\n  );\n}\n\nTextDocument createDocument(String content) {\n  return TextDocument(Uri.parse('test://hello/world'), 'text', 0, content);\n}\n\nlsp.Either2<\n  lsp.TextDocumentContentChangeEvent1,\n  lsp.TextDocumentContentChangeEvent2\n>\nupdateFull(String text) {\n  return lsp.Either2.t2(lsp.TextDocumentContentChangeEvent2(text: text));\n}\n\nlsp.Either2<\n  lsp.TextDocumentContentChangeEvent1,\n  lsp.TextDocumentContentChangeEvent2\n>\nupdateIncremental(String text, lsp.Range range) {\n  return lsp.Either2.t1(\n    lsp.TextDocumentContentChangeEvent1(text: text, range: range),\n  );\n}\n\nlsp.Range forSubstring(TextDocument document, String substring) {\n  final i = document.getText().indexOf(substring);\n  final start = document.positionAt(i);\n  final end = document.positionAt(i + substring.length);\n  final range = lsp.Range(start: start, end: end);\n  return range;\n}\n\nlsp.Range afterSubstring(TextDocument document, String substring) {\n  final i = document.getText().indexOf(substring);\n  final pos = document.positionAt(i + substring.length);\n  return lsp.Range(start: pos, end: pos);\n}\n\nvoid main() {\n  group('lines, offsets and positions', () {\n    test('empty content', () {\n      final document = createDocument('');\n\n      expect(document.languageId, equals('text'));\n      expect(document.lineCount, equals(1));\n      expect(document.offsetAt(position(0, 0)), equals(0));\n\n      final pos = document.positionAt(0);\n      expect(pos.line, equals(0));\n      expect(pos.character, equals(0));\n    });\n\n    test('single line', () {\n      const content = 'Hello World';\n      final document = createDocument(content);\n      expect(document.lineCount, equals(1));\n\n      for (var i = 0; i < content.length; i++) {\n        expect(document.offsetAt(position(0, i)), equals(i));\n        final pos = document.positionAt(i);\n        expect(pos.line, equals(0));\n        expect(pos.character, equals(i));\n      }\n    });\n\n    test('multiple lines', () {\n      const content = 'abcde\\nfghij\\nklmno\\n';\n      final document = createDocument(content);\n      expect(document.lineCount, equals(4));\n\n      for (var i = 0; i < content.length; i++) {\n        final line = (i / 6).floor();\n        final char = i % 6;\n        expect(document.offsetAt(position(line, char)), equals(i));\n\n        final pos = document.positionAt(i);\n        expect(pos.line, equals(line));\n        expect(pos.character, equals(char));\n      }\n\n      // Out of bounds.\n      expect(document.offsetAt(position(3, 0)), content.length);\n      expect(document.offsetAt(position(3, 1)), content.length);\n\n      var pos = document.positionAt(18);\n      expect(pos.line, equals(3));\n      expect(pos.character, equals(0));\n\n      pos = document.positionAt(19);\n      expect(pos.line, equals(3));\n      expect(pos.character, equals(0));\n    });\n\n    test('starts with newline', () {\n      const content = '\\nABCDE';\n      final document = createDocument(content);\n      expect(document.lineCount, equals(2));\n    });\n\n    test('newline characters', () {\n      var document = createDocument('\\rABCDE');\n      expect(document.lineCount, equals(2));\n      document = createDocument('\\nABCDE');\n      expect(document.lineCount, equals(2));\n\n      document = createDocument('\\r\\nABCDE');\n      expect(document.lineCount, equals(2));\n\n      document = createDocument('\\n\\nABCDE');\n      expect(document.lineCount, equals(3));\n\n      document = createDocument('\\r\\rABCDE');\n      expect(document.lineCount, equals(3));\n\n      document = createDocument('\\n\\rABCDE');\n      expect(document.lineCount, equals(3));\n    });\n\n    test('getText', () {\n      const content = 'abcde\\nfghij\\nklmno';\n      final document = createDocument(content);\n      expect(document.getText(), equals(content));\n\n      expect(document.getText(range: range(0, 0, 0, 5)), equals('abcde'));\n      expect(document.getText(range: range(0, 4, 1, 1)), equals('e\\nf'));\n    });\n\n    test('invalid input at beginning of file', () {\n      final document = createDocument('asdf');\n      expect(document.offsetAt(position(-1, 0)), 0);\n      expect(document.offsetAt(position(0, -1)), 0);\n\n      final pos = document.positionAt(-1);\n      expect(pos.line, equals(0));\n      expect(pos.character, equals(0));\n    });\n\n    test('invalid input at end of file', () {\n      final document = createDocument('asdf');\n      expect(document.offsetAt(position(1, 1)), 4);\n\n      final pos = document.positionAt(8);\n      expect(pos.line, equals(0));\n      expect(pos.character, equals(4));\n    });\n\n    test('invalid input at beginning of line', () {\n      final document = createDocument('a\\ns\\nd\\r\\nf');\n      expect(document.offsetAt(position(0, -1)), 0);\n      expect(document.offsetAt(position(1, -1)), 2);\n      expect(document.offsetAt(position(2, -1)), 4);\n      expect(document.offsetAt(position(3, -1)), 7);\n    });\n\n    test('invalid input at end of line', () {\n      final document = createDocument('a\\ns\\nd\\r\\nf');\n      expect(document.offsetAt(position(0, 10)), 1);\n      expect(document.offsetAt(position(1, 10)), 3);\n      expect(document.offsetAt(position(2, 2)), 5);\n      expect(document.offsetAt(position(2, 3)), 5);\n      expect(document.offsetAt(position(2, 10)), 5);\n      expect(document.offsetAt(position(3, 10)), 8);\n\n      final pos = document.positionAt(6);\n      expect(pos.line, equals(2));\n      expect(pos.character, equals(1));\n    });\n  });\n\n  group('full updates', () {\n    test('one full update', () {\n      final document = createDocument('asdfqwer');\n      document.update([updateFull('hjklyuio')], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('hjklyuio'));\n    });\n\n    test('several full updates', () {\n      final document = createDocument('asdfqwer');\n      document.update([updateFull('hjklyuio'), updateFull('12345')], 2);\n      expect(document.version, equals(2));\n      expect(document.getText(), equals('12345'));\n    });\n  });\n\n  group('incremental updates', () {\n    void expectLineAtOffsets(TextDocument document) {\n      // Assuming \\n.\n      final text = document.getText();\n      final characters = text.split('');\n      var expected = 0;\n      for (var i = 0; i < text.length; i++) {\n        expect(document.positionAt(i).line, expected);\n        if (characters[i] == '\\n') {\n          expected += 1;\n        }\n      }\n      expect(document.positionAt(text.length).line, equals(expected));\n    }\n\n    test('removing content', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('', forSubstring(document, 'abcde')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('removing content over multiple lines', () {\n      final document = createDocument('abcde\\nfghij\\nklmno\\npqrst');\n      expect(document.version, equals(0));\n      expect(document.lineCount, equals(4));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('', forSubstring(document, 'fghij\\nklmno')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), 'abcde\\n\\npqrst');\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('adding content', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('12345', afterSubstring(document, 'abcde\\n')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('abcde\\n12345fghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('adding content over multiple lines', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental(\n          '12345\\n67890\\n',\n          afterSubstring(document, 'abcde\\n'),\n        ),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('abcde\\n12345\\n67890\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(5));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing single-line content with more content', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('1234567890', forSubstring(document, 'abcde')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('1234567890\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing single-line content with less content', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('1', forSubstring(document, 'abcde')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('1\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing single-line content with same amount of characters', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('12345', forSubstring(document, 'abcde')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('12345\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing multi-line content with more lines', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental(\n          '12345\\n67890\\nABCDE\\n',\n          forSubstring(document, 'abcde\\n'),\n        ),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('12345\\n67890\\nABCDE\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(5));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing multi-line content with fewer lines', () {\n      final document = createDocument('12345\\n67890\\nABCDE\\nfghij\\nklmno');\n      expect(document.lineCount, equals(5));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental(\n          'abcde\\n',\n          forSubstring(document, '12345\\n67890\\nABCDE\\n'),\n        ),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('abcde\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('replacing multi-line content with same amounts', () {\n      final document = createDocument('12345\\n67890\\nABCDE\\nfghij\\nklmno');\n      expect(document.lineCount, equals(5));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental(\n          'abcde\\nFGHJI',\n          forSubstring(document, 'ABCDE\\nfghij'),\n        ),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('12345\\n67890\\nabcde\\nFGHJI\\nklmno'));\n      expect(document.lineCount, equals(5));\n      expectLineAtOffsets(document);\n    });\n\n    test('replace large number of lines', () {\n      final document = createDocument('12345\\n67890\\nasdf');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      var text = '';\n      for (var i = 0; i < 20000; i++) {\n        // ignore: use_string_buffers\n        text += 'asdf\\n';\n      }\n      document.update([\n        updateIncremental(text, forSubstring(document, '67890\\n')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), '12345\\n${text}asdf');\n      expect(document.lineCount, 20002);\n      expectLineAtOffsets(document);\n    });\n\n    test('several incremental changes', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      expect(document.version, equals(0));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('BCD', forSubstring(document, 'bcd')),\n        updateIncremental('LMN', forSubstring(document, 'lmn')),\n        updateIncremental('GHI', forSubstring(document, 'ghi')),\n      ], 1);\n      expect(document.version, equals(1));\n      expect(document.getText(), equals('aBCDe\\nfGHIj\\nkLMNo'));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n    });\n\n    test('append', () {\n      final document = createDocument('abcde');\n      expect(document.lineCount, equals(1));\n      document.update([\n        updateIncremental('\\nfghij\\nklmno', afterSubstring(document, 'abcde')),\n      ], 1);\n      expect(document.getText(), equals('abcde\\nfghij\\nklmno'));\n      expect(document.lineCount, equals(3));\n    });\n\n    test('delete', () {\n      final document = createDocument('abcde\\nfghij\\nklmno');\n      expect(document.lineCount, equals(3));\n      document.update([updateIncremental('', forSubstring(document, 'o'))], 1);\n      expect(document.getText(), equals('abcde\\nfghij\\nklmn'));\n      expect(document.version, equals(1));\n      expect(document.lineCount, equals(3));\n      expectLineAtOffsets(document);\n      document.update([\n        updateIncremental('', forSubstring(document, 'fghij\\nklmn')),\n      ], 2);\n      expect(document.getText(), equals('abcde\\n'));\n      expect(document.version, equals(2));\n      expect(document.lineCount, equals(2));\n      expectLineAtOffsets(document);\n    });\n\n    test('handles weird update ranges', () {\n      var document = createDocument('abcde\\nfghij');\n      document.update([updateIncremental('1234', range(-4, 0, -2, 3))], 1);\n      expect(document.getText(), equals('1234abcde\\nfghij'));\n\n      document = createDocument('abcde\\nfghij');\n      document.update([updateIncremental('1234', range(-1, 0, 0, 5))], 1);\n      expect(document.getText(), equals('1234\\nfghij'));\n\n      document = createDocument('abcde\\nfghij');\n      document.update([updateIncremental('1234', range(1, 0, 13, 14))], 1);\n      expect(document.getText(), equals('abcde\\n1234'));\n\n      document = createDocument('abcde\\nfghij');\n      document.update([updateIncremental('1234', range(13, 0, 35, 14))], 1);\n      expect(document.getText(), equals('abcde\\nfghij1234'));\n\n      document = createDocument('abcde\\nfghij');\n      document.update([updateIncremental('1234', range(-13, 0, 35, 14))], 1);\n      expect(document.getText(), equals('1234'));\n    });\n  });\n\n  group('getWellFormedRange', () {\n    test('swaps start and end when needed', () {\n      final document = createDocument('hello');\n      final range = lsp.Range(\n        start: lsp.Position(character: 2, line: 1),\n        end: lsp.Position(character: 1, line: 1),\n      );\n      final wellformedRange = document.getWellformedRange(range);\n      expect(wellformedRange.start, equals(range.end));\n      expect(wellformedRange.end, equals(range.start));\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/CHANGELOG.md",
    "content": "# 9.1.1\n\n- fix: `BlocSelector` rebuilds when `selector` changes ([#4415](https://github.com/felangel/bloc/pull/4415))\n\n# 9.1.0\n\n- feat: expose `dispose` callback on `RepositoryProvider` ([#4356](https://github.com/felangel/bloc/pull/4356))\n\n# 9.0.0\n\n- fix: ensure widget is mounted before invoking listener ([#4237](https://github.com/felangel/bloc/pull/4237))\n- chore(deps): upgrade to `bloc ^9.0.0`\n- chore(deps): bump minimum Dart SDK version to 2.14\n- chore: update sponsors\n- chore: add funding to pubspec.yaml ([#4200](https://github.com/felangel/bloc/pull/4200))\n\n# 8.1.6\n\n- docs: fix gallery image urls ([#4184](https://github.com/felangel/bloc/pull/4184))\n\n# 8.1.5\n\n- feat: override `debugFillProperties` ([#4082](https://github.com/felangel/bloc/pull/4082))\n- docs: add missing comma to `BlocListener` API docs ([#4106](https://github.com/felangel/bloc/pull/4106))\n- chore: update copyright year\n- chore: update sponsors\n\n# 8.1.4\n\n- chore: update sponsors ([#4054](https://github.com/felangel/bloc/pull/4054))\n- chore: adjust `example` themes\n- chore: fix `require_trailing_commas` ([#3977](https://github.com/felangel/bloc/pull/3977))\n- chore: add `topics` to `pubspec.yaml` ([#3914](https://github.com/felangel/bloc/pull/3914))\n\n# 8.1.3\n\n- docs: remove graphql sample references from README ([#3820](https://github.com/felangel/bloc/pull/3820))\n- docs: upgrade to Dart 3 ([#3809](https://github.com/felangel/bloc/pull/3820))\n- refactor: standardize analysis_options ([#3809](https://github.com/felangel/bloc/pull/3820))\n- refactor: update sdk constraints and fix analysis warnings ([#3809](https://github.com/felangel/bloc/pull/3820))\n\n# 8.1.2\n\n- chore: add screenshots to `pubspec.yaml` ([#3717](https://github.com/felangel/bloc/pull/3717))\n- chore(deps): upgrade to `bloc ^8.1.1` ([#3716](https://github.com/felangel/bloc/pull/3716))\n- chore: update example to Dart 2.19 ([#3715](https://github.com/felangel/bloc/pull/3715))\n- refactor: `BlocObserver` instances to use `const` constructors ([#3713](https://github.com/felangel/bloc/pull/3713))\n- refactor: remove unnecessary single child widget mixins ([#3675](https://github.com/felangel/bloc/pull/3675))\n- refactor: upgrade to Flutter 3.7 ([#3699](https://github.com/felangel/bloc/pull/3699))\n  - remove deprecated `invariant_booleans` lint rule\n\n# 8.1.1\n\n- chore: remove dependency overrides from example to fix pana score\n\n# 8.1.0\n\n- feat: upgrade to `bloc: ^8.1.0`\n- chore: upgrade example to latest bloc and hydrated_bloc ([#3481](https://github.com/felangel/bloc/pull/3481))\n- docs: update GetStream utm tags ([#3136](https://github.com/felangel/bloc/pull/3136))\n- docs: update VGV sponsors logo ([#3125](https://github.com/felangel/bloc/pull/3125))\n\n# 8.0.1\n\n- refactor: use core interfaces from `bloc v8.0.2` ([#3012](https://github.com/felangel/bloc/pull/3012))\n- docs: update example to follow naming conventions ([#3027](https://github.com/felangel/bloc/pull/3027))\n\n# 8.0.0\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0`\n\n# 8.0.0-dev.3\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`\n\n# 8.0.0-dev.2\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`\n\n# 8.0.0-dev.1\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`\n\n# 7.3.3\n\n- fix: add missing child assertion to `BlocListener` and `BlocProvider` ([#2924](https://github.com/felangel/bloc/pull/2924))\n\n# 7.3.2\n\n- fix: `BlocProvider` explicitly default `lazy` to `true` to support `avoid_redundant_argument_values` ([#2917](https://github.com/felangel/bloc/pull/2917))\n\n# 7.3.1\n\n- fix: determine bloc reference changes via `identical`\n  - Previously `identityHashCode` was used to determine if the bloc reference had changed to trigger a rebuild ([#2482](https://github.com/felangel/bloc/pull/2482)) however, it's possible for different bloc references to have the same `hashCode` as a result of hash collisions. The fix uses `identical` to determine whether the bloc reference has changed.\n- docs: add inline docs to library\n- docs: minor improvements to example and `README`\n- chore: remove unneeded imports\n\n# 7.3.0\n\n- feat: upgrade to `bloc: ^7.2.0`\n\n# 7.2.0\n\n- feat: upgrade to `provider: ^6.0.0`\n\n# 7.1.0\n\n- feat: add `BlocSelector` widget\n\n# 7.0.1\n\n- fix: `BlocConsumer`, `BlocBuilder`, and `BlocListener` depend on bloc/cubit instance\n  - when a provided bloc instance changes, `BlocConsumer`, `BlocBuilder`, and `BlocListener` will all update to use the new instance\n- docs: update `BlocBuilder` and `BlocListener` inline API docs to include `buildWhen` and `listenWhen`\n\n# 7.0.0\n\n- **BREAKING**: refactor: rename `cubit` parameter to `bloc`\n  - refactor: rename `cubit` parameter in `BlocListener` to `bloc`\n  - refactor: rename `cubit` parameter in `BlocBuilder` to `bloc`\n  - refactor: rename `cubit` parameter in `BlocConsumer` to `bloc`\n- **BREAKING**: opt into null safety\n  - upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- **BREAKING**: refactor: remove deprecated `context.repository`\n- feat: upgrade to `bloc: ^7.0.0`\n- feat: upgrade to `provider: ^5.0.0`\n\n# 7.0.0-nullsafety.6\n\n- chore: bump to `bloc: ^7.0.0-nullsafety.4`\n\n# 7.0.0-nullsafety.5\n\n- **BREAKING**: refactor: remove deprecated `context.repository`\n\n# 7.0.0-nullsafety.4\n\n- chore: bump to `provider: ^5.0.0`\n\n# 7.0.0-nullsafety.3\n\n- chore: bump to `bloc: ^7.0.0-nullsafety.3`\n- chore: bump to `provider: ^5.0.0-nullsafety.5`\n\n# 7.0.0-nullsafety.2\n\n- chore: bump to `bloc: ^7.0.0-nullsafety.2`\n\n# 7.0.0-nullsafety.1\n\n- **BREAKING**: refactor: upgrade to `bloc ^7.0.0-nullsafety.1`\n  - refactor: rename `cubit` parameter in `BlocListener` to `bloc`\n  - refactor: rename `cubit` parameter in `BlocBuilder` to `bloc`\n  - refactor: rename `cubit` parameter in `BlocConsumer` to `bloc`\n- feat: upgrade to `provider ^5.0.0-nullsafety.3`\n\n# 7.0.0-nullsafety.0\n\n- **BREAKING**: opt into null safety\n- feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n\n# 7.0.0-dev.2\n\n- **BREAKING**: revert `7.0.0-dev.1`\n- **BREAKING**: remove `context.bloc` extension\n- **BREAKING**: rename `cubit` parameter in `BlocListener` to `value`\n- **BREAKING**: rename `cubit` parameter in `BlocBuilder` to `value`\n- **BREAKING**: rename `cubit` parameter in `BlocConsumer` to `value`\n- docs: update inline API docs\n\n# 7.0.0-dev.1\n\n- **BREAKING**: remove dependency on `package:provider`\n- feat: add optional listen to `BlocProvider`\n- feat: add ListenProviderExtension\n- docs: improve inline documentation for `BlocProvider` and `RepositoryProvider`\n\n# 6.1.3\n\n- feat: expose `ProviderNotFoundException`\n- chore: upgrade to `bloc ^6.1.3`\n- chore: upgrade to `provider ^4.3.3`\n\n# 6.1.2\n\n- fix: `BlocProvider.value` use `InheritedProvider.value`\n\n# 6.1.1\n\n- fix: `BlocConsumer` does not respect `buildWhen` and results in unnecessary rebuilds\n\n# 6.1.0\n\n- feat: add optional `listen` to `BlocProvider` and `RepositoryProvider`\n- feat: add `context.select<T, R>(R Function(T value))` which allows widgets to listen to only a small part of the state of `T` (`R`)\n- feat: add `context.watch<T>()` which allows widgets to listen to changes in the state of `T`\n- feat: add `context.read<T>()` which allows widgets to access `T` without listening for changes\n- deprecated: `context.bloc` in favor of `context.read` and `context.watch`\n- deprecated: `context.repository` in favor of `context.read` and `context.watch`\n- fix: rethrow `ProviderNotFoundException` from `RepositoryProvider` for external dependencies\n- docs: improve inline documentation for `BlocProvider` and `RepositoryProvider`\n- docs: update list of examples in `README`\n\n# 6.0.6\n\n- docs: improve inline documentation for `buildWhen`\n\n# 6.0.5\n\n- fix: `BlocBuilder` `builder` and `buildWhen` state mismatch\n\n# 6.0.4\n\n- fix: state synchronization issue when providing a condition\n\n# 6.0.3\n\n- refactor: `BlocConsumer` requires a single subscription\n- refactor: `BlocBuilder` extends `BlocListener`\n- refactor: `MultiRepositoryProvider` extends `MultiProvider`\n\n# 6.0.2\n\n- docs: fix missing API documentation generated by dartdoc\n\n# 6.0.1\n\n- docs: minor documentation fixes and improvements\n\n# 6.0.0\n\n- **BREAKING**: upgrade to `bloc ^6.0.0`\n- **BREAKING**: `BlocBuilder` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- **BREAKING**: `BlocListener` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- **BREAKING**: `BlocConsumer` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- feat: remove external dependency on [package:flutter_cubit](https://pub.dev/packages/flutter_cubit)\n- docs: inline documentation updates\n- docs: README updates\n- docs: example application updates\n\n# 6.0.0-dev.1\n\n- **BREAKING**: upgrade to `bloc ^6.0.0-dev.1`\n- **BREAKING**: `BlocBuilder` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- **BREAKING**: `BlocListener` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- **BREAKING**: `BlocConsumer` interop with `cubit` (`bloc` parameter renamed to `cubit`)\n- feat: remove external dependency on [package:flutter_cubit](https://pub.dev/packages/flutter_cubit)\n- docs: inline documentation updates\n- docs: README updates\n- docs: example application updates\n\n# 5.0.1\n\n- fix: upgrade to `bloc ^5.0.1`\n- docs: minor documentation updates\n\n# 5.0.0\n\n- **BREAKING**: `condition` on `BlocBuilder` renamed to `buildWhen`\n- **BREAKING**: `condition` on `BlocListener` renamed to `listenWhen`\n- **BREAKING**: upgrade to `bloc v5.0.0`\n- refactor: internal implementation updates to use [flutter_cubit](https://pub.dev/packages/flutter_cubit)\n- docs: various improvements\n- docs: logo updates\n\n# 5.0.0-dev.5\n\n- Update to `bloc: ^5.0.0-dev.11`\n- Minor README updates\n\n# 5.0.0-dev.4\n\n- Update to `bloc: ^5.0.0-dev.10`\n- Minor README updates\n\n# 5.0.0-dev.3\n\n- Update to `bloc: ^5.0.0-dev.7`\n\n# 5.0.0-dev.2\n\n- Update to `bloc: ^5.0.0-dev.6`\n- Update to `flutter_cubit ^0.0.12`\n- Various Documentation Updates\n\n# 5.0.0-dev.1\n\n- Update to `bloc: ^5.0.0-dev.3`\n- Internal implementation updates to use [flutter_cubit](https://pub.dev/packages/flutter_cubit)\n\n# 4.0.1\n\n- Fix `ProviderNotFoundException` handling ([#1286](https://github.com/felangel/bloc/pull/1286))\n\n# 4.0.0\n\n- Update to `bloc: ^4.0.0`\n- Update to `provider: ^4.0.5`\n\n# 4.0.0-dev.4\n\n- Update to `bloc: ^4.0.0-dev.4`\n\n# 4.0.0-dev.3\n\n- Update to `bloc: ^4.0.0-dev.3`\n\n# 4.0.0-dev.2\n\n- Update to `bloc: ^4.0.0-dev.2`\n\n# 4.0.0-dev.1\n\n- Update to `bloc: ^4.0.0-dev.1`\n\n# 3.2.0\n\n- Fix type inference for: `MultiBlocProvider`, `MultiRepositoryProvider`, `MultiBlocListener` ([#773](https://github.com/felangel/bloc/pull/773))\n- Fix swallowed exceptions within `BlocProvider` and `RepositoryProvider` ([#807](https://github.com/felangel/bloc/issues/807))\n- Add `BlocProviderExtension` and `RepositoryProviderExtension` on `BuildContext` ([#608](https://github.com/felangel/bloc/issues/608))\n\n# 3.1.0\n\n- Expose lazy parameter on `RepositoryProvider` and `BlocProvider` ([#749](https://github.com/felangel/bloc/pull/749))\n- Updated to `provider: ^4.0.1` ([#748](https://github.com/felangel/bloc/issues/748))\n- Add `BlocConsumer` ([#545](https://github.com/felangel/bloc/issues/545))\n- Export `bloc` as part of `flutter_bloc`\n\n# 3.0.0\n\n- Updated to `bloc: ^3.0.0` ([#700](https://github.com/felangel/bloc/pull/700))\n- Updated to `flutter >=1.12.1` ([#700](https://github.com/felangel/bloc/pull/700))\n- Updated to `provider: ^4.0.0` ([#700](https://github.com/felangel/bloc/pull/700), [#734](https://github.com/felangel/bloc/pull/734))\n- Revert `BlocBuilder` and `BlocListener` condition behavior to set `previousState` to the previous bloc state ([#709](https://github.com/felangel/bloc/issues/709))\n\n# 3.0.0-dev.1\n\n- Updated to `bloc: ^3.0.0-dev.1`\n\n# 2.1.1\n\n- Fix internal analysis warnings\n- Enforce provider `^3.2.0`\n\n# 2.1.0\n\n- Deprecate `builder` in `BlocProvider` in favor of `create` to align with `provider`.\n- Deprecate `builder` in `RepositoryProvider` in favor of `create` to align with `provider`.\n\n# 2.0.1\n\n- Fix `BlocBuilder` and `BlocListener` condition behavior to set `previousState` to the previous state used by `BlocBuilder`/`BlocListener` instead of the previous state of the `bloc`.\n- Minor Documentation Updates\n\n# 2.0.0\n\n- Updated to `bloc: ^2.0.0` and Documentation Updates\n- Adhere to [effective dart](https://dart.dev/guides/language/effective-dart) ([#561](https://github.com/felangel/bloc/issues/561))\n\n# 1.0.0\n\nUpdated to `bloc: ^1.0.0` and Documentation Updates\n\n# 0.22.1\n\nMinor Bugfixes and Documentation Updates\n\n# 0.22.0\n\nUpdated to `bloc: ^0.16.0` and Documentation Updates\n\n# 0.21.0\n\nUpdated to `bloc: ^0.15.0` and Documentation Updates\n\n# 0.20.1\n\n- Minor Updates to Package Dependencies\n- Documentation Updates\n\n# 0.20.0\n\n- Add Automatic Bloc Lookup to `BlocBuilder` and `BlocListener` ([#415](https://github.com/felangel/bloc/pull/415))\n- Support for `BlocProvider` instantiation and look-up within the same `BuildContext` ([#415](https://github.com/felangel/bloc/pull/415))\n- Documentation Updates\n\n# 0.19.1\n\nAdd optional `condition` to `BlocListener` to control listener calls ([#406](https://github.com/felangel/bloc/pull/406)) and Documentation Updates\n\n# 0.19.0\n\nAddresses [#354](https://github.com/felangel/bloc/issues/354)\n\n#### BlocProvider\n\n- Refactor `BlocProvider` to extend `Provider`\n- Rename `BlocProviderTree` to `MultiBlocProvider`\n\n#### ImmutableProvider\n\n- Refactor `ImmutableProvider` to extend `Provider`\n- Rename `ImmutableProvider` to `RepositoryProvider`\n- Rename `ImmutableProviderTree` to `MultiRepositoryProvider`\n\n#### BlocListener\n\n- Rename `BlocListenerTree` to `MultiBlocListener`\n\n#### Documentation\n\n- Inline documentation updates/improvements\n\n# 0.18.3\n\nFix `BlocProvider` bug where `copyWith` does not preserve `dispose` value ([#376](https://github.com/felangel/bloc/issues/376)).\n\n# 0.18.2\n\nFix `BlocListener` bug where `listener` gets called even when no state change occurs ([#368](https://github.com/felangel/bloc/issues/368)).\n\n# 0.18.1\n\nMinor Documentation Updates\n\n# 0.18.0\n\nExpose `ImmutableProvider` & `ImmutableProviderTree` to enable developers to provide immutable values, such as repositories, throughout the widget tree ([#364](https://github.com/felangel/bloc/pull/364)) and Documentation Updates\n\n# 0.17.0\n\nUpdate `BlocProvider` to automatically `dispose` the provided bloc ([#349](https://github.com/felangel/bloc/pull/349)) and Documentation Updates\n\n# 0.16.0\n\nUpdate `BlocProvider` to expose `builder` and `dispose` ([#344](https://github.com/felangel/bloc/pull/344) and [#347](https://github.com/felangel/bloc/pull/347)) and Documentation Updates\n\n# 0.15.1\n\nFix `null` initial `previousState` in `BlocBuilder` `condition` ([#328](https://github.com/felangel/bloc/issues/328)) and Documentation Updates\n\n# 0.15.0\n\nAdded optional `condition` to `BlocBuilder` to control widget rebuilds ([#315](https://github.com/felangel/bloc/issues/315)) and Documentation Updates\n\n# 0.14.0\n\nUpdated to `bloc: ^0.14.0` and Documentation Updates\n\n# 0.13.0\n\nUpdated to `bloc: ^0.13.0` and Documentation Updates\n\n# 0.12.0\n\nAdded `BlocListenerTree` and Documentation Updates\n\n# 0.11.1\n\nBroaden Dart version range and Minor Documentation Updates\n\n# 0.11.0\n\nUpdated to `bloc: ^0.12.0` and Documentation Updates\n\n# 0.10.1\n\nInvoke `BlocWidgetListener` on initial state and Documentation Updates\n\n# 0.10.0\n\nAdded `BlocListener` and Documentation Updates\n\n# 0.9.1\n\nMinor Updates to Documentation.\n\n# 0.9.0\n\nUpdated to `bloc: ^0.11.0` and Documentation Updates\n\n# 0.8.0\n\nUpdated to `bloc: ^0.10.0` and Documentation Updates\n\n# 0.7.1\n\nMinor Updates to Documentation.\n\n# 0.7.0\n\nAdded `BlocProviderTree` and Documentation Updates\n\n# 0.6.3\n\nUpdated to `bloc:^0.9.3` and Minor Updates to Documentation\n\n# 0.6.2\n\nAdditional Minor Updates to Documentation\n\n# 0.6.1\n\nMinor Updates to Documentation\n\n# 0.6.0\n\nUpdated to `bloc: ^0.9.0`\n\n# 0.5.4\n\nAdditional Minor Updates to Documentation\n\n# 0.5.3\n\nAdditional Minor Updates to Documentation\n\n# 0.5.2\n\nMinor Updates to Documentation\n\n# 0.5.1\n\n`BlocProvider` performance improvements\n\n# 0.5.0\n\nUpdated to `bloc: ^0.8.0`\n\n# 0.4.12\n\nAdditional Minor Updates to Documentation\n\n# 0.4.11\n\nAdditional Minor Updates to Documentation\n\n# 0.4.10\n\nAdditional `BlocBuilder` enhancements\n\n- `BlocBuilder` no longer filters out States giving developers full control\n\nMinor Updates to Documentation and Examples\n\n# 0.4.9\n\nAdditional `BlocBuilder` enhancements\n\n- `BlocBuilder` no longer has a dependency on `RxDart`\n- Using `bloc: \">=0.7.5 <0.8.0\"`\n\n# 0.4.8\n\nAdditional `BlocProvider` performance improvements\n\n# 0.4.7\n\nMinor Updates to Documentation and Examples\n\n# 0.4.6\n\nBug Fixes\n\n- Fixed bug where `BlocBuilder` would return initial state instead of the latest state\n\n# 0.4.5\n\nAdditional Minor Updates to Documentation\n\n# 0.4.4\n\nMinor updates to documentation and improved error reporting in `BlocProvider`\n\n# 0.4.3\n\n`BlocBuilder` performance improvements\n\n# 0.4.2\n\n`BlocProvider` performance improvements\n\n# 0.4.1\n\nMinor Updates to Documentation\n\n# 0.4.0\n\nUpdated to `bloc: ^0.7.0`\n\n# 0.3.1\n\nMinor Updates to Documentation\n\n# 0.3.0\n\nUpdated to `bloc: ^0.6.0`\n\n# 0.2.1\n\nMinor Updates to Documentation\n\n# 0.2.0\n\nUpdates to `BlocBuilder` and `BlocProvider`\n\n- `BlocBuilder` does not automatically dispose a `Bloc`. Developers are now responsible for determining when to call `Bloc.dispose()`\n- `BlocProvider` support for `of(context)` with generics\n  - Support for multiple nested `BlocProviders` with different Bloc Types.\n\n# 0.1.1\n\nMinor Updates to Documentation\n\n# 0.1.0\n\nInitial Version of the library.\n\n- Includes the ability to connect presentation layer to `Bloc` by using the `BlocBuilder` Widget.\n- Includes `BlocProvider`, a DI widget that allows a single instance of a bloc to be provided to multiple widgets within a subtree.\n"
  },
  {
    "path": "packages/flutter_bloc/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "packages/flutter_bloc/README.md",
    "content": "<p align=\"right\">\n<a href=\"https://flutter.dev/docs/development/packages-and-plugins/favorites\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/pub/flutter_favorite.png\" width=\"100\" alt=\"build\"></a>\n</p>\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/flutter_bloc.png\" height=\"100\" alt=\"Flutter Bloc Package\" />\n</p>\n\n<p align=\"center\">\n<a href=\"https://pub.dev/packages/flutter_bloc\"><img src=\"https://img.shields.io/pub/v/flutter_bloc.svg\" alt=\"Pub\"></a>\n<a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n<a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n<a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n<a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n<a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n<a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n<a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n<a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n<a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\n---\n\nWidgets that make it easy to integrate blocs and cubits into [Flutter](https://flutter.dev). Built to work with [package:bloc](https://pub.dev/packages/bloc).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n_\\*Note: All widgets exported by the `flutter_bloc` package integrate with both `Cubit` and `Bloc` instances._\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Usage\n\nLets take a look at how to use `BlocProvider` to provide a `CounterCubit` to a `CounterPage` and react to state changes with `BlocBuilder`.\n\n### counter_cubit.dart\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n### main.dart\n\n```dart\nvoid main() => runApp(CounterApp());\n\nclass CounterApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: BlocProvider(\n        create: (_) => CounterCubit(),\n        child: CounterPage(),\n      ),\n    );\n  }\n}\n```\n\n### counter_page.dart\n\n```dart\nclass CounterPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Counter')),\n      body: BlocBuilder<CounterCubit, int>(\n        builder: (context, count) => Center(child: Text('$count')),\n      ),\n      floatingActionButton: Column(\n        crossAxisAlignment: CrossAxisAlignment.end,\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: <Widget>[\n          FloatingActionButton(\n            child: const Icon(Icons.add),\n            onPressed: () => context.read<CounterCubit>().increment(),\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.remove),\n            onPressed: () => context.read<CounterCubit>().decrement(),\n          ),\n        ],\n      ),\n    );\n  }\n}\n```\n\nAt this point we have successfully separated our presentational layer from our business logic layer. Notice that the `CounterPage` widget knows nothing about what happens when a user taps the buttons. The widget simply notifies the `CounterCubit` that the user has pressed either the increment or decrement button.\n\n## Bloc Widgets\n\n### BlocBuilder\n\n**BlocBuilder** is a Flutter widget which requires a `bloc` and a `builder` function. `BlocBuilder` handles building the widget in response to new states. `BlocBuilder` is very similar to `StreamBuilder` but has a more simple API to reduce the amount of boilerplate code needed. The `builder` function will potentially be called many times and should be a [pure function](https://en.wikipedia.org/wiki/Pure_function) that returns a widget in response to the state.\n\nSee `BlocListener` if you want to \"do\" anything in response to state changes such as navigation, showing a dialog, etc...\n\nIf the `bloc` parameter is omitted, `BlocBuilder` will automatically perform a lookup using `BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocBuilder<BlocA, BlocAState>(\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  }\n)\n```\n\nOnly specify the bloc if you wish to provide a bloc that will be scoped to a single widget and isn't accessible via a parent `BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocBuilder<BlocA, BlocAState>(\n  bloc: blocA, // provide the local bloc instance\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  }\n)\n```\n\nFor fine-grained control over when the `builder` function is called an optional `buildWhen` can be provided. `buildWhen` takes the previous bloc state and current bloc state and returns a boolean. If `buildWhen` returns true, `builder` will be called with `state` and the widget will rebuild. If `buildWhen` returns false, `builder` will not be called with `state` and no rebuild will occur.\n\n```dart\nBlocBuilder<BlocA, BlocAState>(\n  buildWhen: (previousState, state) {\n    // return true/false to determine whether or not\n    // to rebuild the widget with state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  }\n)\n```\n\n### BlocSelector\n\n**BlocSelector** is a Flutter widget which is analogous to `BlocBuilder` but allows developers to filter updates by selecting a new value based on the current bloc state. Unnecessary builds are prevented if the selected value does not change. The selected value must be immutable in order for `BlocSelector` to accurately determine whether `builder` should be called again.\n\nIf the `bloc` parameter is omitted, `BlocSelector` will automatically perform a lookup using `BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocSelector<BlocA, BlocAState, SelectedState>(\n  selector: (state) {\n    // return selected state based on the provided state.\n  },\n  builder: (context, state) {\n    // return widget here based on the selected state.\n  },\n)\n```\n\n### BlocProvider\n\n**BlocProvider** is a Flutter widget which provides a bloc to its children via `BlocProvider.of<T>(context)`. It is used as a dependency injection (DI) widget so that a single instance of a bloc can be provided to multiple widgets within a subtree.\n\nIn most cases, `BlocProvider` should be used to create new blocs which will be made available to the rest of the subtree. In this case, since `BlocProvider` is responsible for creating the bloc, it will automatically handle closing it.\n\n```dart\nBlocProvider(\n  create: (BuildContext context) => BlocA(),\n  child: ChildA(),\n);\n```\n\nBy default, BlocProvider will create the bloc lazily, meaning `create` will get executed when the bloc is looked up via `BlocProvider.of<BlocA>(context)`.\n\nTo override this behavior and force `create` to be run immediately, `lazy` can be set to `false`.\n\n```dart\nBlocProvider(\n  lazy: false,\n  create: (BuildContext context) => BlocA(),\n  child: ChildA(),\n);\n```\n\nIn some cases, `BlocProvider` can be used to provide an existing bloc to a new portion of the widget tree. This will be most commonly used when an existing `bloc` needs to be made available to a new route. In this case, `BlocProvider` will not automatically close the bloc since it did not create it.\n\n```dart\nBlocProvider.value(\n  value: BlocProvider.of<BlocA>(context),\n  child: ScreenA(),\n);\n```\n\nthen from either `ChildA`, or `ScreenA` we can retrieve `BlocA` with:\n\n```dart\n// with extensions\ncontext.read<BlocA>();\n\n// without extensions\nBlocProvider.of<BlocA>(context);\n```\n\nThe above snippets result in a one time lookup and the widget will not be notified of changes. To retrieve the instance and subscribe to subsequent state changes use:\n\n```dart\n// with extensions\ncontext.watch<BlocA>();\n\n// without extensions\nBlocProvider.of<BlocA>(context, listen: true);\n```\n\nIn addition, `context.select` can be used to retrieve part of a state and react to changes only when the selected part changes.\n\n```dart\nfinal isPositive = context.select((CounterBloc b) => b.state >= 0);\n```\n\nThe snippet above will only rebuild if the state of the `CounterBloc` changes from positive to negative or vice versa and is functionally identical to using a `BlocSelector`.\n\n### MultiBlocProvider\n\n**MultiBlocProvider** is a Flutter widget that merges multiple `BlocProvider` widgets into one.\n`MultiBlocProvider` improves the readability and eliminates the need to nest multiple `BlocProviders`.\nBy using `MultiBlocProvider` we can go from:\n\n```dart\nBlocProvider<BlocA>(\n  create: (BuildContext context) => BlocA(),\n  child: BlocProvider<BlocB>(\n    create: (BuildContext context) => BlocB(),\n    child: BlocProvider<BlocC>(\n      create: (BuildContext context) => BlocC(),\n      child: ChildA(),\n    )\n  )\n)\n```\n\nto:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider<BlocA>(\n      create: (BuildContext context) => BlocA(),\n    ),\n    BlocProvider<BlocB>(\n      create: (BuildContext context) => BlocB(),\n    ),\n    BlocProvider<BlocC>(\n      create: (BuildContext context) => BlocC(),\n    ),\n  ],\n  child: ChildA(),\n)\n```\n\n### BlocListener\n\n**BlocListener** is a Flutter widget which takes a `BlocWidgetListener` and an optional `bloc` and invokes the `listener` in response to state changes in the bloc. It should be used for functionality that needs to occur once per state change such as navigation, showing a `SnackBar`, showing a `Dialog`, etc...\n\n`listener` is only called once for each state change (**NOT** including the initial state) unlike `builder` in `BlocBuilder` and is a `void` function.\n\nIf the bloc parameter is omitted, `BlocListener` will automatically perform a lookup using `BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocListener<BlocA, BlocAState>(\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  child: Container(),\n)\n```\n\nOnly specify the bloc if you wish to provide a bloc that is otherwise not accessible via `BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocListener<BlocA, BlocAState>(\n  bloc: blocA,\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  }\n)\n```\n\nFor fine-grained control over when the `listener` function is called an optional `listenWhen` can be provided. `listenWhen` takes the previous bloc state and current bloc state and returns a boolean. If `listenWhen` returns true, `listener` will be called with `state`. If `listenWhen` returns false, `listener` will not be called with `state`.\n\n```dart\nBlocListener<BlocA, BlocAState>(\n  listenWhen: (previousState, state) {\n    // return true/false to determine whether or not\n    // to call listener with state\n  },\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  child: Container(),\n)\n```\n\n### MultiBlocListener\n\n**MultiBlocListener** is a Flutter widget that merges multiple `BlocListener` widgets into one.\n`MultiBlocListener` improves the readability and eliminates the need to nest multiple `BlocListeners`.\nBy using `MultiBlocListener` we can go from:\n\n```dart\nBlocListener<BlocA, BlocAState>(\n  listener: (context, state) {},\n  child: BlocListener<BlocB, BlocBState>(\n    listener: (context, state) {},\n    child: BlocListener<BlocC, BlocCState>(\n      listener: (context, state) {},\n      child: ChildA(),\n    ),\n  ),\n)\n```\n\nto:\n\n```dart\nMultiBlocListener(\n  listeners: [\n    BlocListener<BlocA, BlocAState>(\n      listener: (context, state) {},\n    ),\n    BlocListener<BlocB, BlocBState>(\n      listener: (context, state) {},\n    ),\n    BlocListener<BlocC, BlocCState>(\n      listener: (context, state) {},\n    ),\n  ],\n  child: ChildA(),\n)\n```\n\n### BlocConsumer\n\n**BlocConsumer** exposes a `builder` and `listener` in order react to new states. `BlocConsumer` is analogous to a nested `BlocListener` and `BlocBuilder` but reduces the amount of boilerplate needed. `BlocConsumer` should only be used when it is necessary to both rebuild UI and execute other reactions to state changes in the `bloc`. `BlocConsumer` takes a required `BlocWidgetBuilder` and `BlocWidgetListener` and an optional `bloc`, `BlocBuilderCondition`, and `BlocListenerCondition`.\n\nIf the `bloc` parameter is omitted, `BlocConsumer` will automatically perform a lookup using\n`BlocProvider` and the current `BuildContext`.\n\n```dart\nBlocConsumer<BlocA, BlocAState>(\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  }\n)\n```\n\nAn optional `listenWhen` and `buildWhen` can be implemented for more granular control over when `listener` and `builder` are called. The `listenWhen` and `buildWhen` will be invoked on each `bloc` `state` change. They each take the previous `state` and current `state` and must return a `bool` which determines whether or not the `builder` and/or `listener` function will be invoked. The previous `state` will be initialized to the `state` of the `bloc` when the `BlocConsumer` is initialized. `listenWhen` and `buildWhen` are optional and if they aren't implemented, they will default to `true`.\n\n```dart\nBlocConsumer<BlocA, BlocAState>(\n  listenWhen: (previous, current) {\n    // return true/false to determine whether or not\n    // to invoke listener with state\n  },\n  listener: (context, state) {\n    // do stuff here based on BlocA's state\n  },\n  buildWhen: (previous, current) {\n    // return true/false to determine whether or not\n    // to rebuild the widget with state\n  },\n  builder: (context, state) {\n    // return widget here based on BlocA's state\n  }\n)\n```\n\n### RepositoryProvider\n\n**RepositoryProvider** is a Flutter widget which provides a repository to its children via `RepositoryProvider.of<T>(context)`. It is used as a dependency injection (DI) widget so that a single instance of a repository can be provided to multiple widgets within a subtree. `BlocProvider` should be used to provide blocs whereas `RepositoryProvider` should only be used for repositories.\n\n```dart\nRepositoryProvider(\n  create: (context) => RepositoryA(),\n  child: ChildA(),\n);\n```\n\nthen from `ChildA` we can retrieve the `Repository` instance with:\n\n```dart\n// with extensions\ncontext.read<RepositoryA>();\n\n// without extensions\nRepositoryProvider.of<RepositoryA>(context)\n```\n\nRepositories that manage resources which must be disposed can do so via the `dispose` callback:\n\n```dart\nRepositoryProvider(\n  create: (context) => RepositoryA(),\n  dispose: (repository) => repository.dispose(),\n  child: ChildA(),\n);\n```\n\n### MultiRepositoryProvider\n\n**MultiRepositoryProvider** is a Flutter widget that merges multiple `RepositoryProvider` widgets into one.\n`MultiRepositoryProvider` improves the readability and eliminates the need to nest multiple `RepositoryProvider`.\nBy using `MultiRepositoryProvider` we can go from:\n\n```dart\nRepositoryProvider<RepositoryA>(\n  create: (context) => RepositoryA(),\n  child: RepositoryProvider<RepositoryB>(\n    create: (context) => RepositoryB(),\n    child: RepositoryProvider<RepositoryC>(\n      create: (context) => RepositoryC(),\n      child: ChildA(),\n    )\n  )\n)\n```\n\nto:\n\n```dart\nMultiRepositoryProvider(\n  providers: [\n    RepositoryProvider<RepositoryA>(\n      create: (context) => RepositoryA(),\n    ),\n    RepositoryProvider<RepositoryB>(\n      create: (context) => RepositoryB(),\n    ),\n    RepositoryProvider<RepositoryC>(\n      create: (context) => RepositoryC(),\n    ),\n  ],\n  child: ChildA(),\n)\n```\n\n## Gallery\n\n<div style=\"text-align: center\">\n    <table>\n        <tr>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-counter\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_counter.gif\" width=\"200\"/>\n                </a>\n            </td>            \n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-infinite-list\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_infinite_list.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-login\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_firebase_login.gif\" width=\"200\" />\n                </a>\n            </td>\n        </tr>\n        <tr>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/github-search\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_github_search.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-weather\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_weather.gif\" width=\"200\"/>\n                </a>\n            </td>\n            <td style=\"text-align: center\">\n                <a href=\"https://bloclibrary.dev/tutorials/flutter-todos\">\n                    <img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/examples/flutter_todos.gif\" width=\"200\"/>\n                </a>\n            </td>\n        </tr>\n    </table>\n</div>\n\n## Examples\n\n- [Counter](https://bloclibrary.dev/tutorials/flutter-counter) - an example of how to create a `CounterBloc` to implement the classic Flutter Counter app.\n- [Form Validation](https://github.com/felangel/bloc/tree/master/examples/flutter_form_validation) - an example of how to use the `bloc` and `flutter_bloc` packages to implement form validation.\n- [Bloc with Stream](https://github.com/felangel/bloc/tree/master/examples/flutter_bloc_with_stream) - an example of how to hook up a `bloc` to a `Stream` and update the UI in response to data from the `Stream`.\n- [Complex List](https://github.com/felangel/bloc/tree/master/examples/flutter_complex_list) - an example of how to manage a list of items and asynchronously delete items one at a time using `bloc` and `flutter_bloc`.\n- [Infinite List](https://bloclibrary.dev/tutorials/flutter-infinite-list) - an example of how to use the `bloc` and `flutter_bloc` packages to implement an infinite scrolling list.\n- [Login Flow](https://bloclibrary.dev/tutorials/flutter-login) - an example of how to use the `bloc` and `flutter_bloc` packages to implement a Login Flow.\n- [Firebase Login](https://bloclibrary.dev/tutorials/flutter-firebase-login) - an example of how to use the `bloc` and `flutter_bloc` packages to implement login via Firebase.\n- [Github Search](https://bloclibrary.dev/tutorials/github-search) - an example of how to create a Github Search Application using the `bloc` and `flutter_bloc` packages.\n- [Weather](https://bloclibrary.dev/tutorials/flutter-weather) - an example of how to create a Weather Application using the `bloc` and `flutter_bloc` packages. The app uses a `RefreshIndicator` to implement \"pull-to-refresh\" as well as dynamic theming.\n- [Todos](https://bloclibrary.dev/tutorials/flutter-todos) - an example of how to create a Todos Application using the `bloc` and `flutter_bloc` packages.\n- [Timer](https://bloclibrary.dev/tutorials/flutter-timer) - an example of how to create a Timer using the `bloc` and `flutter_bloc` packages.\n- [Shopping Cart](https://github.com/felangel/bloc/tree/master/examples/flutter_shopping_cart) - an example of how to create a Shopping Cart Application using the `bloc` and `flutter_bloc` packages based on [flutter samples](https://github.com/flutter/samples/tree/master/provider_shopper).\n- [Dynamic Form](https://github.com/felangel/bloc/tree/master/examples/flutter_dynamic_form) - an example of how to use the `bloc` and `flutter_bloc` packages to implement a dynamic form which pulls data from a repository.\n- [Wizard](https://github.com/felangel/bloc/tree/master/examples/flutter_wizard) - an example of how to build a multi-step wizard using the `bloc` and `flutter_bloc` packages.\n- [Fluttersaurus](https://github.com/felangel/fluttersaurus) - an example of how to use the `bloc` and `flutter_bloc` packages to create a thesuarus app -- made for Bytconf Flutter 2020.\n- [I/O Photo Booth](https://github.com/flutter/photobooth) - an example of how to use the `bloc` and `flutter_bloc` packages to create a virtual photo booth web app -- made for Google I/O 2021.\n- [I/O Pinball](https://github.com/flutter/pinball) - an example of how to use the `bloc` and `flutter_bloc` packages to create a pinball web app -- made for Google I/O 2022.\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/flutter_bloc/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n"
  },
  {
    "path": "packages/flutter_bloc/example/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Web related\nlib/generated_plugin_registrant.dart\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "packages/flutter_bloc/example/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "packages/flutter_bloc/example/README.md",
    "content": "# example\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "packages/flutter_bloc/example/analysis_options.yaml",
    "content": "include: ../../../analysis_options.yaml\n"
  },
  {
    "path": "packages/flutter_bloc/example/lib/main.dart",
    "content": "// ignore_for_file: avoid_print\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nvoid main() {\n  Bloc.observer = const AppBlocObserver();\n  runApp(const App());\n}\n\n/// {@template app_bloc_observer}\n/// Custom [BlocObserver] that observes all bloc and cubit state changes.\n/// {@endtemplate}\nclass AppBlocObserver extends BlocObserver {\n  /// {@macro app_bloc_observer}\n  const AppBlocObserver();\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    if (bloc is Cubit) print(change);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    print(transition);\n  }\n}\n\n/// {@template app}\n/// A [StatelessWidget] that:\n/// * uses [bloc](https://pub.dev/packages/bloc) and\n/// [flutter_bloc](https://pub.dev/packages/flutter_bloc)\n/// to manage the state of a counter and the app theme.\n/// {@endtemplate}\nclass App extends StatelessWidget {\n  /// {@macro app}\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => ThemeCubit(),\n      child: const AppView(),\n    );\n  }\n}\n\n/// {@template app_view}\n/// A [StatelessWidget] that:\n/// * reacts to state changes in the [ThemeCubit]\n/// and updates the theme of the [MaterialApp].\n/// * renders the [CounterPage].\n/// {@endtemplate}\nclass AppView extends StatelessWidget {\n  /// {@macro app_view}\n  const AppView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<ThemeCubit, ThemeData>(\n      builder: (_, theme) {\n        return MaterialApp(\n          theme: theme,\n          home: const CounterPage(),\n        );\n      },\n    );\n  }\n}\n\n/// {@template counter_page}\n/// A [StatelessWidget] that:\n/// * provides a [CounterBloc] to the [CounterView].\n/// {@endtemplate}\nclass CounterPage extends StatelessWidget {\n  /// {@macro counter_page}\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => CounterBloc(),\n      child: const CounterView(),\n    );\n  }\n}\n\n/// {@template counter_view}\n/// A [StatelessWidget] that:\n/// * demonstrates how to consume and interact with a [CounterBloc].\n/// {@endtemplate}\nclass CounterView extends StatelessWidget {\n  /// {@macro counter_view}\n  const CounterView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Counter')),\n      body: Center(\n        child: BlocBuilder<CounterBloc, int>(\n          builder: (context, count) {\n            return Text(\n              '$count',\n              style: Theme.of(context).textTheme.displayLarge,\n            );\n          },\n        ),\n      ),\n      floatingActionButton: Column(\n        crossAxisAlignment: CrossAxisAlignment.end,\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: <Widget>[\n          FloatingActionButton(\n            child: const Icon(Icons.add),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterIncrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.remove),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterDecrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.brightness_6),\n            onPressed: () {\n              context.read<ThemeCubit>().toggleTheme();\n            },\n          ),\n        ],\n      ),\n    );\n  }\n}\n\n/// Event being processed by [CounterBloc].\nabstract class CounterEvent {}\n\n/// Notifies bloc to increment state.\nclass CounterIncrementPressed extends CounterEvent {}\n\n/// Notifies bloc to decrement state.\nclass CounterDecrementPressed extends CounterEvent {}\n\n/// {@template counter_bloc}\n/// A simple [Bloc] that manages an `int` as its state.\n/// {@endtemplate}\nclass CounterBloc extends Bloc<CounterEvent, int> {\n  /// {@macro counter_bloc}\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n\n/// {@template brightness_cubit}\n/// A simple [Cubit] that manages the [ThemeData] as its state.\n/// {@endtemplate}\nclass ThemeCubit extends Cubit<ThemeData> {\n  /// {@macro brightness_cubit}\n  ThemeCubit() : super(_lightTheme);\n\n  static final _lightTheme = ThemeData.light();\n\n  static final _darkTheme = ThemeData.dark();\n\n  /// Toggles the current brightness between light and dark.\n  void toggleTheme() {\n    emit(state.brightness == Brightness.dark ? _lightTheme : _darkTheme);\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/example/pubspec.yaml",
    "content": "name: example\ndescription: A new Flutter project.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.0.0 <4.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "packages/flutter_bloc/example/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  flutter_bloc:\n    path: ../"
  },
  {
    "path": "packages/flutter_bloc/example/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"example\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>example</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/flutter_bloc/example/web/manifest.json",
    "content": "{\n    \"name\": \"example\",\n    \"short_name\": \"example\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/flutter_bloc.dart",
    "content": "/// Flutter widgets that make it easy to implement the BLoC design pattern.\n/// Built to be used with the [bloc state management package](https://pub.dev/packages/bloc).\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary flutter_bloc;\n\nexport 'package:bloc/bloc.dart';\nexport 'package:provider/provider.dart'\n    show ProviderNotFoundException, ReadContext, SelectContext, WatchContext;\n\nexport './src/bloc_builder.dart';\nexport './src/bloc_consumer.dart';\nexport './src/bloc_listener.dart';\nexport './src/bloc_provider.dart';\nexport './src/bloc_selector.dart';\nexport './src/multi_bloc_listener.dart';\nexport './src/multi_bloc_provider.dart';\nexport './src/multi_repository_provider.dart';\nexport './src/repository_provider.dart';\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/bloc_builder.dart",
    "content": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\n/// Signature for the `builder` function which takes the `BuildContext` and\n/// [state] and is responsible for returning a widget which is to be rendered.\n/// This is analogous to the `builder` function in [StreamBuilder].\ntypedef BlocWidgetBuilder<S> = Widget Function(BuildContext context, S state);\n\n/// Signature for the `buildWhen` function which takes the previous `state` and\n/// the current `state` and is responsible for returning a [bool] which\n/// determines whether to rebuild [BlocBuilder] with the current `state`.\ntypedef BlocBuilderCondition<S> = bool Function(S previous, S current);\n\n/// {@template bloc_builder}\n/// [BlocBuilder] handles building a widget in response to new `states`.\n/// [BlocBuilder] is analogous to [StreamBuilder] but has simplified API to\n/// reduce the amount of boilerplate code needed as well as [bloc]-specific\n/// performance improvements.\n\n/// Please refer to [BlocListener] if you want to \"do\" anything in response to\n/// `state` changes such as navigation, showing a dialog, etc...\n///\n/// If the [bloc] parameter is omitted, [BlocBuilder] will automatically\n/// perform a lookup using [BlocProvider] and the current [BuildContext].\n///\n/// ```dart\n/// BlocBuilder<BlocA, BlocAState>(\n///   builder: (context, state) {\n///   // return widget here based on BlocA's state\n///   }\n/// )\n/// ```\n///\n/// Only specify the [bloc] if you wish to provide a [bloc] that is otherwise\n/// not accessible via [BlocProvider] and the current [BuildContext].\n///\n/// ```dart\n/// BlocBuilder<BlocA, BlocAState>(\n///   bloc: blocA,\n///   builder: (context, state) {\n///   // return widget here based on BlocA's state\n///   }\n/// )\n/// ```\n/// {@endtemplate}\n///\n/// {@template bloc_builder_build_when}\n/// An optional [buildWhen] can be implemented for more granular control over\n/// how often [BlocBuilder] rebuilds.\n/// [buildWhen] should only be used for performance optimizations as it\n/// provides no security about the state passed to the [builder] function.\n/// [buildWhen] will be invoked on each [bloc] `state` change.\n/// [buildWhen] takes the previous `state` and current `state` and must\n/// return a [bool] which determines whether or not the [builder] function will\n/// be invoked.\n/// The previous `state` will be initialized to the `state` of the [bloc] when\n/// the [BlocBuilder] is initialized.\n/// [buildWhen] is optional and if omitted, it will default to `true`.\n///\n/// ```dart\n/// BlocBuilder<BlocA, BlocAState>(\n///   buildWhen: (previous, current) {\n///     // return true/false to determine whether or not\n///     // to rebuild the widget with state\n///   },\n///   builder: (context, state) {\n///     // return widget here based on BlocA's state\n///   }\n/// )\n/// ```\n/// {@endtemplate}\nclass BlocBuilder<B extends StateStreamable<S>, S>\n    extends BlocBuilderBase<B, S> {\n  /// {@macro bloc_builder}\n  /// {@macro bloc_builder_build_when}\n  const BlocBuilder({\n    required this.builder,\n    Key? key,\n    B? bloc,\n    BlocBuilderCondition<S>? buildWhen,\n  }) : super(key: key, bloc: bloc, buildWhen: buildWhen);\n\n  /// The [builder] function which will be invoked on each widget build.\n  /// The [builder] takes the `BuildContext` and current `state` and\n  /// must return a widget.\n  /// This is analogous to the [builder] function in [StreamBuilder].\n  final BlocWidgetBuilder<S> builder;\n\n  @override\n  Widget build(BuildContext context, S state) => builder(context, state);\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties.add(\n      ObjectFlagProperty<BlocWidgetBuilder<S>>.has('builder', builder),\n    );\n  }\n}\n\n/// {@template bloc_builder_base}\n/// Base class for widgets that build themselves based on interaction with\n/// a specified [bloc].\n///\n/// A [BlocBuilderBase] is stateful and maintains the state of the interaction\n/// so far. The type of the state and how it is updated with each interaction\n/// is defined by sub-classes.\n/// {@endtemplate}\nabstract class BlocBuilderBase<B extends StateStreamable<S>, S>\n    extends StatefulWidget {\n  /// {@macro bloc_builder_base}\n  const BlocBuilderBase({Key? key, this.bloc, this.buildWhen})\n      : super(key: key);\n\n  /// The [bloc] that the [BlocBuilderBase] will interact with.\n  /// If omitted, [BlocBuilderBase] will automatically perform a lookup using\n  /// [BlocProvider] and the current `BuildContext`.\n  final B? bloc;\n\n  /// {@macro bloc_builder_build_when}\n  final BlocBuilderCondition<S>? buildWhen;\n\n  /// Returns a widget based on the `BuildContext` and current [state].\n  Widget build(BuildContext context, S state);\n\n  @override\n  State<BlocBuilderBase<B, S>> createState() => _BlocBuilderBaseState<B, S>();\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties\n      ..add(\n        ObjectFlagProperty<BlocBuilderCondition<S>?>.has(\n          'buildWhen',\n          buildWhen,\n        ),\n      )\n      ..add(DiagnosticsProperty<B?>('bloc', bloc));\n  }\n}\n\nclass _BlocBuilderBaseState<B extends StateStreamable<S>, S>\n    extends State<BlocBuilderBase<B, S>> {\n  late B _bloc;\n  late S _state;\n\n  @override\n  void initState() {\n    super.initState();\n    _bloc = widget.bloc ?? context.read<B>();\n    _state = _bloc.state;\n  }\n\n  @override\n  void didUpdateWidget(BlocBuilderBase<B, S> oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final oldBloc = oldWidget.bloc ?? context.read<B>();\n    final currentBloc = widget.bloc ?? oldBloc;\n    if (oldBloc != currentBloc) {\n      _bloc = currentBloc;\n      _state = _bloc.state;\n    }\n  }\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    final bloc = widget.bloc ?? context.read<B>();\n    if (_bloc != bloc) {\n      _bloc = bloc;\n      _state = _bloc.state;\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    if (widget.bloc == null) {\n      // Trigger a rebuild if the bloc reference has changed.\n      // See https://github.com/felangel/bloc/issues/2127.\n      context.select<B, bool>((bloc) => identical(_bloc, bloc));\n    }\n    return BlocListener<B, S>(\n      bloc: _bloc,\n      listenWhen: widget.buildWhen,\n      listener: (context, state) => setState(() => _state = state),\n      child: widget.build(context, _state),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/bloc_consumer.dart",
    "content": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\n/// {@template bloc_consumer}\n/// [BlocConsumer] exposes a [builder] and [listener] in order react to new\n/// states.\n/// [BlocConsumer] is analogous to a nested `BlocListener`\n/// and `BlocBuilder` but reduces the amount of boilerplate needed.\n/// [BlocConsumer] should only be used when it is necessary to both rebuild UI\n/// and execute other reactions to state changes in the [bloc].\n///\n/// [BlocConsumer] takes a required `BlocWidgetBuilder`\n/// and `BlocWidgetListener` and an optional [bloc],\n/// `BlocBuilderCondition`, and `BlocListenerCondition`.\n///\n/// If the [bloc] parameter is omitted, [BlocConsumer] will automatically\n/// perform a lookup using `BlocProvider` and the current `BuildContext`.\n///\n/// ```dart\n/// BlocConsumer<BlocA, BlocAState>(\n///   listener: (context, state) {\n///     // do stuff here based on BlocA's state\n///   },\n///   builder: (context, state) {\n///     // return widget here based on BlocA's state\n///   }\n/// )\n/// ```\n///\n/// An optional [listenWhen] and [buildWhen] can be implemented for more\n/// granular control over when [listener] and [builder] are called.\n/// The [listenWhen] and [buildWhen] will be invoked on each [bloc] `state`\n/// change.\n/// They each take the previous `state` and current `state` and must return\n/// a [bool] which determines whether or not the [builder] and/or [listener]\n/// function will be invoked.\n/// The previous `state` will be initialized to the `state` of the [bloc] when\n/// the [BlocConsumer] is initialized.\n/// [listenWhen] and [buildWhen] are optional and if they aren't implemented,\n/// they will default to `true`.\n///\n/// ```dart\n/// BlocConsumer<BlocA, BlocAState>(\n///   listenWhen: (previous, current) {\n///     // return true/false to determine whether or not\n///     // to invoke listener with state\n///   },\n///   listener: (context, state) {\n///     // do stuff here based on BlocA's state\n///   },\n///   buildWhen: (previous, current) {\n///     // return true/false to determine whether or not\n///     // to rebuild the widget with state\n///   },\n///   builder: (context, state) {\n///     // return widget here based on BlocA's state\n///   }\n/// )\n/// ```\n/// {@endtemplate}\nclass BlocConsumer<B extends StateStreamable<S>, S> extends StatefulWidget {\n  /// {@macro bloc_consumer}\n  const BlocConsumer({\n    required this.builder,\n    required this.listener,\n    Key? key,\n    this.bloc,\n    this.buildWhen,\n    this.listenWhen,\n  }) : super(key: key);\n\n  /// The [bloc] that the [BlocConsumer] will interact with.\n  /// If omitted, [BlocConsumer] will automatically perform a lookup using\n  /// `BlocProvider` and the current `BuildContext`.\n  final B? bloc;\n\n  /// The [builder] function which will be invoked on each widget build.\n  /// The [builder] takes the `BuildContext` and current `state` and\n  /// must return a widget.\n  /// This is analogous to the [builder] function in [StreamBuilder].\n  final BlocWidgetBuilder<S> builder;\n\n  /// Takes the `BuildContext` along with the [bloc] `state`\n  /// and is responsible for executing in response to `state` changes.\n  final BlocWidgetListener<S> listener;\n\n  /// Takes the previous `state` and the current `state` and is responsible for\n  /// returning a [bool] which determines whether or not to trigger\n  /// [builder] with the current `state`.\n  final BlocBuilderCondition<S>? buildWhen;\n\n  /// Takes the previous `state` and the current `state` and is responsible for\n  /// returning a [bool] which determines whether or not to call [listener] of\n  /// [BlocConsumer] with the current `state`.\n  final BlocListenerCondition<S>? listenWhen;\n\n  @override\n  State<BlocConsumer<B, S>> createState() => _BlocConsumerState<B, S>();\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties\n      ..add(DiagnosticsProperty<B?>('bloc', bloc))\n      ..add(ObjectFlagProperty<BlocWidgetBuilder<S>>.has('builder', builder))\n      ..add(ObjectFlagProperty<BlocWidgetListener<S>>.has('listener', listener))\n      ..add(\n        ObjectFlagProperty<BlocBuilderCondition<S>?>.has(\n          'buildWhen',\n          buildWhen,\n        ),\n      )\n      ..add(\n        ObjectFlagProperty<BlocListenerCondition<S>?>.has(\n          'listenWhen',\n          listenWhen,\n        ),\n      );\n  }\n}\n\nclass _BlocConsumerState<B extends StateStreamable<S>, S>\n    extends State<BlocConsumer<B, S>> {\n  late B _bloc;\n\n  @override\n  void initState() {\n    super.initState();\n    _bloc = widget.bloc ?? context.read<B>();\n  }\n\n  @override\n  void didUpdateWidget(BlocConsumer<B, S> oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final oldBloc = oldWidget.bloc ?? context.read<B>();\n    final currentBloc = widget.bloc ?? oldBloc;\n    if (oldBloc != currentBloc) _bloc = currentBloc;\n  }\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    final bloc = widget.bloc ?? context.read<B>();\n    if (_bloc != bloc) _bloc = bloc;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    if (widget.bloc == null) {\n      // Trigger a rebuild if the bloc reference has changed.\n      // See https://github.com/felangel/bloc/issues/2127.\n      context.select<B, bool>((bloc) => identical(_bloc, bloc));\n    }\n    return BlocBuilder<B, S>(\n      bloc: _bloc,\n      builder: widget.builder,\n      buildWhen: (previous, current) {\n        if (widget.listenWhen?.call(previous, current) ?? true) {\n          widget.listener(context, current);\n        }\n        return widget.buildWhen?.call(previous, current) ?? true;\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/bloc_listener.dart",
    "content": "import 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:provider/single_child_widget.dart';\n\n/// Signature for the `listener` function which takes the `BuildContext` along\n/// with the `state` and is responsible for executing in response to\n/// `state` changes.\ntypedef BlocWidgetListener<S> = void Function(BuildContext context, S state);\n\n/// Signature for the `listenWhen` function which takes the previous `state`\n/// and the current `state` and is responsible for returning a [bool] which\n/// determines whether or not to call [BlocWidgetListener] of [BlocListener]\n/// with the current `state`.\ntypedef BlocListenerCondition<S> = bool Function(S previous, S current);\n\n/// {@template bloc_listener}\n/// Takes a [BlocWidgetListener] and an optional [bloc] and invokes\n/// the [listener] in response to `state` changes in the [bloc].\n/// It should be used for functionality that needs to occur only in response to\n/// a `state` change such as navigation, showing a `SnackBar`, showing\n/// a `Dialog`, etc...\n/// The [listener] is guaranteed to only be called once for each `state` change\n/// unlike the `builder` in `BlocBuilder`.\n///\n/// If the [bloc] parameter is omitted, [BlocListener] will automatically\n/// perform a lookup using [BlocProvider] and the current `BuildContext`.\n///\n/// ```dart\n/// BlocListener<BlocA, BlocAState>(\n///   listener: (context, state) {\n///     // do stuff here based on BlocA's state\n///   },\n///   child: Container(),\n/// )\n/// ```\n/// Only specify the [bloc] if you wish to provide a [bloc] that is otherwise\n/// not accessible via [BlocProvider] and the current `BuildContext`.\n///\n/// ```dart\n/// BlocListener<BlocA, BlocAState>(\n///   value: blocA,\n///   listener: (context, state) {\n///     // do stuff here based on BlocA's state\n///   },\n///   child: Container(),\n/// )\n/// ```\n/// {@endtemplate}\n///\n/// {@template bloc_listener_listen_when}\n/// An optional [listenWhen] can be implemented for more granular control\n/// over when [listener] is called.\n/// [listenWhen] will be invoked on each [bloc] `state` change.\n/// [listenWhen] takes the previous `state` and current `state` and must\n/// return a [bool] which determines whether or not the [listener] function\n/// will be invoked.\n/// The previous `state` will be initialized to the `state` of the [bloc]\n/// when the [BlocListener] is initialized.\n/// [listenWhen] is optional and if omitted, it will default to `true`.\n///\n/// ```dart\n/// BlocListener<BlocA, BlocAState>(\n///   listenWhen: (previous, current) {\n///     // return true/false to determine whether or not\n///     // to invoke listener with state\n///   },\n///   listener: (context, state) {\n///     // do stuff here based on BlocA's state\n///   },\n///   child: Container(),\n/// )\n/// ```\n/// {@endtemplate}\nclass BlocListener<B extends StateStreamable<S>, S>\n    extends BlocListenerBase<B, S> {\n  /// {@macro bloc_listener}\n  /// {@macro bloc_listener_listen_when}\n  const BlocListener({\n    required BlocWidgetListener<S> listener,\n    Key? key,\n    B? bloc,\n    BlocListenerCondition<S>? listenWhen,\n    Widget? child,\n  }) : super(\n          key: key,\n          child: child,\n          listener: listener,\n          bloc: bloc,\n          listenWhen: listenWhen,\n        );\n}\n\n/// {@template bloc_listener_base}\n/// Base class for widgets that listen to state changes in a specified [bloc].\n///\n/// A [BlocListenerBase] is stateful and maintains the state subscription.\n/// The type of the state and what happens with each state change\n/// is defined by sub-classes.\n/// {@endtemplate}\nabstract class BlocListenerBase<B extends StateStreamable<S>, S>\n    extends SingleChildStatefulWidget {\n  /// {@macro bloc_listener_base}\n  const BlocListenerBase({\n    required this.listener,\n    Key? key,\n    this.bloc,\n    this.child,\n    this.listenWhen,\n  }) : super(key: key, child: child);\n\n  /// The widget which will be rendered as a descendant of the\n  /// [BlocListenerBase].\n  final Widget? child;\n\n  /// The [bloc] whose `state` will be listened to.\n  /// Whenever the [bloc]'s `state` changes, [listener] will be invoked.\n  final B? bloc;\n\n  /// The [BlocWidgetListener] which will be called on every `state` change.\n  /// This [listener] should be used for any code which needs to execute\n  /// in response to a `state` change.\n  final BlocWidgetListener<S> listener;\n\n  /// {@macro bloc_listener_listen_when}\n  final BlocListenerCondition<S>? listenWhen;\n\n  @override\n  SingleChildState<BlocListenerBase<B, S>> createState() =>\n      _BlocListenerBaseState<B, S>();\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties\n      ..add(DiagnosticsProperty<B?>('bloc', bloc))\n      ..add(ObjectFlagProperty<BlocWidgetListener<S>>.has('listener', listener))\n      ..add(\n        ObjectFlagProperty<BlocListenerCondition<S>?>.has(\n          'listenWhen',\n          listenWhen,\n        ),\n      );\n  }\n}\n\nclass _BlocListenerBaseState<B extends StateStreamable<S>, S>\n    extends SingleChildState<BlocListenerBase<B, S>> {\n  StreamSubscription<S>? _subscription;\n  late B _bloc;\n  late S _previousState;\n\n  @override\n  void initState() {\n    super.initState();\n    _bloc = widget.bloc ?? context.read<B>();\n    _previousState = _bloc.state;\n    _subscribe();\n  }\n\n  @override\n  void didUpdateWidget(BlocListenerBase<B, S> oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final oldBloc = oldWidget.bloc ?? context.read<B>();\n    final currentBloc = widget.bloc ?? oldBloc;\n    if (oldBloc != currentBloc) {\n      if (_subscription != null) {\n        _unsubscribe();\n        _bloc = currentBloc;\n        _previousState = _bloc.state;\n      }\n      _subscribe();\n    }\n  }\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    final bloc = widget.bloc ?? context.read<B>();\n    if (_bloc != bloc) {\n      if (_subscription != null) {\n        _unsubscribe();\n        _bloc = bloc;\n        _previousState = _bloc.state;\n      }\n      _subscribe();\n    }\n  }\n\n  @override\n  Widget buildWithChild(BuildContext context, Widget? child) {\n    if (widget.bloc == null) {\n      // Trigger a rebuild if the bloc reference has changed.\n      // See https://github.com/felangel/bloc/issues/2127.\n      context.select<B, bool>((bloc) => identical(_bloc, bloc));\n    }\n    return child ?? const SizedBox.shrink();\n  }\n\n  @override\n  void dispose() {\n    _unsubscribe();\n    super.dispose();\n  }\n\n  void _subscribe() {\n    _subscription = _bloc.stream.listen((state) {\n      if (!mounted) return;\n      if (widget.listenWhen?.call(_previousState, state) ?? true) {\n        widget.listener(context, state);\n      }\n      _previousState = state;\n    });\n  }\n\n  void _unsubscribe() {\n    _subscription?.cancel();\n    _subscription = null;\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/bloc_provider.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\nimport 'package:provider/single_child_widget.dart';\n\n/// {@template bloc_provider}\n/// Takes a `create` function that is responsible for\n/// creating the [Bloc] or [Cubit] and a [child] which will have access\n/// to the instance via `BlocProvider.of(context)`.\n/// It is used as a dependency injection (DI) widget so that a single instance\n/// of a [Bloc] or [Cubit] can be provided to multiple widgets within a subtree.\n///\n/// ```dart\n/// BlocProvider(\n///   create: (BuildContext context) => BlocA(),\n///   child: ChildA(),\n/// );\n/// ```\n///\n/// It automatically handles closing the instance when used with [Create].\n/// By default, `create` is called only when the instance is accessed.\n/// To override this behavior, set [lazy] to `false`.\n///\n/// ```dart\n/// BlocProvider(\n///   lazy: false,\n///   create: (BuildContext context) => BlocA(),\n///   child: ChildA(),\n/// );\n/// ```\n///\n/// {@endtemplate}\nclass BlocProvider<T extends StateStreamableSource<Object?>>\n    extends SingleChildStatelessWidget {\n  /// {@macro bloc_provider}\n  const BlocProvider({\n    required T Function(BuildContext context) create,\n    Key? key,\n    this.child,\n    this.lazy = true,\n  })  : _create = create,\n        _value = null,\n        super(key: key, child: child);\n\n  /// Takes a [value] and a [child] which will have access to the [value] via\n  /// `BlocProvider.of(context)`.\n  /// When `BlocProvider.value` is used, the [Bloc] or [Cubit]\n  /// will not be automatically closed.\n  /// As a result, `BlocProvider.value` should only be used for providing\n  /// existing instances to new subtrees.\n  ///\n  /// A new [Bloc] or [Cubit] should not be created in `BlocProvider.value`.\n  /// New instances should always be created using the\n  /// default constructor within the `create` function.\n  ///\n  /// ```dart\n  /// BlocProvider.value(\n  ///   value: BlocProvider.of<BlocA>(context),\n  ///   child: ScreenA(),\n  /// );\n  /// ```\n  const BlocProvider.value({\n    required T value,\n    Key? key,\n    this.child,\n  })  : _value = value,\n        _create = null,\n        lazy = true,\n        super(key: key, child: child);\n\n  /// Widget which will have access to the [Bloc] or [Cubit].\n  final Widget? child;\n\n  /// Whether the [Bloc] or [Cubit] should be created lazily.\n  /// Defaults to `true`.\n  final bool lazy;\n\n  final T Function(BuildContext context)? _create;\n\n  final T? _value;\n\n  /// Method that allows widgets to access a [Bloc] or [Cubit] instance\n  /// as long as their `BuildContext` contains a [BlocProvider] instance.\n  ///\n  /// If we want to access an instance of `BlocA` which was provided higher up\n  /// in the widget tree we can do so via:\n  ///\n  /// ```dart\n  /// BlocProvider.of<BlocA>(context);\n  /// ```\n  static T of<T extends StateStreamableSource<Object?>>(\n    BuildContext context, {\n    bool listen = false,\n  }) {\n    try {\n      return Provider.of<T>(context, listen: listen);\n    } on ProviderNotFoundException catch (e) {\n      if (e.valueType != T) rethrow;\n      throw FlutterError(\n        '''\n        BlocProvider.of() called with a context that does not contain a $T.\n        No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().\n\n        This can happen if the context you used comes from a widget above the BlocProvider.\n\n        The context used was: $context\n        ''',\n      );\n    }\n  }\n\n  @override\n  Widget buildWithChild(BuildContext context, Widget? child) {\n    assert(\n      child != null,\n      '$runtimeType used outside of MultiBlocProvider must specify a child',\n    );\n    final value = _value;\n    return value != null\n        ? InheritedProvider<T>.value(\n            value: value,\n            startListening: _startListening,\n            lazy: lazy,\n            child: child,\n          )\n        : InheritedProvider<T>(\n            create: _create,\n            dispose: (_, bloc) => bloc.close(),\n            startListening: _startListening,\n            lazy: lazy,\n            child: child,\n          );\n  }\n\n  static VoidCallback _startListening(\n    InheritedContext<StateStreamable<dynamic>?> e,\n    StateStreamable<dynamic> value,\n  ) {\n    final subscription = value.stream.listen(\n      (dynamic _) => e.markNeedsNotifyDependents(),\n    );\n    return subscription.cancel;\n  }\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties.add(DiagnosticsProperty<bool>('lazy', lazy));\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/bloc_selector.dart",
    "content": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\n/// Signature for the `selector` function which\n/// is responsible for returning a selected value, [T], based on [state].\ntypedef BlocWidgetSelector<S, T> = T Function(S state);\n\n/// {@template bloc_selector}\n/// [BlocSelector] is analogous to [BlocBuilder] but allows developers to\n/// filter updates by selecting a new value based on the bloc state.\n/// Unnecessary builds are prevented if the selected value does not change.\n///\n/// **Note**: the selected value must be immutable in order for [BlocSelector]\n/// to accurately determine whether [builder] should be called again.\n///\n/// ```dart\n/// BlocSelector<BlocA, BlocAState, SelectedState>(\n///   selector: (state) {\n///     // return selected state based on the provided state.\n///   },\n///   builder: (context, state) {\n///     // return widget here based on the selected state.\n///   },\n/// )\n/// ```\n/// {@endtemplate}\nclass BlocSelector<B extends StateStreamable<S>, S, T> extends StatefulWidget {\n  /// {@macro bloc_selector}\n  const BlocSelector({\n    required this.selector,\n    required this.builder,\n    Key? key,\n    this.bloc,\n  }) : super(key: key);\n\n  /// The [bloc] that the [BlocSelector] will interact with.\n  /// If omitted, [BlocSelector] will automatically perform a lookup using\n  /// [BlocProvider] and the current [BuildContext].\n  final B? bloc;\n\n  /// The [builder] function which will be invoked\n  /// when the selected state changes.\n  /// The [builder] takes the [BuildContext] and selected `state` and\n  /// must return a widget.\n  /// This is analogous to the [builder] function in [BlocBuilder].\n  final BlocWidgetBuilder<T> builder;\n\n  /// The [selector] function which will be invoked on each widget build\n  /// and is responsible for returning a selected value of type [T] based on\n  /// the current state.\n  final BlocWidgetSelector<S, T> selector;\n\n  @override\n  State<BlocSelector<B, S, T>> createState() => _BlocSelectorState<B, S, T>();\n\n  @override\n  void debugFillProperties(DiagnosticPropertiesBuilder properties) {\n    super.debugFillProperties(properties);\n    properties\n      ..add(DiagnosticsProperty<B?>('bloc', bloc))\n      ..add(ObjectFlagProperty<BlocWidgetBuilder<T>>.has('builder', builder))\n      ..add(\n        ObjectFlagProperty<BlocWidgetSelector<S, T>>.has(\n          'selector',\n          selector,\n        ),\n      );\n  }\n}\n\nclass _BlocSelectorState<B extends StateStreamable<S>, S, T>\n    extends State<BlocSelector<B, S, T>> {\n  late B _bloc;\n  late T _state;\n\n  @override\n  void initState() {\n    super.initState();\n    _bloc = widget.bloc ?? context.read<B>();\n    _state = widget.selector(_bloc.state);\n  }\n\n  @override\n  void didUpdateWidget(BlocSelector<B, S, T> oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    final oldBloc = oldWidget.bloc ?? context.read<B>();\n    final currentBloc = widget.bloc ?? oldBloc;\n    if (oldBloc != currentBloc) {\n      _bloc = currentBloc;\n      _state = widget.selector(_bloc.state);\n    } else if (oldWidget.selector != widget.selector) {\n      _state = widget.selector(_bloc.state);\n    }\n  }\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    final bloc = widget.bloc ?? context.read<B>();\n    if (_bloc != bloc) {\n      _bloc = bloc;\n      _state = widget.selector(_bloc.state);\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    if (widget.bloc == null) {\n      // Trigger a rebuild if the bloc reference has changed.\n      // See https://github.com/felangel/bloc/issues/2127.\n      context.select<B, bool>((bloc) => identical(_bloc, bloc));\n    }\n    return BlocListener<B, S>(\n      bloc: _bloc,\n      listener: (context, state) {\n        final selectedState = widget.selector(state);\n        if (_state != selectedState) setState(() => _state = selectedState);\n      },\n      child: widget.builder(context, _state),\n    );\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/multi_bloc_listener.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/src/bloc_listener.dart';\nimport 'package:provider/provider.dart';\nimport 'package:provider/single_child_widget.dart';\n\n/// {@template multi_bloc_listener}\n/// Merges multiple [BlocListener] widgets into one widget tree.\n///\n/// [MultiBlocListener] improves the readability and eliminates the need\n/// to nest multiple [BlocListener]s.\n///\n/// By using [MultiBlocListener] we can go from:\n///\n/// ```dart\n/// BlocListener<BlocA, BlocAState>(\n///   listener: (context, state) {},\n///   child: BlocListener<BlocB, BlocBState>(\n///     listener: (context, state) {},\n///     child: BlocListener<BlocC, BlocCState>(\n///       listener: (context, state) {},\n///       child: ChildA(),\n///     ),\n///   ),\n/// )\n/// ```\n///\n/// to:\n///\n/// ```dart\n/// MultiBlocListener(\n///   listeners: [\n///     BlocListener<BlocA, BlocAState>(\n///       listener: (context, state) {},\n///     ),\n///     BlocListener<BlocB, BlocBState>(\n///       listener: (context, state) {},\n///     ),\n///     BlocListener<BlocC, BlocCState>(\n///       listener: (context, state) {},\n///     ),\n///   ],\n///   child: ChildA(),\n/// )\n/// ```\n///\n/// [MultiBlocListener] converts the [BlocListener] list into a tree of nested\n/// [BlocListener] widgets.\n/// As a result, the only advantage of using [MultiBlocListener] is improved\n/// readability due to the reduction in nesting and boilerplate.\n/// {@endtemplate}\nclass MultiBlocListener extends MultiProvider {\n  /// {@macro multi_bloc_listener}\n  MultiBlocListener({\n    required List<SingleChildWidget> listeners,\n    required Widget child,\n    Key? key,\n  }) : super(key: key, providers: listeners, child: child);\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/multi_bloc_provider.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/src/bloc_provider.dart';\nimport 'package:provider/provider.dart';\nimport 'package:provider/single_child_widget.dart';\n\n/// {@template multi_bloc_provider}\n/// Merges multiple [BlocProvider] widgets into one widget tree.\n///\n/// [MultiBlocProvider] improves the readability and eliminates the need\n/// to nest multiple [BlocProvider]s.\n///\n/// By using [MultiBlocProvider] we can go from:\n///\n/// ```dart\n/// BlocProvider<BlocA>(\n///   create: (BuildContext context) => BlocA(),\n///   child: BlocProvider<BlocB>(\n///     create: (BuildContext context) => BlocB(),\n///     child: BlocProvider<BlocC>(\n///       create: (BuildContext context) => BlocC(),\n///       child: ChildA(),\n///     )\n///   )\n/// )\n/// ```\n///\n/// to:\n///\n/// ```dart\n/// MultiBlocProvider(\n///   providers: [\n///     BlocProvider<BlocA>(\n///       create: (BuildContext context) => BlocA(),\n///     ),\n///     BlocProvider<BlocB>(\n///       create: (BuildContext context) => BlocB(),\n///     ),\n///     BlocProvider<BlocC>(\n///       create: (BuildContext context) => BlocC(),\n///     ),\n///   ],\n///   child: ChildA(),\n/// )\n/// ```\n///\n/// [MultiBlocProvider] converts the [BlocProvider] list into a tree of nested\n/// [BlocProvider] widgets.\n/// As a result, the only advantage of using [MultiBlocProvider] is improved\n/// readability due to the reduction in nesting and boilerplate.\n/// {@endtemplate}\nclass MultiBlocProvider extends MultiProvider {\n  /// {@macro multi_bloc_provider}\n  MultiBlocProvider({\n    required List<SingleChildWidget> providers,\n    required Widget child,\n    Key? key,\n  }) : super(key: key, providers: providers, child: child);\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/multi_repository_provider.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:flutter_bloc/src/repository_provider.dart';\nimport 'package:provider/provider.dart';\nimport 'package:provider/single_child_widget.dart';\n\n/// {@template multi_repository_provider}\n/// Merges multiple [RepositoryProvider] widgets into one widget tree.\n///\n/// [MultiRepositoryProvider] improves the readability and eliminates the need\n/// to nest multiple [RepositoryProvider]s.\n///\n/// By using [MultiRepositoryProvider] we can go from:\n///\n/// ```dart\n/// RepositoryProvider<RepositoryA>(\n///   create: (context) => RepositoryA(),\n///   child: RepositoryProvider<RepositoryB>(\n///     create: (context) => RepositoryB(),\n///     child: RepositoryProvider<RepositoryC>(\n///       create: (context) => RepositoryC(),\n///       child: ChildA(),\n///     )\n///   )\n/// )\n/// ```\n///\n/// to:\n///\n/// ```dart\n/// MultiRepositoryProvider(\n///   providers: [\n///     RepositoryProvider<RepositoryA>(create: (context) => RepositoryA()),\n///     RepositoryProvider<RepositoryB>(create: (context) => RepositoryB()),\n///     RepositoryProvider<RepositoryC>(create: (context) => RepositoryC()),\n///   ],\n///   child: ChildA(),\n/// )\n/// ```\n///\n/// [MultiRepositoryProvider] converts the [RepositoryProvider] list into a tree\n/// of nested [RepositoryProvider] widgets.\n/// As a result, the only advantage of using [MultiRepositoryProvider] is\n/// improved readability due to the reduction in nesting and boilerplate.\n/// {@endtemplate}\nclass MultiRepositoryProvider extends MultiProvider {\n  /// {@macro multi_repository_provider}\n  MultiRepositoryProvider({\n    required List<SingleChildWidget> providers,\n    required Widget child,\n    Key? key,\n  }) : super(key: key, providers: providers, child: child);\n}\n"
  },
  {
    "path": "packages/flutter_bloc/lib/src/repository_provider.dart",
    "content": "import 'package:flutter/widgets.dart';\nimport 'package:provider/provider.dart';\n\n/// {@template repository_provider}\n/// Takes a `create` function that is responsible for creating the repository\n/// and a `child` which will have access to the repository via\n/// `RepositoryProvider.of(context)`.\n/// It is used as a dependency injection (DI) widget so that a single instance\n/// of a repository can be provided to multiple widgets within a subtree.\n///\n/// ```dart\n/// RepositoryProvider(\n///   create: (context) => RepositoryA(),\n///   child: ChildA(),\n/// );\n/// ```\n///\n/// Lazily creates the repository unless `lazy` is set to `false`.\n///\n/// ```dart\n/// RepositoryProvider(\n///   lazy: false,\n///   create: (context) => RepositoryA(),\n///   child: ChildA(),\n/// );\n/// ```\n///\n/// Repositories that manage resources which must be disposed\n/// can do so via the `dispose` callback.\n///\n/// ```dart\n/// RepositoryProvider(\n///  create: (context) => RepositoryA(),\n///  dispose: (repository) => repository.dispose(),\n///  child: ChildA(),\n///);\n/// ```\n/// {@endtemplate}\nclass RepositoryProvider<T> extends Provider<T> {\n  /// {@macro repository_provider}\n  RepositoryProvider({\n    required T Function(BuildContext context) create,\n    void Function(T value)? dispose,\n    Key? key,\n    Widget? child,\n    bool? lazy,\n  }) : super(\n          key: key,\n          create: create,\n          dispose: (_, value) => dispose?.call(value),\n          child: child,\n          lazy: lazy,\n        );\n\n  /// Takes a repository and a [child] which will have access to the repository.\n  /// A new repository should not be created in `RepositoryProvider.value`.\n  /// Repositories should always be created using the default constructor\n  /// within the [Create] function.\n  RepositoryProvider.value({\n    required T value,\n    Key? key,\n    Widget? child,\n  }) : super.value(\n          key: key,\n          value: value,\n          child: child,\n        );\n\n  /// Method that allows widgets to access a repository instance as long as\n  /// their `BuildContext` contains a [RepositoryProvider] instance.\n  static T of<T>(BuildContext context, {bool listen = false}) {\n    try {\n      return Provider.of<T>(context, listen: listen);\n    } on ProviderNotFoundException catch (e) {\n      if (e.valueType != T) rethrow;\n      throw FlutterError(\n        '''\n        RepositoryProvider.of() called with a context that does not contain a repository of type $T.\n        No ancestor could be found starting from the context that was passed to RepositoryProvider.of<$T>().\n\n        This can happen if the context you used comes from a widget above the RepositoryProvider.\n\n        The context used was: $context\n        ''',\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/flutter_bloc/pubspec.yaml",
    "content": "name: flutter_bloc\ndescription: Flutter widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.\nversion: 9.1.1\nrepository: https://github.com/felangel/bloc/tree/master/packages/flutter_bloc\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://bloclibrary.dev\ndocumentation: https://bloclibrary.dev/getting-started\ntopics: [bloc, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  flutter:\n    sdk: flutter\n  provider: ^6.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  flutter_test:\n    sdk: flutter\n\nscreenshots:\n  - description: The flutter bloc package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/flutter_bloc/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/flutter_bloc/test/bloc_builder_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MyThemeApp extends StatefulWidget {\n  const MyThemeApp({\n    required Cubit<ThemeData> themeCubit,\n    required void Function() onBuild,\n    Key? key,\n  })  : _themeCubit = themeCubit,\n        _onBuild = onBuild,\n        super(key: key);\n\n  final Cubit<ThemeData> _themeCubit;\n  final void Function() _onBuild;\n\n  @override\n  State<MyThemeApp> createState() => MyThemeAppState();\n}\n\nclass MyThemeAppState extends State<MyThemeApp> {\n  MyThemeAppState();\n\n  @override\n  void initState() {\n    super.initState();\n    _themeCubit = widget._themeCubit;\n    _onBuild = widget._onBuild;\n  }\n\n  late Cubit<ThemeData> _themeCubit;\n  late final void Function() _onBuild;\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<Cubit<ThemeData>, ThemeData>(\n      bloc: _themeCubit,\n      builder: (context, theme) {\n        _onBuild();\n        return MaterialApp(\n          key: const Key('material_app'),\n          theme: theme,\n          home: Column(\n            children: [\n              ElevatedButton(\n                key: const Key('raised_button_1'),\n                child: const SizedBox(),\n                onPressed: () {\n                  setState(() => _themeCubit = DarkThemeCubit());\n                },\n              ),\n              ElevatedButton(\n                key: const Key('raised_button_2'),\n                child: const SizedBox(),\n                onPressed: () {\n                  // ignore: no_self_assignments\n                  setState(() => _themeCubit = _themeCubit);\n                },\n              ),\n            ],\n          ),\n        );\n      },\n    );\n  }\n}\n\nclass ThemeCubit extends Cubit<ThemeData> {\n  ThemeCubit() : super(ThemeData.light());\n\n  void setDarkTheme() => emit(ThemeData.dark());\n  void setLightTheme() => emit(ThemeData.light());\n}\n\nclass DarkThemeCubit extends Cubit<ThemeData> {\n  DarkThemeCubit() : super(ThemeData.dark());\n\n  void setLightTheme() => emit(ThemeData.light());\n}\n\nclass MyCounterApp extends StatefulWidget {\n  const MyCounterApp({Key? key}) : super(key: key);\n\n  @override\n  State<StatefulWidget> createState() => MyCounterAppState();\n}\n\nclass MyCounterAppState extends State<MyCounterApp> {\n  final CounterCubit _cubit = CounterCubit();\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Scaffold(\n        key: const Key('myCounterApp'),\n        body: Column(\n          children: <Widget>[\n            BlocBuilder<CounterCubit, int>(\n              bloc: _cubit,\n              buildWhen: (previousState, state) {\n                return (previousState + state) % 3 == 0;\n              },\n              builder: (context, count) {\n                return Text(\n                  '$count',\n                  key: const Key('myCounterAppTextCondition'),\n                );\n              },\n            ),\n            BlocBuilder<CounterCubit, int>(\n              bloc: _cubit,\n              builder: (context, count) {\n                return Text(\n                  '$count',\n                  key: const Key('myCounterAppText'),\n                );\n              },\n            ),\n            ElevatedButton(\n              key: const Key('myCounterAppIncrementButton'),\n              onPressed: _cubit.increment,\n              child: const SizedBox(),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({int seed = 0}) : super(seed);\n\n  void increment() => emit(state + 1);\n}\n\nvoid main() {\n  group('BlocBuilder', () {\n    testWidgets('passes initial state to widget', (tester) async {\n      final themeCubit = ThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MyThemeApp(themeCubit: themeCubit, onBuild: () => numBuilds++),\n      );\n\n      final materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.light());\n      expect(numBuilds, 1);\n    });\n\n    testWidgets('receives events and sends state updates to widget',\n        (tester) async {\n      final themeCubit = ThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MyThemeApp(themeCubit: themeCubit, onBuild: () => numBuilds++),\n      );\n\n      themeCubit.setDarkTheme();\n\n      await tester.pumpAndSettle();\n\n      final materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 2);\n    });\n\n    testWidgets(\n        'infers the cubit from the context if the cubit is not provided',\n        (tester) async {\n      final themeCubit = ThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: themeCubit,\n          child: BlocBuilder<ThemeCubit, ThemeData>(\n            builder: (context, theme) {\n              numBuilds++;\n              return MaterialApp(\n                key: const Key('material_app'),\n                theme: theme,\n                home: const SizedBox(),\n              );\n            },\n          ),\n        ),\n      );\n\n      themeCubit.setDarkTheme();\n\n      await tester.pumpAndSettle();\n\n      var materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 2);\n\n      themeCubit.setLightTheme();\n\n      await tester.pumpAndSettle();\n\n      materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.light());\n      expect(numBuilds, 3);\n    });\n\n    testWidgets('updates cubit and performs new lookup when widget is updated',\n        (tester) async {\n      final themeCubit = ThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        StatefulBuilder(\n          builder: (context, setState) => BlocProvider.value(\n            value: themeCubit,\n            child: BlocBuilder<ThemeCubit, ThemeData>(\n              builder: (context, theme) {\n                numBuilds++;\n                return MaterialApp(\n                  key: const Key('material_app'),\n                  theme: theme,\n                  home: ElevatedButton(\n                    child: const SizedBox(),\n                    onPressed: () => setState(() {}),\n                  ),\n                );\n              },\n            ),\n          ),\n        ),\n      );\n\n      await tester.tap(find.byType(ElevatedButton));\n      await tester.pumpAndSettle();\n\n      final materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.light());\n      expect(numBuilds, 2);\n    });\n\n    testWidgets(\n        'updates when the cubit is changed at runtime to a different cubit and '\n        'unsubscribes from old cubit', (tester) async {\n      final themeCubit = ThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MyThemeApp(themeCubit: themeCubit, onBuild: () => numBuilds++),\n      );\n\n      await tester.pumpAndSettle();\n\n      var materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.light());\n      expect(numBuilds, 1);\n\n      await tester.tap(find.byKey(const Key('raised_button_1')));\n      await tester.pumpAndSettle();\n\n      materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 2);\n\n      themeCubit.setLightTheme();\n      await tester.pumpAndSettle();\n\n      materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 2);\n    });\n\n    testWidgets(\n        'does not update when the cubit is changed at runtime to same cubit '\n        'and stays subscribed to current cubit', (tester) async {\n      final themeCubit = DarkThemeCubit();\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MyThemeApp(themeCubit: themeCubit, onBuild: () => numBuilds++),\n      );\n\n      await tester.pumpAndSettle();\n\n      var materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 1);\n\n      await tester.tap(find.byKey(const Key('raised_button_2')));\n      await tester.pumpAndSettle();\n\n      materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 2);\n\n      themeCubit.setLightTheme();\n      await tester.pumpAndSettle();\n\n      materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.light());\n      expect(numBuilds, 3);\n    });\n\n    testWidgets('shows latest state instead of initial state', (tester) async {\n      final themeCubit = ThemeCubit()..setDarkTheme();\n      await tester.pumpAndSettle();\n\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MyThemeApp(themeCubit: themeCubit, onBuild: () => numBuilds++),\n      );\n\n      await tester.pumpAndSettle();\n\n      final materialApp = tester.widget<MaterialApp>(\n        find.byKey(const Key('material_app')),\n      );\n\n      expect(materialApp.theme, ThemeData.dark());\n      expect(numBuilds, 1);\n    });\n\n    testWidgets('with buildWhen only rebuilds when buildWhen evaluates to true',\n        (tester) async {\n      await tester.pumpWidget(const MyCounterApp());\n      await tester.pumpAndSettle();\n\n      expect(find.byKey(const Key('myCounterApp')), findsOneWidget);\n\n      final incrementButtonFinder =\n          find.byKey(const Key('myCounterAppIncrementButton'));\n      expect(incrementButtonFinder, findsOneWidget);\n\n      final counterText1 =\n          tester.widget<Text>(find.byKey(const Key('myCounterAppText')));\n      expect(counterText1.data, '0');\n\n      final conditionalCounterText1 = tester\n          .widget<Text>(find.byKey(const Key('myCounterAppTextCondition')));\n      expect(conditionalCounterText1.data, '0');\n\n      await tester.tap(incrementButtonFinder);\n      await tester.pumpAndSettle();\n\n      final counterText2 =\n          tester.widget<Text>(find.byKey(const Key('myCounterAppText')));\n      expect(counterText2.data, '1');\n\n      final conditionalCounterText2 = tester\n          .widget<Text>(find.byKey(const Key('myCounterAppTextCondition')));\n      expect(conditionalCounterText2.data, '0');\n\n      await tester.tap(incrementButtonFinder);\n      await tester.pumpAndSettle();\n\n      final counterText3 =\n          tester.widget<Text>(find.byKey(const Key('myCounterAppText')));\n      expect(counterText3.data, '2');\n\n      final conditionalCounterText3 = tester\n          .widget<Text>(find.byKey(const Key('myCounterAppTextCondition')));\n      expect(conditionalCounterText3.data, '2');\n\n      await tester.tap(incrementButtonFinder);\n      await tester.pumpAndSettle();\n\n      final counterText4 =\n          tester.widget<Text>(find.byKey(const Key('myCounterAppText')));\n      expect(counterText4.data, '3');\n\n      final conditionalCounterText4 = tester\n          .widget<Text>(find.byKey(const Key('myCounterAppTextCondition')));\n      expect(conditionalCounterText4.data, '2');\n    });\n\n    testWidgets('calls buildWhen and builder with correct state',\n        (tester) async {\n      final buildWhenPreviousState = <int>[];\n      final buildWhenCurrentState = <int>[];\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      await tester.pumpWidget(\n        BlocBuilder<CounterCubit, int>(\n          bloc: counterCubit,\n          buildWhen: (previous, state) {\n            if (state.isEven) {\n              buildWhenPreviousState.add(previous);\n              buildWhenCurrentState.add(state);\n              return true;\n            }\n            return false;\n          },\n          builder: (_, state) {\n            states.add(state);\n            return const SizedBox();\n          },\n        ),\n      );\n      await tester.pump();\n      counterCubit\n        ..increment()\n        ..increment()\n        ..increment();\n      await tester.pumpAndSettle();\n\n      expect(states, [0, 2]);\n      expect(buildWhenPreviousState, [1]);\n      expect(buildWhenCurrentState, [2]);\n    });\n\n    testWidgets(\n        'does not rebuild with latest state when '\n        'buildWhen is false and widget is updated', (tester) async {\n      const key = Key('__target__');\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: StatefulBuilder(\n            builder: (context, setState) => BlocBuilder<CounterCubit, int>(\n              bloc: counterCubit,\n              buildWhen: (previous, state) => state.isEven,\n              builder: (_, state) {\n                states.add(state);\n                return ElevatedButton(\n                  key: key,\n                  child: const SizedBox(),\n                  onPressed: () => setState(() {}),\n                );\n              },\n            ),\n          ),\n        ),\n      );\n      await tester.pump();\n      counterCubit\n        ..increment()\n        ..increment()\n        ..increment();\n      await tester.pumpAndSettle();\n      expect(states, [0, 2]);\n\n      await tester.tap(find.byKey(key));\n      await tester.pumpAndSettle();\n      expect(states, [0, 2, 2]);\n    });\n\n    testWidgets('rebuilds when provided bloc is changed', (tester) async {\n      final firstCounterCubit = CounterCubit();\n      final secondCounterCubit = CounterCubit(seed: 100);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: firstCounterCubit,\n            child: BlocBuilder<CounterCubit, int>(\n              builder: (context, state) => Text('Count $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('Count 0'), findsOneWidget);\n\n      firstCounterCubit.increment();\n      await tester.pumpAndSettle();\n      expect(find.text('Count 1'), findsOneWidget);\n      expect(find.text('Count 0'), findsNothing);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: secondCounterCubit,\n            child: BlocBuilder<CounterCubit, int>(\n              builder: (context, state) => Text('Count $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('Count 100'), findsOneWidget);\n      expect(find.text('Count 1'), findsNothing);\n\n      secondCounterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('Count 101'), findsOneWidget);\n    });\n\n    testWidgets('overrides debugFillProperties', (tester) async {\n      final builder = DiagnosticPropertiesBuilder();\n\n      BlocBuilder(\n        bloc: CounterCubit(),\n        builder: (context, state) => const SizedBox(),\n        buildWhen: (previous, current) => previous != current,\n      ).debugFillProperties(builder);\n\n      final description = builder.properties\n          .where((node) => !node.isFiltered(DiagnosticLevel.info))\n          .map((node) => node.toString())\n          .toList();\n\n      expect(\n        description,\n        <String>[\n          'has buildWhen',\n          \"bloc: Instance of 'CounterCubit'\",\n          'has builder',\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/bloc_consumer_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({int seed = 0}) : super(seed);\n\n  void increment() => emit(state + 1);\n}\n\nvoid main() {\n  group('BlocConsumer', () {\n    testWidgets(\n        'accesses the bloc directly and passes initial state to builder and '\n        'nothing to listener', (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocConsumer<CounterCubit, int>(\n              bloc: counterCubit,\n              builder: (context, state) {\n                return Text('State: $state');\n              },\n              listener: (_, state) {\n                listenerStates.add(state);\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(listenerStates, isEmpty);\n    });\n\n    testWidgets(\n        'accesses the bloc directly '\n        'and passes multiple states to builder and listener', (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocConsumer<CounterCubit, int>(\n              bloc: counterCubit,\n              builder: (context, state) {\n                return Text('State: $state');\n              },\n              listener: (_, state) {\n                listenerStates.add(state);\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(listenerStates, isEmpty);\n      counterCubit.increment();\n      await tester.pump();\n      expect(find.text('State: 1'), findsOneWidget);\n      expect(listenerStates, [1]);\n    });\n\n    testWidgets(\n        'accesses the bloc via context and passes initial state to builder',\n        (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      await tester.pumpWidget(\n        BlocProvider<CounterCubit>.value(\n          value: counterCubit,\n          child: MaterialApp(\n            home: Scaffold(\n              body: BlocConsumer<CounterCubit, int>(\n                bloc: counterCubit,\n                builder: (context, state) {\n                  return Text('State: $state');\n                },\n                listener: (_, state) {\n                  listenerStates.add(state);\n                },\n              ),\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(listenerStates, isEmpty);\n    });\n\n    testWidgets(\n        'accesses the bloc via context and passes multiple states to builder',\n        (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocConsumer<CounterCubit, int>(\n              bloc: counterCubit,\n              builder: (context, state) {\n                return Text('State: $state');\n              },\n              listener: (_, state) {\n                listenerStates.add(state);\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(listenerStates, isEmpty);\n      counterCubit.increment();\n      await tester.pump();\n      expect(find.text('State: 1'), findsOneWidget);\n      expect(listenerStates, [1]);\n    });\n\n    testWidgets('does not trigger rebuilds when buildWhen evaluates to false',\n        (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      final builderStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocConsumer<CounterCubit, int>(\n              bloc: counterCubit,\n              buildWhen: (previous, current) => (previous + current) % 3 == 0,\n              builder: (context, state) {\n                builderStates.add(state);\n                return Text('State: $state');\n              },\n              listener: (_, state) {\n                listenerStates.add(state);\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, isEmpty);\n\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, [1]);\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('State: 2'), findsOneWidget);\n      expect(builderStates, [0, 2]);\n      expect(listenerStates, [1, 2]);\n    });\n\n    testWidgets(\n        'does not trigger rebuilds when '\n        'buildWhen evaluates to false (inferred bloc)', (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      final builderStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider.value(\n              value: counterCubit,\n              child: BlocConsumer<CounterCubit, int>(\n                buildWhen: (previous, current) => (previous + current) % 3 == 0,\n                builder: (context, state) {\n                  builderStates.add(state);\n                  return Text('State: $state');\n                },\n                listener: (_, state) {\n                  listenerStates.add(state);\n                },\n              ),\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, isEmpty);\n\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, [1]);\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('State: 2'), findsOneWidget);\n      expect(builderStates, [0, 2]);\n      expect(listenerStates, [1, 2]);\n    });\n\n    testWidgets('updates when cubit/bloc reference has changed',\n        (tester) async {\n      const buttonKey = Key('__button__');\n      var counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      final builderStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: StatefulBuilder(\n              builder: (context, setState) {\n                return BlocConsumer<CounterCubit, int>(\n                  bloc: counterCubit,\n                  builder: (context, state) {\n                    builderStates.add(state);\n                    return TextButton(\n                      key: buttonKey,\n                      onPressed: () => setState(() {}),\n                      child: Text('State: $state'),\n                    );\n                  },\n                  listener: (_, state) {\n                    listenerStates.add(state);\n                  },\n                );\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, isEmpty);\n\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(find.text('State: 1'), findsOneWidget);\n      expect(builderStates, [0, 1]);\n      expect(listenerStates, [1]);\n\n      counterCubit = CounterCubit();\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pumpAndSettle();\n\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0, 1, 0]);\n      expect(listenerStates, [1]);\n    });\n\n    testWidgets('does not trigger listen when listenWhen evaluates to false',\n        (tester) async {\n      final counterCubit = CounterCubit();\n      final listenerStates = <int>[];\n      final builderStates = <int>[];\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocConsumer<CounterCubit, int>(\n              bloc: counterCubit,\n              builder: (context, state) {\n                builderStates.add(state);\n                return Text('State: $state');\n              },\n              listenWhen: (previous, current) => (previous + current) % 3 == 0,\n              listener: (_, state) {\n                listenerStates.add(state);\n              },\n            ),\n          ),\n        ),\n      );\n      expect(find.text('State: 0'), findsOneWidget);\n      expect(builderStates, [0]);\n      expect(listenerStates, isEmpty);\n\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(find.text('State: 1'), findsOneWidget);\n      expect(builderStates, [0, 1]);\n      expect(listenerStates, isEmpty);\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('State: 2'), findsOneWidget);\n      expect(builderStates, [0, 1, 2]);\n      expect(listenerStates, [2]);\n    });\n\n    testWidgets(\n        'calls buildWhen/listenWhen and builder/listener with correct states',\n        (tester) async {\n      final buildWhenPreviousState = <int>[];\n      final buildWhenCurrentState = <int>[];\n      final buildStates = <int>[];\n      final listenWhenPreviousState = <int>[];\n      final listenWhenCurrentState = <int>[];\n      final listenStates = <int>[];\n      final counterCubit = CounterCubit();\n      await tester.pumpWidget(\n        BlocConsumer<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (previous, current) {\n            if (current % 3 == 0) {\n              listenWhenPreviousState.add(previous);\n              listenWhenCurrentState.add(current);\n              return true;\n            }\n            return false;\n          },\n          listener: (_, state) {\n            listenStates.add(state);\n          },\n          buildWhen: (previous, current) {\n            if (current.isEven) {\n              buildWhenPreviousState.add(previous);\n              buildWhenCurrentState.add(current);\n              return true;\n            }\n            return false;\n          },\n          builder: (_, state) {\n            buildStates.add(state);\n            return const SizedBox();\n          },\n        ),\n      );\n      await tester.pump();\n      counterCubit\n        ..increment()\n        ..increment()\n        ..increment();\n      await tester.pumpAndSettle();\n\n      expect(buildStates, [0, 2]);\n      expect(buildWhenPreviousState, [1]);\n      expect(buildWhenCurrentState, [2]);\n\n      expect(listenStates, [3]);\n      expect(listenWhenPreviousState, [2]);\n      expect(listenWhenCurrentState, [3]);\n    });\n\n    testWidgets(\n        'rebuilds and updates subscription '\n        'when provided bloc is changed', (tester) async {\n      final firstCounterCubit = CounterCubit();\n      final secondCounterCubit = CounterCubit(seed: 100);\n\n      final states = <int>[];\n      const expectedStates = [1, 101];\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: firstCounterCubit,\n            child: BlocConsumer<CounterCubit, int>(\n              listener: (_, state) => states.add(state),\n              builder: (context, state) => Text('Count $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('Count 0'), findsOneWidget);\n\n      firstCounterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('Count 1'), findsOneWidget);\n      expect(find.text('Count 0'), findsNothing);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: secondCounterCubit,\n            child: BlocConsumer<CounterCubit, int>(\n              listener: (_, state) => states.add(state),\n              builder: (context, state) => Text('Count $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('Count 100'), findsOneWidget);\n      expect(find.text('Count 1'), findsNothing);\n\n      secondCounterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('Count 101'), findsOneWidget);\n      expect(states, expectedStates);\n    });\n\n    testWidgets('overrides debugFillProperties', (tester) async {\n      final builder = DiagnosticPropertiesBuilder();\n\n      BlocConsumer(\n        bloc: CounterCubit(),\n        buildWhen: (previous, current) => previous != current,\n        builder: (context, state) => const SizedBox(),\n        listener: (context, state) {},\n        listenWhen: (previous, current) => previous != current,\n      ).debugFillProperties(builder);\n\n      final description = builder.properties\n          .where((node) => !node.isFiltered(DiagnosticLevel.info))\n          .map((node) => node.toString())\n          .toList();\n\n      expect(\n        description,\n        <String>[\n          \"bloc: Instance of 'CounterCubit'\",\n          'has builder',\n          'has listener',\n          'has buildWhen',\n          'has listenWhen',\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/bloc_listener_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({int seed = 0}) : super(seed);\n\n  void increment() => emit(state + 1);\n}\n\nclass MyApp extends StatefulWidget {\n  const MyApp({Key? key, this.onListenerCalled}) : super(key: key);\n\n  final BlocWidgetListener<int>? onListenerCalled;\n\n  @override\n  State<MyApp> createState() => _MyAppState();\n}\n\nclass _MyAppState extends State<MyApp> {\n  late CounterCubit _counterCubit;\n\n  @override\n  void initState() {\n    super.initState();\n    _counterCubit = CounterCubit();\n  }\n\n  @override\n  void dispose() {\n    _counterCubit.close();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Scaffold(\n        body: BlocListener<CounterCubit, int>(\n          bloc: _counterCubit,\n          listener: (context, state) {\n            widget.onListenerCalled?.call(context, state);\n          },\n          child: Column(\n            children: [\n              ElevatedButton(\n                key: const Key('cubit_listener_reset_button'),\n                child: const SizedBox(),\n                onPressed: () {\n                  setState(() => _counterCubit = CounterCubit());\n                },\n              ),\n              ElevatedButton(\n                key: const Key('cubit_listener_noop_button'),\n                child: const SizedBox(),\n                onPressed: () {\n                  // ignore: no_self_assignments\n                  setState(() => _counterCubit = _counterCubit);\n                },\n              ),\n              ElevatedButton(\n                key: const Key('cubit_listener_increment_button'),\n                child: const SizedBox(),\n                onPressed: () => _counterCubit.increment(),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n\nvoid main() {\n  group('BlocListener', () {\n    testWidgets('does nothing when child is not specified', (tester) async {\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: CounterCubit(),\n          listener: (context, state) {},\n        ),\n      );\n      expect(tester.takeException(), isNull);\n    });\n\n    testWidgets('renders child when specified', (tester) async {\n      const targetKey = Key('cubit_listener_container');\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: CounterCubit(),\n          listener: (_, __) {},\n          child: const SizedBox(key: targetKey),\n        ),\n      );\n      expect(find.byKey(targetKey), findsOneWidget);\n    });\n\n    testWidgets('calls listener on single state change', (tester) async {\n      final counterCubit = CounterCubit();\n      final states = <int>[];\n      const expectedStates = [1];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n      expect(states, expectedStates);\n    });\n\n    testWidgets('calls listener on multiple state change', (tester) async {\n      final counterCubit = CounterCubit();\n      final states = <int>[];\n      const expectedStates = [1, 2];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      expect(states, expectedStates);\n    });\n\n    testWidgets(\n        'updates when the cubit is changed at runtime to a different cubit '\n        'and unsubscribes from old cubit', (tester) async {\n      var listenerCallCount = 0;\n      int? latestState;\n      final incrementFinder = find.byKey(\n        const Key('cubit_listener_increment_button'),\n      );\n      final resetCubitFinder = find.byKey(\n        const Key('cubit_listener_reset_button'),\n      );\n      await tester.pumpWidget(\n        MyApp(\n          onListenerCalled: (_, state) {\n            listenerCallCount++;\n            latestState = state;\n          },\n        ),\n      );\n\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 1);\n      expect(latestState, 1);\n\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 2);\n      expect(latestState, 2);\n\n      await tester.tap(resetCubitFinder);\n      await tester.pump();\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 3);\n      expect(latestState, 1);\n    });\n\n    testWidgets(\n        'does not update when the cubit is changed at runtime to same cubit '\n        'and stays subscribed to current cubit', (tester) async {\n      var listenerCallCount = 0;\n      int? latestState;\n      final incrementFinder = find.byKey(\n        const Key('cubit_listener_increment_button'),\n      );\n      final noopCubitFinder = find.byKey(\n        const Key('cubit_listener_noop_button'),\n      );\n      await tester.pumpWidget(\n        MyApp(\n          onListenerCalled: (context, state) {\n            listenerCallCount++;\n            latestState = state;\n          },\n        ),\n      );\n\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 1);\n      expect(latestState, 1);\n\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 2);\n      expect(latestState, 2);\n\n      await tester.tap(noopCubitFinder);\n      await tester.pump();\n      await tester.tap(incrementFinder);\n      await tester.pump();\n      expect(listenerCallCount, 3);\n      expect(latestState, 3);\n    });\n\n    testWidgets(\n        'calls listenWhen on single state change with correct previous '\n        'and current states', (tester) async {\n      int? latestPreviousState;\n      var listenWhenCallCount = 0;\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [1];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (previous, state) {\n            listenWhenCallCount++;\n            latestPreviousState = previous;\n            states.add(state);\n            return true;\n          },\n          listener: (_, __) {},\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n      expect(listenWhenCallCount, 1);\n      expect(latestPreviousState, 0);\n    });\n\n    testWidgets(\n        'calls listenWhen with previous listener state and current cubit state',\n        (tester) async {\n      int? latestPreviousState;\n      var listenWhenCallCount = 0;\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [2];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (previous, state) {\n            listenWhenCallCount++;\n            if ((previous + state) % 3 == 0) {\n              latestPreviousState = previous;\n              states.add(state);\n              return true;\n            }\n            return false;\n          },\n          listener: (_, __) {},\n        ),\n      );\n      counterCubit\n        ..increment()\n        ..increment()\n        ..increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n      expect(listenWhenCallCount, 3);\n      expect(latestPreviousState, 1);\n    });\n\n    testWidgets('calls listenWhen and listener with correct state',\n        (tester) async {\n      final listenWhenPreviousState = <int>[];\n      final listenWhenCurrentState = <int>[];\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (previous, current) {\n            if (current % 3 == 0) {\n              listenWhenPreviousState.add(previous);\n              listenWhenCurrentState.add(current);\n              return true;\n            }\n            return false;\n          },\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit\n        ..increment()\n        ..increment()\n        ..increment();\n      await tester.pump();\n\n      expect(states, [3]);\n      expect(listenWhenPreviousState, [2]);\n      expect(listenWhenCurrentState, [3]);\n    });\n\n    testWidgets(\n        'infers the cubit from the context if the cubit is not provided',\n        (tester) async {\n      int? latestPreviousState;\n      var listenWhenCallCount = 0;\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [1];\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: counterCubit,\n          child: BlocListener<CounterCubit, int>(\n            listenWhen: (previous, state) {\n              listenWhenCallCount++;\n              latestPreviousState = previous;\n              states.add(state);\n              return true;\n            },\n            listener: (context, state) {},\n          ),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n      expect(listenWhenCallCount, 1);\n      expect(latestPreviousState, 0);\n    });\n\n    testWidgets(\n        'calls listenWhen on multiple state change with correct previous '\n        'and current states', (tester) async {\n      int? latestPreviousState;\n      var listenWhenCallCount = 0;\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [1, 2];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (previous, state) {\n            listenWhenCallCount++;\n            latestPreviousState = previous;\n            states.add(state);\n            return true;\n          },\n          listener: (_, __) {},\n        ),\n      );\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n      expect(listenWhenCallCount, 2);\n      expect(latestPreviousState, 1);\n    });\n\n    testWidgets(\n        'does not call listener when listenWhen returns false on single state '\n        'change', (tester) async {\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = <int>[];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (_, __) => false,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n    });\n\n    testWidgets(\n        'calls listener when listenWhen returns true on single state change',\n        (tester) async {\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [1];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (_, __) => true,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n    });\n\n    testWidgets(\n        'does not call listener when listenWhen returns false '\n        'on multiple state changes', (tester) async {\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = <int>[];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (_, __) => false,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n    });\n\n    testWidgets(\n        'calls listener when listenWhen returns true on multiple state change',\n        (tester) async {\n      final states = <int>[];\n      final counterCubit = CounterCubit();\n      const expectedStates = [1, 2, 3, 4];\n      await tester.pumpWidget(\n        BlocListener<CounterCubit, int>(\n          bloc: counterCubit,\n          listenWhen: (_, __) => true,\n          listener: (_, state) => states.add(state),\n        ),\n      );\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n      counterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n    });\n\n    testWidgets(\n        'updates subscription '\n        'when provided bloc is changed', (tester) async {\n      final firstCounterCubit = CounterCubit();\n      final secondCounterCubit = CounterCubit(seed: 100);\n\n      final states = <int>[];\n      const expectedStates = [1, 101];\n\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: firstCounterCubit,\n          child: BlocListener<CounterCubit, int>(\n            listener: (_, state) => states.add(state),\n          ),\n        ),\n      );\n\n      firstCounterCubit.increment();\n\n      await tester.pumpWidget(\n        BlocProvider.value(\n          value: secondCounterCubit,\n          child: BlocListener<CounterCubit, int>(\n            listener: (_, state) => states.add(state),\n          ),\n        ),\n      );\n\n      secondCounterCubit.increment();\n      await tester.pump();\n      firstCounterCubit.increment();\n      await tester.pump();\n\n      expect(states, expectedStates);\n    });\n\n    testWidgets('overrides debugFillProperties', (tester) async {\n      final builder = DiagnosticPropertiesBuilder();\n\n      BlocListener(\n        bloc: CounterCubit(),\n        listener: (context, state) {},\n        listenWhen: (previous, current) => previous != current,\n      ).debugFillProperties(builder);\n\n      final description = builder.properties\n          .where((node) => !node.isFiltered(DiagnosticLevel.info))\n          .map((node) => node.toString())\n          .toList();\n\n      expect(\n        description,\n        <String>[\n          \"bloc: Instance of 'CounterCubit'\",\n          'has listener',\n          'has listenWhen',\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/bloc_provider_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'dart:async';\n\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MockCubit<S> extends Cubit<S> {\n  MockCubit(S state) : super(state);\n\n  @override\n  Stream<S> get stream => Stream<S>.empty();\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({\n    required Widget child,\n    Key? key,\n    CounterCubit Function(BuildContext context)? create,\n    CounterCubit? value,\n  })  : _create = create,\n        _value = value,\n        _child = child,\n        super(key: key);\n\n  final CounterCubit Function(BuildContext context)? _create;\n  final CounterCubit? _value;\n  final Widget _child;\n\n  @override\n  Widget build(BuildContext context) {\n    if (_value != null) {\n      return MaterialApp(\n        home: BlocProvider<CounterCubit>.value(\n          value: _value!,\n          child: _child,\n        ),\n      );\n    }\n    return MaterialApp(\n      home: BlocProvider<CounterCubit>(\n        create: _create!,\n        child: _child,\n      ),\n    );\n  }\n}\n\nclass MyStatefulApp extends StatefulWidget {\n  const MyStatefulApp({\n    required this.child,\n    Key? key,\n  }) : super(key: key);\n\n  final Widget child;\n\n  @override\n  State<MyStatefulApp> createState() => _MyStatefulAppState();\n}\n\nclass _MyStatefulAppState extends State<MyStatefulApp> {\n  late CounterCubit cubit;\n\n  @override\n  void initState() {\n    cubit = CounterCubit();\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: BlocProvider<CounterCubit>(\n        create: (context) => cubit,\n        child: Scaffold(\n          appBar: AppBar(\n            actions: <Widget>[\n              IconButton(\n                key: const Key('iconButtonKey'),\n                icon: const Icon(Icons.edit),\n                tooltip: 'Change State',\n                onPressed: () {\n                  setState(() => cubit = CounterCubit());\n                },\n              ),\n            ],\n          ),\n          body: widget.child,\n        ),\n      ),\n    );\n  }\n\n  @override\n  void dispose() {\n    cubit.close();\n    super.dispose();\n  }\n}\n\nclass MyAppNoProvider extends MaterialApp {\n  const MyAppNoProvider({\n    required Widget home,\n    Key? key,\n  }) : super(key: key, home: home);\n}\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({Key? key, this.onBuild}) : super(key: key);\n\n  final void Function()? onBuild;\n\n  @override\n  Widget build(BuildContext context) {\n    final counterCubit = BlocProvider.of<CounterCubit>(context);\n\n    return Scaffold(\n      body: BlocBuilder<CounterCubit, int>(\n        bloc: counterCubit,\n        builder: (context, count) {\n          onBuild?.call();\n          return Text('$count', key: const Key('counter_text'));\n        },\n      ),\n    );\n  }\n}\n\nclass RoutePage extends StatelessWidget {\n  const RoutePage({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: Column(\n        children: [\n          ElevatedButton(\n            key: const Key('route_button'),\n            child: const SizedBox(),\n            onPressed: () {\n              Navigator.of(context).pushReplacement(\n                MaterialPageRoute<Widget>(\n                  builder: (context) => const SizedBox(),\n                ),\n              );\n            },\n          ),\n          ElevatedButton(\n            key: const Key('increment_buton'),\n            child: const SizedBox(),\n            onPressed: () {\n              BlocProvider.of<CounterCubit>(context).increment();\n            },\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({VoidCallback? onClose})\n      : _onClose = onClose,\n        super(0);\n\n  final VoidCallback? _onClose;\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n\n  @override\n  Future<void> close() {\n    _onClose?.call();\n    return super.close();\n  }\n}\n\nvoid main() {\n  group('BlocProvider', () {\n    testWidgets(\n        'throws AssertionError '\n        'when child is not specified', (tester) async {\n      const expected =\n          '''BlocProvider<CounterCubit> used outside of MultiBlocProvider must specify a child''';\n      await tester.pumpWidget(BlocProvider(create: (_) => CounterCubit()));\n      expect(\n        tester.takeException(),\n        isA<AssertionError>().having((e) => e.message, 'message', expected),\n      );\n    });\n\n    testWidgets(\n        '.value throws AssertionError '\n        'when child is not specified', (tester) async {\n      const expected =\n          '''BlocProvider<CounterCubit> used outside of MultiBlocProvider must specify a child''';\n      await tester.pumpWidget(BlocProvider.value(value: CounterCubit()));\n      expect(\n        tester.takeException(),\n        isA<AssertionError>().having((e) => e.message, 'message', expected),\n      );\n    });\n\n    testWidgets('lazy is true by default', (tester) async {\n      final blocProvider = BlocProvider(\n        create: (_) => CounterCubit(),\n        child: const SizedBox(),\n      );\n      expect(blocProvider.lazy, isTrue);\n    });\n\n    testWidgets('.value lazy is true', (tester) async {\n      final blocProvider = BlocProvider.value(\n        value: CounterCubit(),\n        child: const SizedBox(),\n      );\n      expect(blocProvider.lazy, isTrue);\n    });\n\n    testWidgets('lazily loads cubits by default', (tester) async {\n      var createCalled = false;\n      await tester.pumpWidget(\n        BlocProvider(\n          create: (_) {\n            createCalled = true;\n            return CounterCubit();\n          },\n          child: const SizedBox(),\n        ),\n      );\n      expect(createCalled, isFalse);\n    });\n\n    testWidgets('can override lazy loading', (tester) async {\n      var createCalled = false;\n      await tester.pumpWidget(\n        BlocProvider(\n          lazy: false,\n          create: (_) {\n            createCalled = true;\n            return CounterCubit();\n          },\n          child: const SizedBox(),\n        ),\n      );\n      expect(createCalled, isTrue);\n    });\n\n    testWidgets('can be provided without an explicit type', (tester) async {\n      const key = Key('__text_count__');\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocProvider(\n            create: (_) => CounterCubit(),\n            child: Builder(\n              builder: (context) => Text(\n                '${BlocProvider.of<CounterCubit>(context).state}',\n                key: key,\n              ),\n            ),\n          ),\n        ),\n      );\n      final text = tester.widget(find.byKey(key)) as Text;\n      expect(text.data, '0');\n    });\n\n    testWidgets('passes cubit to children', (tester) async {\n      await tester.pumpWidget(\n        MyApp(\n          create: (_) => CounterCubit(),\n          child: const CounterPage(),\n        ),\n      );\n\n      final counterText = tester.widget<Text>(\n        find.byKey(const Key('counter_text')),\n      );\n      expect(counterText.data, '0');\n    });\n\n    testWidgets('passes cubit to children within same build', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider(\n              create: (context) => CounterCubit(),\n              child: BlocBuilder<CounterCubit, int>(\n                builder: (context, state) => Text('state: $state'),\n              ),\n            ),\n          ),\n        ),\n      );\n      expect(find.text('state: 0'), findsOneWidget);\n    });\n\n    testWidgets(\n        'triggers updates in child with context.watch '\n        'when provided bloc instance changes,', (tester) async {\n      const buttonKey = Key('__button__');\n      var cubit = CounterCubit();\n      await tester.pumpWidget(\n        StatefulBuilder(\n          builder: (context, setState) => MaterialApp(\n            home: Scaffold(\n              body: BlocProvider.value(\n                value: cubit,\n                child: Builder(\n                  builder: (context) {\n                    final state = context.watch<CounterCubit>().state;\n                    return GestureDetector(\n                      onTap: () => cubit.increment(),\n                      child: Text('state: $state'),\n                    );\n                  },\n                ),\n              ),\n              floatingActionButton: FloatingActionButton(\n                key: buttonKey,\n                onPressed: () => setState(() => cubit = CounterCubit()),\n              ),\n            ),\n          ),\n        ),\n      );\n      expect(find.text('state: 0'), findsOneWidget);\n\n      cubit.increment();\n      await tester.pump();\n\n      expect(find.text('state: 1'), findsOneWidget);\n\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pump();\n\n      expect(find.text('state: 0'), findsOneWidget);\n    });\n\n    testWidgets('can access cubit directly within builder', (tester) async {\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: BlocProvider(\n              create: (_) => CounterCubit(),\n              child: BlocBuilder<CounterCubit, int>(\n                builder: (context, state) => Column(\n                  children: [\n                    Text('state: $state'),\n                    ElevatedButton(\n                      key: const Key('increment_button'),\n                      child: const SizedBox(),\n                      onPressed: () {\n                        BlocProvider.of<CounterCubit>(context).increment();\n                      },\n                    ),\n                  ],\n                ),\n              ),\n            ),\n          ),\n        ),\n      );\n      expect(find.text('state: 0'), findsOneWidget);\n      await tester.tap(find.byKey(const Key('increment_button')));\n      await tester.pumpAndSettle();\n      expect(tester.takeException(), isNull);\n      expect(find.text('state: 1'), findsOneWidget);\n    });\n\n    testWidgets('does not call close on cubit if it was not loaded (lazily)',\n        (tester) async {\n      var closeCalled = false;\n      await tester.pumpWidget(\n        MyApp(\n          create: (_) => CounterCubit(onClose: () => closeCalled = true),\n          child: const RoutePage(),\n        ),\n      );\n\n      final routeButtonFinder = find.byKey(const Key('route_button'));\n      expect(routeButtonFinder, findsOneWidget);\n      expect(closeCalled, false);\n\n      await tester.tap(routeButtonFinder);\n      await tester.pumpAndSettle();\n\n      expect(closeCalled, false);\n    });\n\n    testWidgets('calls close on cubit automatically when invoked (lazily)',\n        (tester) async {\n      var closeCalled = false;\n      await tester.pumpWidget(\n        MyApp(\n          create: (_) => CounterCubit(onClose: () => closeCalled = true),\n          child: const RoutePage(),\n        ),\n      );\n      final incrementButtonFinder = find.byKey(const Key('increment_buton'));\n      expect(incrementButtonFinder, findsOneWidget);\n      await tester.tap(incrementButtonFinder);\n      final routeButtonFinder = find.byKey(const Key('route_button'));\n      expect(routeButtonFinder, findsOneWidget);\n      expect(closeCalled, false);\n\n      await tester.tap(routeButtonFinder);\n      await tester.pumpAndSettle();\n\n      expect(closeCalled, true);\n    });\n\n    testWidgets('does not close when created using value', (tester) async {\n      var closeCalled = false;\n      final value = CounterCubit(onClose: () => closeCalled = true);\n      const child = RoutePage();\n      await tester.pumpWidget(MyApp(value: value, child: child));\n\n      final routeButtonFinder = find.byKey(const Key('route_button'));\n      expect(routeButtonFinder, findsOneWidget);\n      expect(closeCalled, false);\n\n      await tester.tap(routeButtonFinder);\n      await tester.pumpAndSettle();\n\n      expect(closeCalled, false);\n    });\n\n    testWidgets(\n        'should throw FlutterError if BlocProvider is not found in current '\n        'context', (tester) async {\n      await tester.pumpWidget(const MyAppNoProvider(home: CounterPage()));\n      final dynamic exception = tester.takeException();\n      const expectedMessage = '''\n        BlocProvider.of() called with a context that does not contain a CounterCubit.\n        No ancestor could be found starting from the context that was passed to BlocProvider.of<CounterCubit>().\n\n        This can happen if the context you used comes from a widget above the BlocProvider.\n\n        The context used was: CounterPage(dirty)\n''';\n      expect((exception as FlutterError).message, expectedMessage);\n    });\n\n    testWidgets(\n        'should throw StateError if internal '\n        'exception is thrown', (tester) async {\n      const expected = 'Tried to read a provider that threw '\n          'during the creation of its value.\\n'\n          'The exception occurred during the creation of type CounterCubit.';\n      final onError = FlutterError.onError;\n      final flutterErrors = <FlutterErrorDetails>[];\n      FlutterError.onError = flutterErrors.add;\n      await tester.pumpWidget(\n        BlocProvider<CounterCubit>(\n          lazy: false,\n          create: (_) => throw Exception('oops'),\n          child: const SizedBox(),\n        ),\n      );\n      FlutterError.onError = onError;\n      expect(\n        flutterErrors,\n        contains(\n          isA<FlutterErrorDetails>().having(\n            (d) => d.exception,\n            'exception',\n            isA<StateError>().having(\n              (e) => e.message,\n              'message',\n              contains(expected),\n            ),\n          ),\n        ),\n      );\n    });\n\n    testWidgets(\n        'should throw StateError '\n        'if exception is for different provider', (tester) async {\n      const expected = 'Tried to read a provider that threw '\n          'during the creation of its value.\\n'\n          'The exception occurred during the creation of type CounterCubit.';\n      final onError = FlutterError.onError;\n      final flutterErrors = <FlutterErrorDetails>[];\n      FlutterError.onError = flutterErrors.add;\n      await tester.pumpWidget(\n        BlocProvider<CounterCubit>(\n          lazy: false,\n          create: (context) {\n            context.read<int>();\n            return CounterCubit();\n          },\n          child: const SizedBox(),\n        ),\n      );\n      FlutterError.onError = onError;\n      expect(\n        flutterErrors,\n        contains(\n          isA<FlutterErrorDetails>().having(\n            (d) => d.exception,\n            'exception',\n            isA<StateError>().having(\n              (e) => e.message,\n              'message',\n              contains(expected),\n            ),\n          ),\n        ),\n      );\n    });\n\n    testWidgets(\n        'should not rebuild widgets that inherited the cubit if the cubit is '\n        'changed', (tester) async {\n      var numBuilds = 0;\n      final child = CounterPage(onBuild: () => numBuilds++);\n      await tester.pumpWidget(\n        MyStatefulApp(\n          child: child,\n        ),\n      );\n      await tester.tap(find.byKey(const Key('iconButtonKey')));\n      await tester.pump();\n      expect(numBuilds, 1);\n    });\n\n    testWidgets('listen: true registers context as dependent', (tester) async {\n      const textKey = Key('__text__');\n      const buttonKey = Key('__button__');\n      var counterCubitCreateCount = 0;\n      var materialBuildCount = 0;\n      var textBuildCount = 0;\n      await tester.pumpWidget(\n        BlocProvider(\n          create: (_) {\n            counterCubitCreateCount++;\n            return CounterCubit();\n          },\n          child: Builder(\n            builder: (context) {\n              materialBuildCount++;\n              return MaterialApp(\n                home: Scaffold(\n                  body: Builder(\n                    builder: (context) {\n                      textBuildCount++;\n                      final count = BlocProvider.of<CounterCubit>(\n                        context,\n                        listen: true,\n                      ).state;\n                      return Text('$count', key: textKey);\n                    },\n                  ),\n                  floatingActionButton: FloatingActionButton(\n                    key: buttonKey,\n                    onPressed: () {\n                      context.read<CounterCubit>().increment();\n                    },\n                  ),\n                ),\n              );\n            },\n          ),\n        ),\n      );\n      var text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '0');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(1));\n\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '1');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(2));\n\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '2');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(3));\n    });\n\n    testWidgets('context.watch registers context as dependent', (tester) async {\n      const textKey = Key('__text__');\n      const buttonKey = Key('__button__');\n      var counterCubitCreateCount = 0;\n      var materialBuildCount = 0;\n      var textBuildCount = 0;\n      await tester.pumpWidget(\n        BlocProvider(\n          create: (_) {\n            counterCubitCreateCount++;\n            return CounterCubit();\n          },\n          child: Builder(\n            builder: (context) {\n              materialBuildCount++;\n              return MaterialApp(\n                home: Scaffold(\n                  body: Builder(\n                    builder: (context) {\n                      textBuildCount++;\n                      final count = context.watch<CounterCubit>().state;\n                      return Text('$count', key: textKey);\n                    },\n                  ),\n                  floatingActionButton: FloatingActionButton(\n                    key: buttonKey,\n                    onPressed: () {\n                      context.read<CounterCubit>().increment();\n                    },\n                  ),\n                ),\n              );\n            },\n          ),\n        ),\n      );\n      var text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '0');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(1));\n\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '1');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(2));\n\n      await tester.tap(find.byKey(buttonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, '2');\n      expect(counterCubitCreateCount, equals(1));\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(3));\n    });\n\n    testWidgets('context.select only rebuilds on changes to selected value',\n        (tester) async {\n      const textKey = Key('__text__');\n      const incrementButtonKey = Key('__increment_button__');\n      const decrementButtonKey = Key('__decrement_button__');\n      var materialBuildCount = 0;\n      var textBuildCount = 0;\n      await tester.pumpWidget(\n        BlocProvider(\n          create: (_) => CounterCubit(),\n          child: Builder(\n            builder: (context) {\n              materialBuildCount++;\n              return MaterialApp(\n                home: Scaffold(\n                  body: Builder(\n                    builder: (context) {\n                      textBuildCount++;\n                      final isPositive = context.select(\n                        (CounterCubit c) => c.state >= 0,\n                      );\n                      return Text('$isPositive', key: textKey);\n                    },\n                  ),\n                  floatingActionButton: Column(\n                    children: [\n                      FloatingActionButton(\n                        key: incrementButtonKey,\n                        onPressed: () {\n                          context.read<CounterCubit>().increment();\n                        },\n                      ),\n                      FloatingActionButton(\n                        key: decrementButtonKey,\n                        onPressed: () {\n                          context.read<CounterCubit>().decrement();\n                        },\n                      ),\n                    ],\n                  ),\n                ),\n              );\n            },\n          ),\n        ),\n      );\n      var text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, 'true');\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(1));\n\n      await tester.tap(find.byKey(decrementButtonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, 'false');\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(2));\n\n      await tester.tap(find.byKey(decrementButtonKey));\n      await tester.pumpAndSettle();\n\n      text = tester.widget<Text>(find.byKey(textKey));\n      expect(text.data, 'false');\n      expect(materialBuildCount, equals(1));\n      expect(textBuildCount, equals(2));\n    });\n\n    testWidgets('should not throw if listen returns null subscription',\n        (tester) async {\n      await tester.pumpWidget(\n        BlocProvider(\n          lazy: false,\n          create: (_) => MockCubit(0),\n          child: const SizedBox(),\n        ),\n      );\n      expect(tester.takeException(), isNull);\n    });\n\n    testWidgets('overrides debugFillProperties', (tester) async {\n      final builder = DiagnosticPropertiesBuilder();\n\n      BlocProvider(\n        create: (context) => CounterCubit(),\n        child: const SizedBox(),\n      ).debugFillProperties(builder);\n\n      final description = builder.properties\n          .where((node) => !node.isFiltered(DiagnosticLevel.info))\n          .map((node) => node.toString())\n          .toList();\n\n      expect(description, <String>['lazy: true']);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/bloc_selector_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({int seed = 0}) : super(seed);\n\n  void increment() => emit(state + 1);\n}\n\nvoid main() {\n  group('BlocSelector', () {\n    testWidgets('renders with correct state', (tester) async {\n      final counterCubit = CounterCubit();\n      var builderCallCount = 0;\n\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocSelector<CounterCubit, int, bool>(\n            bloc: counterCubit,\n            selector: (state) => state.isEven,\n            builder: (context, state) {\n              builderCallCount++;\n              return Text('isEven: $state');\n            },\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n      expect(builderCallCount, equals(1));\n    });\n\n    testWidgets('only rebuilds when selected state changes', (tester) async {\n      final counterCubit = CounterCubit();\n      var builderCallCount = 0;\n\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocSelector<CounterCubit, int, bool>(\n            bloc: counterCubit,\n            selector: (state) => state == 1,\n            builder: (context, state) {\n              builderCallCount++;\n              return Text('equals 1: $state');\n            },\n          ),\n        ),\n      );\n\n      expect(find.text('equals 1: false'), findsOneWidget);\n      expect(builderCallCount, equals(1));\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('equals 1: true'), findsOneWidget);\n      expect(builderCallCount, equals(2));\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('equals 1: false'), findsOneWidget);\n      expect(builderCallCount, equals(3));\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('equals 1: false'), findsOneWidget);\n      expect(builderCallCount, equals(3));\n    });\n\n    testWidgets('infers bloc from context', (tester) async {\n      final counterCubit = CounterCubit();\n      var builderCallCount = 0;\n\n      await tester.pumpWidget(\n        MaterialApp(\n          home: BlocProvider.value(\n            value: counterCubit,\n            child: BlocSelector<CounterCubit, int, bool>(\n              selector: (state) => state.isEven,\n              builder: (context, state) {\n                builderCallCount++;\n                return Text('isEven: $state');\n              },\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n      expect(builderCallCount, equals(1));\n    });\n\n    testWidgets('rebuilds when provided bloc is changed', (tester) async {\n      final firstCounterCubit = CounterCubit();\n      final secondCounterCubit = CounterCubit(seed: 100);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: firstCounterCubit,\n            child: BlocSelector<CounterCubit, int, bool>(\n              selector: (state) => state.isEven,\n              builder: (context, state) => Text('isEven: $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n\n      firstCounterCubit.increment();\n      await tester.pumpAndSettle();\n      expect(find.text('isEven: false'), findsOneWidget);\n      expect(find.text('isEven: true'), findsNothing);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocProvider.value(\n            value: secondCounterCubit,\n            child: BlocSelector<CounterCubit, int, bool>(\n              selector: (state) => state.isEven,\n              builder: (context, state) => Text('isEven: $state'),\n            ),\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n      expect(find.text('isEven: false'), findsNothing);\n\n      secondCounterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('isEven: false'), findsOneWidget);\n      expect(find.text('isEven: true'), findsNothing);\n    });\n\n    testWidgets('rebuilds when bloc is changed at runtime', (tester) async {\n      final firstCounterCubit = CounterCubit();\n      final secondCounterCubit = CounterCubit(seed: 100);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocSelector<CounterCubit, int, bool>(\n            bloc: firstCounterCubit,\n            selector: (state) => state.isEven,\n            builder: (context, state) => Text('isEven: $state'),\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n\n      firstCounterCubit.increment();\n      await tester.pumpAndSettle();\n      expect(find.text('isEven: false'), findsOneWidget);\n      expect(find.text('isEven: true'), findsNothing);\n\n      await tester.pumpWidget(\n        Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocSelector<CounterCubit, int, bool>(\n            bloc: secondCounterCubit,\n            selector: (state) => state.isEven,\n            builder: (context, state) => Text('isEven: $state'),\n          ),\n        ),\n      );\n\n      expect(find.text('isEven: true'), findsOneWidget);\n      expect(find.text('isEven: false'), findsNothing);\n\n      secondCounterCubit.increment();\n      await tester.pumpAndSettle();\n\n      expect(find.text('isEven: false'), findsOneWidget);\n      expect(find.text('isEven: true'), findsNothing);\n    });\n\n    testWidgets('rebuilds when selector changes', (tester) async {\n      final counterCubit = CounterCubit();\n      var selector = (int state) => state.isEven;\n\n      Widget buildWidget() {\n        return Directionality(\n          textDirection: TextDirection.ltr,\n          child: BlocSelector<CounterCubit, int, bool>(\n            bloc: counterCubit,\n            selector: selector,\n            builder: (context, state) => Text('Selected: $state'),\n          ),\n        );\n      }\n\n      await tester.pumpWidget(buildWidget());\n      expect(find.text('Selected: true'), findsOneWidget);\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n      expect(find.text('Selected: false'), findsOneWidget);\n\n      selector = (state) => state.isOdd;\n\n      await tester.pumpWidget(buildWidget());\n      expect(find.text('Selected: true'), findsOneWidget);\n\n      counterCubit.increment();\n      await tester.pumpAndSettle();\n      expect(find.text('Selected: false'), findsOneWidget);\n    });\n\n    testWidgets('overrides debugFillProperties', (tester) async {\n      final builder = DiagnosticPropertiesBuilder();\n\n      BlocSelector(\n        bloc: CounterCubit(),\n        builder: (context, state) => const SizedBox(),\n        selector: (state) => state,\n      ).debugFillProperties(builder);\n\n      final description = builder.properties\n          .where((node) => !node.isFiltered(DiagnosticLevel.info))\n          .map((node) => node.toString())\n          .toList();\n\n      expect(\n        description,\n        <String>[\n          \"bloc: Instance of 'CounterCubit'\",\n          'has builder',\n          'has selector',\n        ],\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/multi_bloc_listener_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n\nvoid main() {\n  group('MultiBlocListener', () {\n    testWidgets('calls listeners on state changes', (tester) async {\n      final statesA = <int>[];\n      const expectedStatesA = [1, 2];\n      final counterCubitA = CounterCubit();\n\n      final statesB = <int>[];\n      final expectedStatesB = [1];\n      final counterCubitB = CounterCubit();\n\n      await tester.pumpWidget(\n        MultiBlocListener(\n          listeners: [\n            BlocListener<CounterCubit, int>(\n              bloc: counterCubitA,\n              listener: (context, state) => statesA.add(state),\n            ),\n            BlocListener<CounterCubit, int>(\n              bloc: counterCubitB,\n              listener: (context, state) => statesB.add(state),\n            ),\n          ],\n          child: const SizedBox(key: Key('multiCubitListener_child')),\n        ),\n      );\n      await tester.pumpAndSettle();\n\n      expect(find.byKey(const Key('multiCubitListener_child')), findsOneWidget);\n\n      counterCubitA.increment();\n      await tester.pump();\n      counterCubitA.increment();\n      await tester.pump();\n      counterCubitB.increment();\n      await tester.pump();\n\n      expect(statesA, expectedStatesA);\n      expect(statesB, expectedStatesB);\n    });\n\n    testWidgets('calls listeners on state changes without explicit types',\n        (tester) async {\n      final statesA = <int>[];\n      const expectedStatesA = [1, 2];\n      final counterCubitA = CounterCubit();\n\n      final statesB = <int>[];\n      final expectedStatesB = [1];\n      final counterCubitB = CounterCubit();\n\n      await tester.pumpWidget(\n        MultiBlocListener(\n          listeners: [\n            BlocListener(\n              bloc: counterCubitA,\n              listener: (BuildContext context, int state) => statesA.add(state),\n            ),\n            BlocListener(\n              bloc: counterCubitB,\n              listener: (BuildContext context, int state) => statesB.add(state),\n            ),\n          ],\n          child: const SizedBox(key: Key('multiCubitListener_child')),\n        ),\n      );\n      await tester.pumpAndSettle();\n\n      expect(find.byKey(const Key('multiCubitListener_child')), findsOneWidget);\n\n      counterCubitA.increment();\n      await tester.pump();\n      counterCubitA.increment();\n      await tester.pump();\n      counterCubitB.increment();\n      await tester.pump();\n\n      expect(statesA, expectedStatesA);\n      expect(statesB, expectedStatesB);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/multi_bloc_provider_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MyAppWithNavigation extends MaterialApp {\n  MyAppWithNavigation({required Widget child, Key? key})\n      : super(key: key, home: Scaffold(body: child));\n}\n\nclass HomePage extends StatelessWidget {\n  const HomePage({\n    Key? key,\n    this.onCounterCubitClosed,\n    this.onThemeCubitClosed,\n    this.counterCubitValue,\n    this.themeCubitValue,\n  }) : super(key: key);\n\n  final VoidCallback? onCounterCubitClosed;\n  final VoidCallback? onThemeCubitClosed;\n  final CounterCubit? counterCubitValue;\n  final ThemeCubit? themeCubitValue;\n\n  @override\n  Widget build(BuildContext context) {\n    List<BlocProvider<StateStreamableSource<Object?>>> getProviders() {\n      final providers = <BlocProvider>[];\n      if (counterCubitValue != null) {\n        providers.add(\n          BlocProvider<CounterCubit>.value(\n            value: counterCubitValue!,\n          ),\n        );\n      } else {\n        providers.add(\n          BlocProvider<CounterCubit>(\n            create: (_) => CounterCubit(onClose: onCounterCubitClosed),\n          ),\n        );\n      }\n\n      if (themeCubitValue != null) {\n        providers.add(\n          BlocProvider<ThemeCubit>.value(\n            value: themeCubitValue!,\n          ),\n        );\n      } else {\n        providers.add(\n          BlocProvider<ThemeCubit>(\n            create: (_) => ThemeCubit(onClose: onThemeCubitClosed),\n          ),\n        );\n      }\n      return providers;\n    }\n\n    return MultiBlocProvider(\n      providers: getProviders(),\n      child: Builder(\n        builder: (context) {\n          return Column(\n            children: [\n              ElevatedButton(\n                key: const Key('pop_button'),\n                child: const SizedBox(),\n                onPressed: () {\n                  Navigator.of(context).pushReplacement(\n                    MaterialPageRoute<void>(builder: (_) => const SizedBox()),\n                  );\n                },\n              ),\n              ElevatedButton(\n                key: const Key('increment_button'),\n                child: const SizedBox(),\n                onPressed: () =>\n                    BlocProvider.of<CounterCubit>(context).increment(),\n              ),\n              ElevatedButton(\n                key: const Key('toggle_theme_button'),\n                child: const SizedBox(),\n                onPressed: () => BlocProvider.of<ThemeCubit>(context).toggle(),\n              ),\n            ],\n          );\n        },\n      ),\n    );\n  }\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<ThemeCubit, ThemeData>(\n      bloc: BlocProvider.of<ThemeCubit>(context),\n      builder: (_, theme) {\n        return MaterialApp(home: const CounterPage(), theme: theme);\n      },\n    );\n  }\n}\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    final counterCubit = BlocProvider.of<CounterCubit>(context);\n\n    return Scaffold(\n      body: BlocBuilder<CounterCubit, int>(\n        bloc: counterCubit,\n        builder: (context, count) {\n          return Center(\n            child: Text('$count', key: const Key('counter_text')),\n          );\n        },\n      ),\n      floatingActionButton: FloatingActionButton(\n        key: const Key('pop_button'),\n        onPressed: () => Navigator.of(context).pop(),\n      ),\n    );\n  }\n}\n\nclass CounterCubit extends Cubit<int> {\n  CounterCubit({VoidCallback? onClose})\n      : _onClose = onClose,\n        super(0);\n\n  final VoidCallback? _onClose;\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n\n  @override\n  Future<void> close() {\n    _onClose?.call();\n    return super.close();\n  }\n}\n\nclass ThemeCubit extends Cubit<ThemeData> {\n  ThemeCubit({VoidCallback? onClose})\n      : _onClose = onClose,\n        super(ThemeData.light());\n\n  final VoidCallback? _onClose;\n\n  void toggle() {\n    emit(state == ThemeData.dark() ? ThemeData.light() : ThemeData.dark());\n  }\n\n  @override\n  Future<void> close() {\n    _onClose?.call();\n    return super.close();\n  }\n}\n\nvoid main() {\n  group('MultiBlocProvider', () {\n    testWidgets('passes cubits to children', (tester) async {\n      await tester.pumpWidget(\n        MultiBlocProvider(\n          providers: [\n            BlocProvider<CounterCubit>(create: (_) => CounterCubit()),\n            BlocProvider<ThemeCubit>(create: (_) => ThemeCubit()),\n          ],\n          child: const MyApp(),\n        ),\n      );\n\n      final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));\n      expect(materialApp.theme, ThemeData.light());\n\n      final counterFinder = find.byKey(const Key('counter_text'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = tester.widget<Text>(counterFinder);\n      expect(counterText.data, '0');\n    });\n\n    testWidgets('passes cubits to children without explicit states',\n        (tester) async {\n      await tester.pumpWidget(\n        MultiBlocProvider(\n          providers: [\n            BlocProvider(create: (_) => CounterCubit()),\n            BlocProvider(create: (_) => ThemeCubit()),\n          ],\n          child: const MyApp(),\n        ),\n      );\n\n      final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));\n      expect(materialApp.theme, ThemeData.light());\n\n      final counterFinder = find.byKey(const Key('counter_text'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = tester.widget<Text>(counterFinder);\n      expect(counterText.data, '0');\n    });\n\n    testWidgets('adds event to each cubit', (tester) async {\n      await tester.pumpWidget(\n        MultiBlocProvider(\n          providers: [\n            BlocProvider<CounterCubit>(\n              create: (_) => CounterCubit()..decrement(),\n            ),\n            BlocProvider<ThemeCubit>(\n              create: (_) => ThemeCubit()..toggle(),\n            ),\n          ],\n          child: const MyApp(),\n        ),\n      );\n\n      await tester.pump();\n\n      final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));\n      expect(materialApp.theme, ThemeData.dark());\n\n      final counterFinder = find.byKey(const Key('counter_text'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = tester.widget<Text>(counterFinder);\n      expect(counterText.data, '-1');\n    });\n\n    testWidgets('close on counter cubit which was loaded (lazily)',\n        (tester) async {\n      var counterCubitClosed = false;\n      var themeCubitClosed = false;\n\n      await tester.pumpWidget(\n        MyAppWithNavigation(\n          child: HomePage(\n            onCounterCubitClosed: () => counterCubitClosed = true,\n            onThemeCubitClosed: () => themeCubitClosed = true,\n          ),\n        ),\n      );\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n\n      await tester.tap(find.byKey(const Key('increment_button')));\n      await tester.pump();\n      await tester.tap(find.byKey(const Key('pop_button')));\n      await tester.pumpAndSettle();\n\n      expect(counterCubitClosed, true);\n      expect(themeCubitClosed, false);\n    });\n\n    testWidgets('close on theme cubit which was loaded (lazily)',\n        (tester) async {\n      var counterCubitClosed = false;\n      var themeCubitClosed = false;\n\n      await tester.pumpWidget(\n        MyAppWithNavigation(\n          child: HomePage(\n            onCounterCubitClosed: () => counterCubitClosed = true,\n            onThemeCubitClosed: () => themeCubitClosed = true,\n          ),\n        ),\n      );\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n\n      await tester.tap(find.byKey(const Key('toggle_theme_button')));\n      await tester.pump();\n      await tester.tap(find.byKey(const Key('pop_button')));\n      await tester.pumpAndSettle();\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, true);\n    });\n\n    testWidgets('close on all cubits which were loaded (lazily)',\n        (tester) async {\n      var counterCubitClosed = false;\n      var themeCubitClosed = false;\n\n      await tester.pumpWidget(\n        MyAppWithNavigation(\n          child: HomePage(\n            onCounterCubitClosed: () => counterCubitClosed = true,\n            onThemeCubitClosed: () => themeCubitClosed = true,\n          ),\n        ),\n      );\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n      await tester.tap(find.byKey(const Key('increment_button')));\n      await tester.pump();\n      await tester.tap(find.byKey(const Key('toggle_theme_button')));\n      await tester.pump();\n      await tester.tap(find.byKey(const Key('pop_button')));\n      await tester.pumpAndSettle();\n\n      expect(counterCubitClosed, true);\n      expect(themeCubitClosed, true);\n    });\n\n    testWidgets(\n        'does not call close on cubits if they were not loaded (lazily)',\n        (tester) async {\n      var counterCubitClosed = false;\n      var themeCubitClosed = false;\n\n      await tester.pumpWidget(\n        MyAppWithNavigation(\n          child: HomePage(\n            onCounterCubitClosed: () => counterCubitClosed = true,\n            onThemeCubitClosed: () => themeCubitClosed = true,\n          ),\n        ),\n      );\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n\n      await tester.tap(find.byKey(const Key('pop_button')));\n      await tester.pumpAndSettle();\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n    });\n\n    testWidgets('does not close when created using value', (tester) async {\n      var counterCubitClosed = false;\n      var themeCubitClosed = false;\n\n      final counterCubit = CounterCubit(\n        onClose: () => counterCubitClosed = true,\n      );\n      final themeCubit = ThemeCubit(\n        onClose: () => themeCubitClosed = true,\n      );\n\n      await tester.pumpWidget(\n        MyAppWithNavigation(\n          child: HomePage(\n            counterCubitValue: counterCubit,\n            themeCubitValue: themeCubit,\n          ),\n        ),\n      );\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n\n      await tester.tap(find.byKey(const Key('pop_button')));\n      await tester.pumpAndSettle();\n\n      expect(counterCubitClosed, false);\n      expect(themeCubitClosed, false);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/multi_repository_provider_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MyApp extends MaterialApp {\n  const MyApp({Key? key}) : super(key: key, home: const CounterPage());\n}\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    final repositoryA = RepositoryProvider.of<RepositoryA>(context);\n    final repositoryB = RepositoryProvider.of<RepositoryB>(context);\n\n    return Scaffold(\n      body: Column(\n        children: [\n          Text(\n            '${repositoryA.data}',\n            key: const Key('RepositoryA_data'),\n          ),\n          Text(\n            '${repositoryB.data}',\n            key: const Key('RepositoryB_data'),\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nclass RepositoryA {\n  const RepositoryA(this.data);\n\n  final int data;\n}\n\nclass RepositoryB {\n  const RepositoryB(this.data);\n\n  final int data;\n}\n\nvoid main() {\n  group('MultiRepositoryProvider', () {\n    testWidgets('passes values to children', (tester) async {\n      await tester.pumpWidget(\n        MultiRepositoryProvider(\n          providers: [\n            RepositoryProvider<RepositoryA>(\n              create: (_) => const RepositoryA(0),\n            ),\n            RepositoryProvider<RepositoryB>(\n              create: (_) => const RepositoryB(1),\n            ),\n          ],\n          child: const MyApp(),\n        ),\n      );\n\n      final repositoryAFinder = find.byKey(const Key('RepositoryA_data'));\n      expect(repositoryAFinder, findsOneWidget);\n\n      final repositoryAText = tester.widget(repositoryAFinder) as Text;\n      expect(repositoryAText.data, '0');\n\n      final repositoryBFinder = find.byKey(const Key('RepositoryB_data'));\n      expect(repositoryBFinder, findsOneWidget);\n\n      final repositoryBText = tester.widget(repositoryBFinder) as Text;\n      expect(repositoryBText.data, '1');\n    });\n\n    testWidgets('passes values to children without explict types',\n        (tester) async {\n      await tester.pumpWidget(\n        MultiRepositoryProvider(\n          providers: [\n            RepositoryProvider(\n              create: (_) => const RepositoryA(0),\n            ),\n            RepositoryProvider(\n              create: (_) => const RepositoryB(1),\n            ),\n          ],\n          child: const MyApp(),\n        ),\n      );\n\n      final repositoryAFinder = find.byKey(const Key('RepositoryA_data'));\n      expect(repositoryAFinder, findsOneWidget);\n\n      final repositoryAText = tester.widget(repositoryAFinder) as Text;\n      expect(repositoryAText.data, '0');\n\n      final repositoryBFinder = find.byKey(const Key('RepositoryB_data'));\n      expect(repositoryBFinder, findsOneWidget);\n\n      final repositoryBText = tester.widget(repositoryBFinder) as Text;\n      expect(repositoryBText.data, '1');\n    });\n  });\n}\n"
  },
  {
    "path": "packages/flutter_bloc/test/repository_provider_test.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nclass MyApp extends StatelessWidget {\n  const MyApp({\n    required this.repository,\n    required this.child,\n    Key? key,\n    this.useValueProvider = false,\n  }) : super(key: key);\n\n  final Repository repository;\n  final Widget child;\n  final bool useValueProvider;\n\n  @override\n  Widget build(BuildContext context) {\n    if (useValueProvider) {\n      return MaterialApp(\n        home: RepositoryProvider<Repository>.value(\n          value: repository,\n          child: child,\n        ),\n      );\n    }\n    return MaterialApp(\n      home: RepositoryProvider<Repository>(\n        create: (_) => repository,\n        child: child,\n      ),\n    );\n  }\n}\n\nclass MyStatefulApp extends StatefulWidget {\n  const MyStatefulApp({required this.child, Key? key}) : super(key: key);\n\n  final Widget child;\n\n  @override\n  State<MyStatefulApp> createState() => _MyStatefulAppState();\n}\n\nclass _MyStatefulAppState extends State<MyStatefulApp> {\n  late Repository _repository;\n\n  @override\n  void initState() {\n    _repository = const Repository(0);\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: RepositoryProvider<Repository>(\n        create: (_) => _repository,\n        child: Scaffold(\n          appBar: AppBar(\n            actions: <Widget>[\n              IconButton(\n                key: const Key('iconButtonKey'),\n                icon: const Icon(Icons.edit),\n                onPressed: () {\n                  setState(() => _repository = const Repository(0));\n                },\n              ),\n            ],\n          ),\n          body: widget.child,\n        ),\n      ),\n    );\n  }\n}\n\nclass MyAppNoProvider extends MaterialApp {\n  const MyAppNoProvider({required Widget child, Key? key})\n      : super(key: key, home: child);\n}\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({Key? key, this.onBuild}) : super(key: key);\n\n  final VoidCallback? onBuild;\n\n  @override\n  Widget build(BuildContext context) {\n    onBuild?.call();\n    final repository = RepositoryProvider.of<Repository>(context);\n\n    return Scaffold(\n      body: Text('${repository.data}', key: const Key('value_data')),\n    );\n  }\n}\n\nclass Repository {\n  const Repository(this.data);\n\n  final int data;\n}\n\nvoid main() {\n  group('RepositoryProvider', () {\n    testWidgets('lazily loads repositories by default', (tester) async {\n      var createCalled = false;\n      await tester.pumpWidget(\n        RepositoryProvider(\n          create: (_) {\n            createCalled = true;\n            return const Repository(0);\n          },\n          child: const SizedBox(),\n        ),\n      );\n      expect(createCalled, isFalse);\n    });\n\n    testWidgets('can override lazy loading', (tester) async {\n      var createCalled = false;\n      await tester.pumpWidget(\n        RepositoryProvider(\n          create: (_) {\n            createCalled = true;\n            return const Repository(0);\n          },\n          lazy: false,\n          child: const SizedBox(),\n        ),\n      );\n      expect(createCalled, isTrue);\n    });\n\n    testWidgets('passes value to children via builder', (tester) async {\n      const repository = Repository(0);\n      const child = CounterPage();\n      await tester.pumpWidget(\n        const MyApp(repository: repository, child: child),\n      );\n\n      final counterFinder = find.byKey(const Key('value_data'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = counterFinder.evaluate().first.widget as Text;\n      expect(counterText.data, '0');\n    });\n\n    testWidgets('passes value to children via value', (tester) async {\n      const repository = Repository(0);\n      const child = CounterPage();\n      await tester.pumpWidget(\n        const MyApp(\n          repository: repository,\n          useValueProvider: true,\n          child: child,\n        ),\n      );\n\n      final counterFinder = find.byKey(const Key('value_data'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = counterFinder.evaluate().first.widget as Text;\n      expect(counterText.data, '0');\n    });\n\n    testWidgets(\n        'should throw FlutterError if RepositoryProvider is not found in '\n        'current context', (tester) async {\n      const child = CounterPage();\n      await tester.pumpWidget(const MyAppNoProvider(child: child));\n      final dynamic exception = tester.takeException();\n      const expectedMessage = '''\n        RepositoryProvider.of() called with a context that does not contain a repository of type Repository.\n        No ancestor could be found starting from the context that was passed to RepositoryProvider.of<Repository>().\n\n        This can happen if the context you used comes from a widget above the RepositoryProvider.\n\n        The context used was: CounterPage(dirty)\n''';\n      expect((exception as FlutterError).message, expectedMessage);\n    });\n\n    testWidgets(\n        'should throw StateError if internal '\n        'exception is thrown', (tester) async {\n      const expected = 'Tried to read a provider that threw '\n          'during the creation of its value.\\n'\n          'The exception occurred during the creation of type Repository.';\n      final onError = FlutterError.onError;\n      final flutterErrors = <FlutterErrorDetails>[];\n      FlutterError.onError = flutterErrors.add;\n      await tester.pumpWidget(\n        RepositoryProvider<Repository>(\n          lazy: false,\n          create: (_) => throw Exception('oops'),\n          child: const SizedBox(),\n        ),\n      );\n      FlutterError.onError = onError;\n      expect(\n        flutterErrors,\n        contains(\n          isA<FlutterErrorDetails>().having(\n            (d) => d.exception,\n            'exception',\n            isA<StateError>().having(\n              (e) => e.message,\n              'message',\n              contains(expected),\n            ),\n          ),\n        ),\n      );\n    });\n\n    testWidgets(\n        'should throw StateError '\n        'if exception is for different provider', (tester) async {\n      const expected = 'Tried to read a provider that threw '\n          'during the creation of its value.\\n'\n          'The exception occurred during the creation of type Repository.';\n      final onError = FlutterError.onError;\n      final flutterErrors = <FlutterErrorDetails>[];\n      FlutterError.onError = flutterErrors.add;\n      await tester.pumpWidget(\n        RepositoryProvider<Repository>(\n          lazy: false,\n          create: (context) {\n            context.read<int>();\n            return const Repository(0);\n          },\n          child: const SizedBox(),\n        ),\n      );\n      FlutterError.onError = onError;\n      expect(\n        flutterErrors,\n        contains(\n          isA<FlutterErrorDetails>().having(\n            (d) => d.exception,\n            'exception',\n            isA<StateError>().having(\n              (e) => e.message,\n              'message',\n              contains(expected),\n            ),\n          ),\n        ),\n      );\n    });\n\n    testWidgets(\n        'should not rebuild widgets that inherited the value if the value is '\n        'changed', (tester) async {\n      var numBuilds = 0;\n      final child = CounterPage(onBuild: () => numBuilds++);\n      await tester.pumpWidget(MyStatefulApp(child: child));\n      await tester.tap(find.byKey(const Key('iconButtonKey')));\n      await tester.pump();\n      expect(numBuilds, 1);\n    });\n\n    testWidgets(\n        'should rebuild widgets that inherited the value if the value is '\n        'changed with context.watch', (tester) async {\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MaterialApp(\n          home: StatefulBuilder(\n            builder: (context, setState) {\n              var repository = const Repository(0);\n              return RepositoryProvider.value(\n                value: repository,\n                child: StatefulBuilder(\n                  builder: (context, _) {\n                    numBuilds++;\n                    final data = context.watch<Repository>().data;\n                    return TextButton(\n                      child: Text('Data: $data'),\n                      onPressed: () {\n                        setState(() => repository = const Repository(1));\n                      },\n                    );\n                  },\n                ),\n              );\n            },\n          ),\n        ),\n      );\n      await tester.tap(find.byType(TextButton));\n      await tester.pump();\n      expect(numBuilds, 2);\n    });\n\n    testWidgets(\n        'should rebuild widgets that inherited the value if the value is '\n        'changed with listen: true', (tester) async {\n      var numBuilds = 0;\n      await tester.pumpWidget(\n        MaterialApp(\n          home: StatefulBuilder(\n            builder: (context, setState) {\n              var repository = const Repository(0);\n              return RepositoryProvider.value(\n                value: repository,\n                child: StatefulBuilder(\n                  builder: (context, _) {\n                    numBuilds++;\n                    final data =\n                        RepositoryProvider.of<Repository>(context, listen: true)\n                            .data;\n                    return TextButton(\n                      child: Text('Data: $data'),\n                      onPressed: () {\n                        setState(() => repository = const Repository(1));\n                      },\n                    );\n                  },\n                ),\n              );\n            },\n          ),\n        ),\n      );\n      await tester.tap(find.byType(TextButton));\n      await tester.pump();\n      expect(numBuilds, 2);\n    });\n\n    testWidgets(\n        'should access repository instance '\n        'via context.read', (tester) async {\n      await tester.pumpWidget(\n        RepositoryProvider(\n          create: (_) => const Repository(0),\n          child: MaterialApp(\n            home: Scaffold(\n              body: Center(\n                child: Builder(\n                  builder: (context) => Text(\n                    '${context.read<Repository>().data}',\n                    key: const Key('value_data'),\n                  ),\n                ),\n              ),\n            ),\n          ),\n        ),\n      );\n      final counterFinder = find.byKey(const Key('value_data'));\n      expect(counterFinder, findsOneWidget);\n\n      final counterText = counterFinder.evaluate().first.widget as Text;\n      expect(counterText.data, '0');\n    });\n\n    testWidgets('calls dispose callback when disposed', (tester) async {\n      var disposeCalled = false;\n      await tester.pumpWidget(\n        MaterialApp(\n          home: Scaffold(\n            body: RepositoryProvider<Repository>(\n              create: (_) => const Repository(0),\n              dispose: (repository) {\n                disposeCalled = true;\n                expect(repository.data, equals(0));\n              },\n              child: Builder(\n                builder: (context) => Text(\n                  '${context.read<Repository>().data}',\n                  key: const Key('value_data'),\n                ),\n              ),\n            ),\n            floatingActionButton: Builder(\n              builder: (context) => FloatingActionButton(\n                onPressed: () => Navigator.of(context).pop(),\n                child: const Icon(Icons.remove),\n              ),\n            ),\n          ),\n        ),\n      );\n      final repositoryFinder = find.byKey(const Key('value_data'));\n      expect(repositoryFinder, findsOneWidget);\n\n      final repositoryText = repositoryFinder.evaluate().first.widget as Text;\n      expect(repositoryText.data, '0');\n\n      final fabFinder = find.byType(FloatingActionButton);\n      expect(fabFinder, findsOneWidget);\n\n      expect(disposeCalled, isFalse);\n      await tester.tap(fabFinder);\n      await tester.pumpAndSettle();\n      expect(disposeCalled, isTrue);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.lock\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# Visual Studio Code related\n.classpath\n.project\n.settings/\n.vscode/\n\n# Flutter repo-specific\n/bin/cache/\n/bin/mingit/\n/dev/benchmarks/mega_gallery/\n/dev/bots/.recipe_deps\n/dev/bots/android_tools/\n/dev/docs/doc/\n/dev/docs/flutter.docs.zip\n/dev/docs/lib/\n/dev/docs/pubspec.yaml\n/dev/integration_tests/**/xcuserdata\n/dev/integration_tests/**/Pods\n/packages/flutter/coverage/\nversion\n\n# packages file containing multi-root paths\n.packages.generated\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\nbuild/\nflutter_*.png\nlinked_*.ds\nunlinked.ds\nunlinked_spec.ds\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n**/android/key.properties\n*.jks\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Flutter.podspec\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/flutter_export_environment.sh\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Coverage\ncoverage/\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n!/dev/ci/**/Gemfile.lock"
  },
  {
    "path": "packages/hydrated_bloc/CHANGELOG.md",
    "content": "# 10.1.1\n\n- fix: make `defaultOnHydrationError` public ([#4567](https://github.com/felangel/bloc/issues/4567))\n\n# 10.1.0\n\n- feat: add `onError` and `HydrationErrorBehavior` to `hydrate` ([#4390](https://github.com/felangel/bloc/pull/4390))\n- docs: improve `storagePrefix` documentation ([#4559](https://github.com/felangel/bloc/pull/4559))\n- chore: update build status badge ([#4502](https://github.com/felangel/bloc/pull/4502))\n- chore: update sponsors ([#4418](https://github.com/felangel/bloc/pull/4418))\n- chore: update example to `flutter_bloc` v9.1.0 ([#4368](https://github.com/felangel/bloc/pull/4368))\n\n# 10.0.0\n\n- **BREAKING** feat!: support for `wasm` ([#4313](https://github.com/felangel/bloc/pull/4313))\n\n  - introduces `HydratedStorageDirectory` and modifies `HydratedStorage.build` signature to use `HydratedStorageDirectory` instead of `Directory` from `dart:io`\n\n  ```dart\n  import 'package:flutter/foundation.dart';\n  import 'package:flutter/material.dart';\n\n  import 'package:flutter_bloc/flutter_bloc.dart';\n  import 'package:hydrated_bloc/hydrated_bloc.dart';\n  import 'package:path_provider/path_provider.dart';\n\n  void main() async {\n    WidgetsFlutterBinding.ensureInitialized();\n    HydratedBloc.storage = await HydratedStorage.build(\n      storageDirectory: kIsWeb\n          ? HydratedStorageDirectory.web\n          : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n    );\n    runApp(const App());\n  }\n  ```\n\n- feat: allow overriding storage per bloc/cubit instance ([#4314](https://github.com/felangel/bloc/pull/4314))\n- feat: migrate to `package:hive_ce` (Hive Community Edition) ([#4262](https://github.com/felangel/bloc/pull/4262))\n- fix: `HydratedStorage.build` should not cache instance ([#4317](https://github.com/felangel/bloc/pull/4317))\n- chore: upgrade to `package:bloc v9.0.0`\n- chore: bump miniumum Dart SDK version to 2.14\n- chore: update sponsors\n- chore: add `funding` to `pubspec.yaml` ([#4200](https://github.com/felangel/bloc/pull/4200))\n\n# 9.1.5\n\n- chore: update copyright year\n- chore: update sponsors\n\n# 9.1.4\n\n- chore: update sponsors ([#4054](https://github.com/felangel/bloc/pull/4054))\n\n# 9.1.3\n\n- fix: `fromJson` can safely access `state` ([#4005](https://github.com/felangel/bloc/pull/4005))\n- chore: add `platforms` to `pubspec.yaml` ([#3993](https://github.com/felangel/bloc/pull/3993))\n- chore: upgrade to `package:mocktail` v1.0.0 ([#3919](https://github.com/felangel/bloc/pull/3919))\n- chore: add `topics` to `pubspec.yaml` ([#3914](https://github.com/felangel/bloc/pull/3914))\n\n# 9.1.2\n\n- fix: clear HydratedStorage instance on close ([#3879](https://github.com/felangel/bloc/pull/3879))\n\n# 9.1.1\n\n- docs: upgrade to Dart 3 ([#3809](https://github.com/felangel/bloc/pull/3809))\n- refactor: standardize analysis_options ([#3809](https://github.com/felangel/bloc/pull/3809))\n- refactor: fix `unawaited` sdk constraint ([#3809](https://github.com/felangel/bloc/pull/3809))\n- refactor: update sdk constraints and fix analysis warnings ([#3809](https://github.com/felangel/bloc/pull/3809))\n\n# 9.1.0\n\n- chore: add screenshots to `pubspec.yaml` ([#3721](https://github.com/felangel/bloc/pull/3721))\n- chore: update example to Dart 2.19 ([#3720](https://github.com/felangel/bloc/pull/3720))\n- chore: update to `bloc ^8.1.1` ([#3719](https://github.com/felangel/bloc/pull/3719))\n- feat: add `storage.close` ([#3705](https://github.com/felangel/bloc/pull/3705))\n- docs: updated docs to reflect v9.0.0 changes ([#3701](https://github.com/felangel/bloc/pull/3701))\n- refactor: upgrade to Dart 2.19 ([#3699](https://github.com/felangel/bloc/pull/3699))\n  - remove deprecated `invariant_booleans` lint rule\n\n# 9.0.0\n\n- **BREAKING**: feat!: reintroduce `HydratedBloc.storage` and remove `HydratedBlocOverrides` ([#3479](https://github.com/felangel/bloc/pull/3479))\n  - upgrade to `bloc: ^8.1.0`\n- fix: update `StorageNotFound` implementation for `toString` ([#3314](https://github.com/felangel/bloc/pull/3314))\n- deps: upgrade to `mocktail ^0.3.0`\n\n# 9.0.0-dev.3\n\n- **BREAKING**: feat!: reintroduce `HydratedBloc.storage` and remove `HydratedBlocOverrides` ([#3479](https://github.com/felangel/bloc/pull/3479))\n  - upgrade to `bloc: ^8.1.0`\n\n# 9.0.0-dev.2\n\n- fix: update `StorageNotFound` implementation for `toString` ([#3314](https://github.com/felangel/bloc/pull/3314))\n\n# 9.0.0-dev.1\n\n- **BREAKING**: feat!: add `createStorage` to `HydratedBlocOverrides.runZoned` ([#3240](https://github.com/felangel/bloc/pull/3240))\n  - deprecate `storage` parameter in `HydratedBlocoverrides.runZoned` in favor of `createStorage`\n\n# 8.1.0\n\n- feat: add `storagePrefix` to support obfuscation tolerance ([#3262](https://github.com/felangel/bloc/pull/3262))\n- docs: update GetStream utm tags ([#3136](https://github.com/felangel/bloc/pull/3136))\n- docs: update VGV sponsors logo ([#3125](https://github.com/felangel/bloc/pull/3125))\n\n# 8.0.0\n\n- **BREAKING**: feat: introduce `HydratedBlocOverrides` API ([#2947](https://github.com/felangel/bloc/pull/2947))\n  - `HydratedBloc.storage` removed in favor of `HydratedBlocOverrides.runZoned` and `HydratedBlocOverrides.current.storage`\n- **BREAKING**: feat: upgrade to `bloc v8.0.0`\n\n# 8.0.0-dev.5\n\n- **BREAKING**: feat: introduce `HydratedBlocOverrides` API ([#2947](https://github.com/felangel/bloc/pull/2947))\n  - `HydratedBloc.storage` removed in favor of `HydratedBlocOverrides.runZoned` and `HydratedBlocOverrides.current.storage`\n\n# 8.0.0-dev.4\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`\n\n# 8.0.0-dev.3\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.4`\n\n# 8.0.0-dev.2\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`\n\n# 8.0.0-dev.1\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`\n\n# 7.1.0\n\n- feat: upgrade to `bloc ^7.2.0`\n\n# 7.0.1\n\n- fix: `HydratedStorage` clear behavior\n\n# 7.0.0\n\n- **BREAKING**: opt into null safety\n  - upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- **BREAKING**: refactor: remove `flutter` dependency\n- **BREAKING**: `storageDirectory` is required when calling `HydratedStorage.build`\n- feat: upgrade to `bloc ^7.0.0`\n- fix: web support with `HydratedStorage.webStorageDirectory`\n- chore: upgrade to `mocktail ^0.1.0`\n- chore: upgrade to `hive ^2.0.0`\n- chore: upgrade to `synchronized: ^3.0.0`\n\n# 7.0.0-nullsafety.4\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.4`\n- chore: upgrade to `mocktail ^0.1.0`\n\n# 7.0.0-nullsafety.3\n\n- fix: web support with `HydratedStorage.webStorageDirectory`\n- chore: upgrade to `hive ^2.0.0`\n- chore: upgrade to `mocktail ^0.0.2-dev.5`\n\n# 7.0.0-nullsafety.2\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.3`\n- chore: upgrade to `hive ^1.6.0-nullsafety.2`\n- chore: upgrade to `synchronized: ^3.0.0`\n\n# 7.0.0-nullsafety.1\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.2`\n\n# 7.0.0-nullsafety.0\n\n- **BREAKING**: opt into null safety\n- **BREAKING**: refactor: upgrade to `bloc ^7.0.0-nullsafety.1`\n- **BREAKING**: refactor: remove `flutter` dependency\n- **BREAKING**: `storageDirectory` is required when calling `HydratedStorage.build`\n- **BREAKING**: `HydratedCubit.storage` is removed in favor of `HydratedBloc.storage`\n- feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n\n# 6.1.0\n\n- feat: export `package:bloc/bloc.dart`\n- deps: update to `bloc: ^6.1.0`\n- deps: require `dart >=2.6.0`\n\n# 6.0.3\n\n- fix: `HydratedStorage` exception due to closed box on `hydrate`\n\n# 6.0.2\n\n- docs: add missing inline documentation for `hydrate`\n\n# 6.0.1\n\n- fix: compatibility with flutter_web\n- chore: upgrade to `bloc ^6.0.1`\n\n# 6.0.0\n\n- **BREAKING**: upgrade to `bloc ^6.0.0`\n- fix: json (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n  - Hydrated: type `'_InternalLinkedHashMap<dynamic, dynamic>'` is not a subtype of type `'Map<String, dynamic>'` ([#1452](https://github.com/felangel/bloc/issues/1452))\n  - Hydrated: HiveError: Cannot write, unknown type: Plan ([#1453](https://github.com/felangel/bloc/issues/1453))\n- fix: handle empty case for list traversal\n- fix: additional complex list (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n- fix: complex list (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n- feat: `StorageNotFound` error thrown if no `Storage` is provided.\n- feat: `HydratedCubit` added for `Cubit` interoperability\n- feat: `HydratedMixin` added for additional flexibility\n- feat: remove external dependency on [package:hydrated_cubit](https://pub.dev/packages/hydrated_cubit)\n- docs: inline documentation updates\n- docs: README updates\n- docs: example application updates\n\n# 6.0.0-dev.5\n\n- fix: handle empty case for list traversal\n\n# 6.0.0-dev.4\n\n- fix: additional complex list (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n\n# 6.0.0-dev.3\n\n- fix: complex list (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n\n# 6.0.0-dev.2\n\n- fix: json (de)serialization errors ([@orsenkucher](https://github.com/orsenkucher))\n  - Hydrated: type `'_InternalLinkedHashMap<dynamic, dynamic>'` is not a subtype of type `'Map<String, dynamic>'` ([#1452](https://github.com/felangel/bloc/issues/1452))\n  - Hydrated: HiveError: Cannot write, unknown type: Plan ([#1453](https://github.com/felangel/bloc/issues/1453))\n\n# 6.0.0-dev.1\n\n- **BREAKING**: upgrade to `bloc ^6.0.0-dev.1`\n- feat: `StorageNotFound` error thrown if no `Storage` is provided.\n- feat: `HydratedCubit` added for `Cubit` interoperability\n- feat: `HydratedMixin` added for additional flexibility\n- feat: remove external dependency on [package:hydrated_cubit](https://pub.dev/packages/hydrated_cubit)\n- docs: inline documentation updates\n- docs: README updates\n- docs: example application updates\n\n# 5.0.3\n\n- fix: excessive storage reads and `fromJson` invocations\n- chore: upgrade to `hydrated_cubit ^0.1.3`\n- chore: upgrade to `bloc ^5.0.1`\n- docs: minor documentation improvements\n\n# 5.0.2\n\n- fix: upgrade to `hydrated_cubit ^0.1.2` to prevent data loss during migration.\n\n# 5.0.1\n\n- fix: export `Storage` interface\n- fix: use `Storage` interface to enable custom `Storage`\n\n# 5.0.0\n\n- **BREAKING**: update to `bloc ^5.0.0`\n- **BREAKING**: extend `hydrated_cubit ^0.1.0`\n- **BREAKING**: `super.initialState` is no longer required\n- docs: minor updates to README\n- docs: logo updates\n\n# 5.0.0-dev.3\n\n- feat: update to `bloc ^5.0.0-dev.11`\n- docs: minor updates to README\n\n# 5.0.0-dev.2\n\n- **BREAKING**: update to `bloc ^5.0.0-dev.10`\n- **BREAKING**: extend `hydrated_cubit ^0.0.3`\n\n# 5.0.0-dev.1\n\n- **BREAKING**: update to `bloc ^5.0.0-dev.7`\n- **BREAKING**: `super.initialState` is no longer required\n\n# 4.1.1\n\n- Remove unnecessary `print` statement\n\n# 4.1.0\n\n- Update default `HydratedStorage` to use `package:hive` (thanks to [@orsenkucher](https://github.com/orsenkucher)).\n- Add encryption support to `HydratedStorage` (thanks to [@orsenkucher](https://github.com/orsenkucher)).\n\n# 4.0.0\n\n- Updated to `bloc: ^4.0.0` and `flutter_bloc: ^4.0.0`\n- `onTransition` moved from `HydratedBlocDelegate` to `HydratedBloc`\n\n# 3.1.0\n\n- Persist `initialState` when initialized (thanks to [@orsenkucher](https://github.com/orsenkucher)).\n- Fix: add `synchronized` to prevent file corruption (thanks to [@orsenkucher](https://github.com/orsenkucher))\n- Refactor `HydratedBlocStorage.getInstance` to avoid using singleton (thanks to [@orsenkucher](https://github.com/orsenkucher))\n- Upgrade to `path_provider: ^1.6.5`\n- Fix: invoke `onError` and continue emitting states when exceptions occur\n\n# 3.0.0\n\n- Updated to `bloc: ^3.0.0`\n\n# 3.0.0-dev.1\n\n- Updated to `bloc: ^3.0.0-dev.1`\n\n# 2.0.0\n\n- Update to `bloc ^2.0.0`\n- Adhere to [effective dart](https://dart.dev/guides/language/effective-dart)\n\n# 1.1.0\n\n- Optional `storageDirectory` can be provided ([#28](https://github.com/felangel/hydrated_bloc/issues/28)).\n- Documentation Updates\n\n# 1.0.0\n\n- Update to bloc `v1.0.0`\n- Documentation Updates\n\n# 0.8.0\n\n- Update to bloc `v0.16.0`\n- Documentation Updates\n\n# 0.7.0\n\n- Desktop support via [path_provider_fde](https://github.com/google/flutter-desktop-embedding/tree/master/plugins/flutter_plugins/path_provider_fde) ([#24](https://github.com/felangel/hydrated_bloc/pull/24)).\n- Documentation and Example Updates\n\n# 0.6.0\n\n- Support clearing individual `HydratedBloc` caches ([#21](https://github.com/felangel/hydrated_bloc/issues/21))\n- Documentation and Example Updates\n\n# 0.5.0\n\n- Support for Desktop ([#18](https://github.com/felangel/hydrated_bloc/pull/18))\n- Documentation and Example Updates\n\n# 0.4.1\n\n- Update to support optional `id` in cases where there are multiple instances of the same `HydratedBloc`\n- Documentation Updates\n\n# 0.4.0\n\n- Update to bloc `v0.15.0`\n- Documentation Updates\n\n# 0.3.2\n\n- Minor Updates to Package Dependencies\n- Documentation Updates\n\n# 0.3.1\n\n- Add guards to `HydratedBlocStorage` to prevent exception if cache is corrupt.\n\n# 0.3.0\n\n- Update `HydratedBlocStorage` to use `getTemporaryDirectory` instead of `getApplicationDocumentsDirectory`\n- Documentation Updates\n\n# 0.2.1\n\n- Bugfix to handle `Blocs` alongside `HydrateBlocs` within the same application.\n- `toJson` can return `null` to avoid persisting the state change\n\n# 0.2.0\n\n- Upated `HydrateBlocDelegate` to have a static `build`\n- Updated `toJson` and `fromJson` to eliminate the need to call `json.encode` and `json.decode` explicitly.\n- `HydratedBlocSharedPreferences` replaced with `HydratedBlocStorage`\n- Removed dependency on `SharedPreferences`\n- Documentation Updates\n\n# 0.1.0\n\n- Renamed `HydratedBlocSharedPreferences` to `HydratedSharedPreferences`\n- Documentation Updates\n\n# 0.0.3\n\nAdded `clear` to `HydratedBlocStorage` API and Documentation Updates\n\n# 0.0.2\n\nDocumentation Updates\n\n# 0.0.1\n\nInitial Version of the library.\n\nIncludes:\n\n- `HydratedBloc`\n- `HydratedBlocDelegate`\n- `HydratedBlocSharedPreferences`\n"
  },
  {
    "path": "packages/hydrated_bloc/LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\nUSE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "packages/hydrated_bloc/README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://github.com/felangel/bloc/raw/master/assets/logos/hydrated_bloc.png\" height=\"100\" alt=\"Hydrated Bloc\">\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n  <a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n  <a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n  <a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n  <a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n  <a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n  <a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n  <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n  <a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n  <a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\nAn extension to [package:bloc](https://github.com/felangel/bloc) which automatically persists and restores bloc and cubit states. Built to work with [package:bloc](https://pub.dev/packages/bloc).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Overview\n\n`hydrated_bloc` exports a `Storage` interface which means it can work with any storage provider. Out of the box, it comes with its own implementation: `HydratedStorage`.\n\n`HydratedStorage` is built on top of [hive](https://pub.dev/packages/hive) for a platform-agnostic, performant storage layer. See the complete [example](https://github.com/felangel/bloc/blob/master/packages/hydrated_bloc/example) for more details.\n\n## Usage\n\n### Setup `HydratedStorage`\n\n```dart\nFuture<void> main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(App());\n}\n```\n\n### Create a HydratedCubit\n\n```dart\nclass CounterCubit extends HydratedCubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => { 'value': state };\n}\n```\n\n### Create a HydratedBloc\n\n```dart\nsealed class CounterEvent {}\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n  }\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => { 'value': state };\n}\n```\n\nNow the `CounterCubit` and `CounterBloc` will automatically persist/restore their state. We can increment the counter value, hot restart, kill the app, etc... and the previous state will be retained.\n\n### HydratedMixin\n\n```dart\nclass CounterCubit extends Cubit<int> with HydratedMixin {\n  CounterCubit() : super(0) {\n    hydrate(); // You must always call `hydrate` when using `HydratedMixin`\n  }\n\n  void increment() => emit(state + 1);\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => { 'value': state };\n}\n```\n\n## Storage Overrides\n\nYou can override the global storage instance for specific `HydratedBloc` or `HydratedCubit` instances by passing a custom storage instance via constructor.\n\n```dart\nclass CounterCubit extends HydratedCubit<int> {\n  CounterCubit() : super(0, storage: EncryptedStorage());\n\n  void increment() => emit(state + 1);\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => { 'value': state };\n}\n```\n\n## Handling Hydration Errors\n\nYou can optionally pass a custom `onError` callback to `hydrate` in order to handle any hydration errors and/or customize the caching behavior when a hydration error occurs.\n\n```dart\nclass CounterBloc extends Bloc<CounterEvent, int> with HydratedMixin {\n  CounterBloc() : super(0) {\n    hydrate(\n      onError: (error, stackTrace) {\n        // Do something in response to hydration errors.\n        // Must return a `HydrationErrorBehavior` to specify whether subsequent\n        // state changes should be persisted.\n        return HydrationErrorBehavior.retain; // Retain the previous state.\n      }\n    );\n  }\n  ...\n}\n```\n\n## Custom Storage Directory\n\nAny `storageDirectory` can be used when creating an instance of `HydratedStorage`:\n\n```dart\nfinal storage = await HydratedStorage.build(\n  storageDirectory: await getApplicationDocumentsDirectory(),\n);\n```\n\n## Custom Hydrated Storage\n\nIf the default `HydratedStorage` doesn't meet your needs, you can always implement a custom `Storage` by simply implementing the `Storage` interface and initializing `HydratedBloc` with the custom `Storage`.\n\n```dart\n// my_hydrated_storage.dart\n\nclass MyHydratedStorage implements Storage {\n  @override\n  dynamic read(String key) {\n    // TODO: implement read\n  }\n\n  @override\n  Future<void> write(String key, dynamic value) async {\n    // TODO: implement write\n  }\n\n  @override\n  Future<void> delete(String key) async {\n    // TODO: implement delete\n  }\n\n  @override\n  Future<void> clear() async {\n    // TODO: implement clear\n  }\n}\n```\n\n```dart\n// main.dart\nHydratedBloc.storage = MyHydratedStorage();\nrunApp(MyApp());\n```\n\n## Custom Storage Prefix\n\nThe `storagePrefix` defines the unique storage namespace for your `HydratedBloc` or `HydratedCubit`.\n\nBy default, it uses `runtimeType`, which isn't resilient to obfuscation or minification in production. If `runtimeType` changes, your saved state will be lost. This is especially relevant for web apps, where code changes frequently alter the minified `runtimeType`.\n\nConsider overriding `storagePrefix` in production for more resilient, persistent storage.\n\n```dart\nclass CounterCubit extends HydratedCubit<int> {\n  CounterCubit() : super(0);\n\n  @override\n  String get storagePrefix => 'CounterCubit';\n}\n```\n\n## Testing\n\nWhen writing unit tests for code that uses `HydratedBloc`, it is recommended to stub the `Storage` implementation using `package:mocktail`.\n\n```dart\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\n\nclass MockStorage extends Mock implements Storage {}\n\nvoid main() {\n  late Storage storage;\n\n  setUp(() {\n    storage = MockStorage();\n    when(\n      () => storage.write(any(), any<dynamic>()),\n    ).thenAnswer((_) async {});\n    HydratedBloc.storage = storage;\n  });\n\n  // ...\n}\n```\n\nYou can also stub the `storage.read` API in individual tests to return cached state:\n\n```dart\ntestWidgets('...', (tester) async {\n  when<dynamic>(() => storage.read('$MyBloc')).thenReturn(MyState().toJson());\n\n  // ...\n});\n```\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/hydrated_bloc/analysis_options.yaml",
    "content": "include: \n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\nanalyzer:\n  exclude:\n    - test/cubits/freezed_cubit.freezed.dart\n    - test/cubits/freezed_cubit.g.dart\n"
  },
  {
    "path": "packages/hydrated_bloc/benchmark/README.md",
    "content": "# benchmark\n\n## Conditions\nBenchmarks made on `Samsung Galaxy S9+`, 16 iterations per test.  \nBloc count is number of `storageTokens` used.  \nState size represents how much raw data takes (dismissing expense on serialization).  \nI.e for state of 1KB, list of 256 ints will be saved. Strings are saved as strings.  \nAES is AES\n\n## Index table\n### AES-ON\n\n| Blocs | 4Bytes | 64Bytes | 256Bytes | 1KB | 4KB | 16KB       | 64KB       | 1MB        | 4MB |\n|-------|--------|---------|----------|-----|-----|------------|------------|------------|-----|\n| 1     | +      | +       | +        | +   | +   | +          | +          | +          | +   |\n| 15    | +      | +       | +        | +   | +   | +          | +          | multi+hive | -   |\n| 30    | +      | +       | +        | +   | +   | +          | multi+hive | only hive  | -   |\n| 75    | +      | +       | +        | +   | +   | +          | multi+hive | only hive  | -   |\n| 150   | +      | +       | +        | +   | +   | multi+hive | multi+hive | -          | -   |\n\n### AES-OFF\n\n| Blocs | 4Bytes | 64Bytes | 256Bytes | 1KB | 4KB | 16KB | 64KB | 1MB        | 4MB        |\n|-------|--------|---------|----------|-----|-----|------|------|------------|------------|\n| 1     | +      | +       | +        | +   | +   | +    | +    | +          | +          |\n| 15    | +      | +       | +        | +   | +   | +    | +    | multi+hive | multi+hive |\n| 30    | +      | +       | +        | +   | +   | +    | +    | multi+hive | multi+hive |\n| 75    | +      | +       | +        | +   | +   | +    | +    | multi+hive | -          |\n| 150   | +      | +       | +        | +   | +   | +    | +    | multi+hive | -          |\n\n## Generalized graphs\n\n<img src=\"./general.svg\">\n\n## Digest\n> Pretty clear hive destroys.  \n> &mdash; <cite>Felix</cite>  \n\nAnd its hard to disagree with him🤪"
  },
  {
    "path": "packages/hydrated_bloc/build.yaml",
    "content": "targets:\n  $default:\n    builders:\n      source_gen|combining_builder:\n        options:\n          ignore_for_file:\n            - implicit_dynamic_parameter\n            - unnecessary_null_checks"
  },
  {
    "path": "packages/hydrated_bloc/example/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# Visual Studio Code related\n.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/Flutter/.last_build_id\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": "packages/hydrated_bloc/example/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "packages/hydrated_bloc/example/README.md",
    "content": "# Hydrated Bloc Example\n\nA sample project that showcases how to use package:hydrated_bloc.\n"
  },
  {
    "path": "packages/hydrated_bloc/example/analysis_options.yaml",
    "content": "include: ../../../analysis_options.yaml\nlinter:\n  rules:\n    public_member_api_docs: false\n"
  },
  {
    "path": "packages/hydrated_bloc/example/lib/main.dart",
    "content": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\n\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:path_provider/path_provider.dart';\n\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  HydratedBloc.storage = await HydratedStorage.build(\n    storageDirectory: kIsWeb\n        ? HydratedStorageDirectory.web\n        : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  );\n  runApp(const App());\n}\n\nclass App extends StatelessWidget {\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => BrightnessCubit(),\n      child: const AppView(),\n    );\n  }\n}\n\nclass AppView extends StatelessWidget {\n  const AppView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<BrightnessCubit, Brightness>(\n      builder: (context, brightness) {\n        return MaterialApp(\n          theme: ThemeData(brightness: brightness),\n          home: const CounterPage(),\n        );\n      },\n    );\n  }\n}\n\nclass CounterPage extends StatelessWidget {\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider<CounterBloc>(\n      create: (_) => CounterBloc(),\n      child: const CounterView(),\n    );\n  }\n}\n\nclass CounterView extends StatelessWidget {\n  const CounterView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    return Scaffold(\n      appBar: AppBar(title: const Text('Counter')),\n      body: Center(\n        child: BlocBuilder<CounterBloc, int>(\n          builder: (context, state) {\n            return Text('$state', style: textTheme.displayMedium);\n          },\n        ),\n      ),\n      floatingActionButton: Column(\n        crossAxisAlignment: CrossAxisAlignment.end,\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: <Widget>[\n          FloatingActionButton(\n            child: const Icon(Icons.brightness_6),\n            onPressed: () => context.read<BrightnessCubit>().toggleBrightness(),\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.add),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterIncrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.remove),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterDecrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.delete_forever),\n            onPressed: () => HydratedBloc.storage.clear(),\n          ),\n        ],\n      ),\n    );\n  }\n}\n\nsealed class CounterEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nfinal class CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends HydratedBloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n}\n\nclass BrightnessCubit extends HydratedCubit<Brightness> {\n  BrightnessCubit() : super(Brightness.light);\n\n  void toggleBrightness() {\n    emit(state == Brightness.light ? Brightness.dark : Brightness.light);\n  }\n\n  @override\n  Brightness fromJson(Map<String, dynamic> json) {\n    return Brightness.values[json['brightness'] as int];\n  }\n\n  @override\n  Map<String, dynamic> toJson(Brightness state) {\n    return <String, int>{'brightness': state.index};\n  }\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/example/pubspec.yaml",
    "content": "name: example\ndescription: A sample project that showcases how to use package:hydrated_bloc.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.0.0 <4.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  hydrated_bloc: ^10.0.0\n  path_provider: ^2.0.15\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "packages/hydrated_bloc/example/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../bloc\n  flutter_bloc:\n    path: ../../flutter_bloc\n  hydrated_bloc:\n    path: ../\n"
  },
  {
    "path": "packages/hydrated_bloc/example/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"example\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>example</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/hydrated_bloc/example/web/manifest.json",
    "content": "{\n    \"name\": \"example\",\n    \"short_name\": \"example\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/hydrated_bloc.dart",
    "content": "/// An extension to [package:bloc](https://github.com/felangel/bloc)\n/// which automatically persists and restores bloc and cubit states.\n/// Built to work with [package:bloc](https://pub.dev/packages/bloc).\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary hydrated_bloc;\n\nexport 'package:bloc/bloc.dart';\n\nexport 'src/hydrated_bloc.dart' hide NIL;\nexport 'src/hydrated_cipher.dart';\nexport 'src/hydrated_storage.dart';\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/src/_migration/_migration_io.dart",
    "content": "import 'dart:convert';\nimport 'dart:io';\n\nimport 'package:hive_ce/hive.dart';\n\n/// The storage migration implementation when using dart:io.\nFuture<dynamic> migrate(String directory, Box<dynamic> box) async {\n  final file = File('$directory/.hydrated_bloc.json');\n  if (file.existsSync()) {\n    try {\n      final dynamic storageJson = json.decode(await file.readAsString());\n      final cache = (storageJson as Map).cast<String, String>();\n      for (final key in cache.keys) {\n        try {\n          final string = cache[key];\n          final dynamic object = json.decode(string ?? '');\n          await box.put(key, object);\n        } catch (_) {}\n      }\n    } catch (_) {}\n    await file.delete();\n  }\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/src/_migration/_migration_stub.dart",
    "content": "import 'package:hive_ce/hive.dart';\n\n/// The storage migration implementation stub.\nFuture<dynamic> migrate(String directory, Box<dynamic> box) async {}\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/src/hydrated_bloc.dart",
    "content": "// ignore_for_file: avoid_catching_errors\n\nimport 'dart:async';\n\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:meta/meta.dart';\n\n/// Describes the various caching behaviors when a hydration error occurs.\nenum HydrationErrorBehavior {\n  /// Overwrite the cached state when a hydration error occurs.\n  /// Any newly emitted states will be persisted which means previously cached\n  /// state will be overwritten. This is the default behavior.\n  overwrite,\n\n  /// Retain the cached state when a hydration error occurs.\n  /// Any newly emitted states will not be persisted until hydrate succeeds\n  /// which means the previously cached state will be retained.\n  retain,\n}\n\n/// Signature of the `onError` callback during `hydrate`.\ntypedef OnHydrationError = HydrationErrorBehavior Function(\n  Object error,\n  StackTrace stackTrace,\n);\n\n/// The default hydration `onError` implementation.\n/// Returns [HydrationErrorBehavior.overwrite].\nHydrationErrorBehavior defaultOnHydrationError(\n  Object error,\n  StackTrace stackTrace,\n) {\n  return HydrationErrorBehavior.overwrite;\n}\n\n/// {@template hydrated_bloc}\n/// Specialized [Bloc] which handles initializing the [Bloc] state\n/// based on the persisted state. This allows state to be persisted\n/// across hot restarts as well as complete app restarts.\n///\n/// ```dart\n/// abstract class CounterEvent {}\n/// class CounterIncrementPressed extends CounterEvent {}\n/// class CounterDecrementPressed extends CounterEvent {}\n///\n/// class CounterBloc extends HydratedBloc<CounterEvent, int> {\n///   CounterBloc() : super(0) {\n///     on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n///     on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n///   }\n///\n///   @override\n///   int fromJson(Map<String, dynamic> json) => json['value'] as int;\n///\n///   @override\n///   Map<String, int> toJson(int state) => {'value': state};\n/// }\n/// ```\n///\n/// {@endtemplate}\nabstract class HydratedBloc<Event, State> extends Bloc<Event, State>\n    with HydratedMixin {\n  /// {@macro hydrated_bloc}\n  HydratedBloc(\n    State state, {\n    Storage? storage,\n    OnHydrationError onHydrationError = defaultOnHydrationError,\n  }) : super(state) {\n    hydrate(storage: storage, onError: onHydrationError);\n  }\n\n  static Storage? _storage;\n\n  /// Setter for instance of [Storage] which will be used to\n  /// manage persisting/restoring the [Bloc] state.\n  static set storage(Storage? storage) => _storage = storage;\n\n  /// Instance of [Storage] which will be used to\n  /// manage persisting/restoring the [Bloc] state.\n  static Storage get storage {\n    if (_storage == null) throw const StorageNotFound();\n    return _storage!;\n  }\n}\n\n/// {@template hydrated_cubit}\n/// Specialized [Cubit] which handles initializing the [Cubit] state\n/// based on the persisted state. This allows state to be persisted\n/// across application restarts.\n///\n/// ```dart\n/// class CounterCubit extends HydratedCubit<int> {\n///   CounterCubit() : super(0);\n///\n///   void increment() => emit(state + 1);\n///   void decrement() => emit(state - 1);\n///\n///   @override\n///   int fromJson(Map<String, dynamic> json) => json['value'] as int;\n///\n///   @override\n///   Map<String, int> toJson(int state) => {'value': state};\n/// }\n/// ```\n///\n/// {@endtemplate}\nabstract class HydratedCubit<State> extends Cubit<State>\n    with HydratedMixin<State> {\n  /// {@macro hydrated_cubit}\n  HydratedCubit(\n    State state, {\n    Storage? storage,\n    OnHydrationError onHydrationError = defaultOnHydrationError,\n  }) : super(state) {\n    hydrate(storage: storage, onError: onHydrationError);\n  }\n}\n\n/// A mixin which enables automatic state persistence\n/// for [Bloc] and [Cubit] classes.\n///\n/// The [hydrate] method must be invoked in the constructor body\n/// when using the [HydratedMixin] directly.\n///\n/// If a mixin is not necessary, it is recommended to\n/// extend [HydratedBloc] and [HydratedCubit] respectively.\n///\n/// ```dart\n/// class CounterBloc extends Bloc<CounterEvent, int> with HydratedMixin {\n///  CounterBloc() : super(0) {\n///    hydrate();\n///  }\n///  ...\n/// }\n/// ```\n///\n/// See also:\n///\n/// * [HydratedBloc] to enable automatic state persistence/restoration with [Bloc]\n/// * [HydratedCubit] to enable automatic state persistence/restoration with [Cubit]\n/// * [HydrationErrorBehavior] to customize state persistence during hydration\n///   errors.\n///\nmixin HydratedMixin<State> on BlocBase<State> {\n  late final Storage __storage;\n  HydrationErrorBehavior? _errorBehavior;\n  var _onErrorCallbackInProgress = false;\n\n  /// Populates the internal state storage with the latest state.\n  /// This should be called when using the [HydratedMixin]\n  /// directly within the constructor body.\n  ///\n  /// ```dart\n  /// class CounterBloc extends Bloc<CounterEvent, int> with HydratedMixin {\n  ///  CounterBloc() : super(0) {\n  ///    hydrate();\n  ///  }\n  ///  ...\n  /// }\n  /// ```\n  ///\n  /// Optionally, pass a custom `onError` callback to handle hydration errors:\n  ///\n  /// ```dart\n  /// class CounterBloc extends Bloc<CounterEvent, int> with HydratedMixin {\n  ///  CounterBloc() : super(0) {\n  ///    hydrate(\n  ///      onError: (error, stackTrace) {\n  ///        // Do something in response to hydration errors.\n  ///        // Must return a `HydrationErrorBehavior` to specify whether subsequent\n  ///        // state changes should be persisted.\n  ///        return HydrationErrorBehavior.retain; // Retain the previous state.\n  ///      }\n  ///    );\n  ///  }\n  ///  ...\n  /// }\n  /// ```\n  void hydrate({\n    Storage? storage,\n    OnHydrationError onError = defaultOnHydrationError,\n  }) {\n    __storage = storage ??= HydratedBloc.storage;\n    try {\n      final stateJson = __storage.read(storageToken) as Map<dynamic, dynamic>?;\n      _state = stateJson != null ? _fromJson(stateJson) : super.state;\n      _errorBehavior = null;\n    } catch (error, stackTrace) {\n      this.onError(error, stackTrace);\n      _state = super.state;\n      _onErrorCallbackInProgress = true;\n      _errorBehavior = onError(error, stackTrace);\n    } finally {\n      _onErrorCallbackInProgress = false;\n    }\n\n    if (_errorBehavior == HydrationErrorBehavior.retain) return;\n\n    try {\n      final stateJson = _toJson(state);\n      if (stateJson != null) {\n        __storage\n            .write(storageToken, stateJson)\n            .then((_) {}, onError: this.onError);\n      }\n    } catch (error, stackTrace) {\n      this.onError(error, stackTrace);\n      if (error is StorageNotFound) rethrow;\n    }\n  }\n\n  State? _state;\n\n  @override\n  State get state => _state ?? super.state;\n\n  @override\n  void onChange(Change<State> change) {\n    super.onChange(change);\n    final state = change.nextState;\n    _state = state;\n\n    if (_onErrorCallbackInProgress) return;\n    if (_errorBehavior == HydrationErrorBehavior.retain) return;\n\n    try {\n      final stateJson = _toJson(state);\n      if (stateJson != null) {\n        __storage.write(storageToken, stateJson).then((_) {}, onError: onError);\n      }\n    } catch (error, stackTrace) {\n      onError(error, stackTrace);\n      rethrow;\n    }\n  }\n\n  State? _fromJson(dynamic json) {\n    final dynamic traversedJson = _traverseRead(json);\n    final castJson = _cast<Map<String, dynamic>>(traversedJson);\n    return fromJson(castJson ?? <String, dynamic>{});\n  }\n\n  Map<String, dynamic>? _toJson(State state) {\n    return _cast<Map<String, dynamic>>(_traverseWrite(toJson(state)).value);\n  }\n\n  dynamic _traverseRead(dynamic value) {\n    if (value is Map) {\n      return value.map<String, dynamic>((dynamic key, dynamic value) {\n        return MapEntry<String, dynamic>(\n          _cast<String>(key) ?? '',\n          _traverseRead(value),\n        );\n      });\n    }\n    if (value is List) {\n      for (var i = 0; i < value.length; i++) {\n        value[i] = _traverseRead(value[i]);\n      }\n    }\n    return value;\n  }\n\n  T? _cast<T>(dynamic x) => x is T ? x : null;\n\n  _Traversed _traverseWrite(Object? value) {\n    final dynamic traversedAtomicJson = _traverseAtomicJson(value);\n    if (traversedAtomicJson is! NIL) {\n      return _Traversed.atomic(traversedAtomicJson);\n    }\n    final dynamic traversedComplexJson = _traverseComplexJson(value);\n    if (traversedComplexJson is! NIL) {\n      return _Traversed.complex(traversedComplexJson);\n    }\n    try {\n      _checkCycle(value);\n      final dynamic customJson = _toEncodable(value);\n      final dynamic traversedCustomJson = _traverseJson(customJson);\n      if (traversedCustomJson is NIL) {\n        throw HydratedUnsupportedError(value);\n      }\n      _removeSeen(value);\n      return _Traversed.complex(traversedCustomJson);\n    } on HydratedCyclicError catch (e) {\n      throw HydratedUnsupportedError(value, cause: e);\n    } on HydratedUnsupportedError {\n      rethrow; // do not stack `HydratedUnsupportedError`\n    } catch (e) {\n      throw HydratedUnsupportedError(value, cause: e);\n    }\n  }\n\n  dynamic _traverseAtomicJson(dynamic object) {\n    if (object is num) {\n      if (!object.isFinite) return const NIL();\n      return object;\n    } else if (identical(object, true)) {\n      return true;\n    } else if (identical(object, false)) {\n      return false;\n    } else if (object == null) {\n      return null;\n    } else if (object is String) {\n      return object;\n    }\n    return const NIL();\n  }\n\n  dynamic _traverseComplexJson(dynamic object) {\n    if (object is List) {\n      if (object.isEmpty) return object;\n      _checkCycle(object);\n      List<dynamic>? list;\n      for (var i = 0; i < object.length; i++) {\n        final traversed = _traverseWrite(object[i]);\n        list ??= traversed.outcome == _Outcome.atomic\n            ? object.sublist(0)\n            : (<dynamic>[]..length = object.length);\n        list[i] = traversed.value;\n      }\n      _removeSeen(object);\n      return list;\n    } else if (object is Map) {\n      _checkCycle(object);\n      final map = <String, dynamic>{};\n      object.forEach((dynamic key, dynamic value) {\n        final castKey = key?.toString();\n        if (castKey != null) map[castKey] = _traverseWrite(value).value;\n      });\n      _removeSeen(object);\n      return map;\n    }\n    return const NIL();\n  }\n\n  dynamic _traverseJson(dynamic object) {\n    final dynamic traversedAtomicJson = _traverseAtomicJson(object);\n    return traversedAtomicJson is! NIL\n        ? traversedAtomicJson\n        : _traverseComplexJson(object);\n  }\n\n  // ignore: avoid_dynamic_calls\n  dynamic _toEncodable(dynamic object) => object.toJson();\n\n  final _seen = <dynamic>[];\n\n  void _checkCycle(Object? object) {\n    for (var i = 0; i < _seen.length; i++) {\n      if (identical(object, _seen[i])) {\n        throw HydratedCyclicError(object);\n      }\n    }\n    _seen.add(object);\n  }\n\n  void _removeSeen(dynamic object) {\n    assert(_seen.isNotEmpty, 'seen must not be empty');\n    assert(identical(_seen.last, object), 'last seen object must be identical');\n    _seen.removeLast();\n  }\n\n  /// [id] is used to uniquely identify multiple instances\n  /// of the same [HydratedBloc] type.\n  /// In most cases it is not necessary;\n  /// however, if you wish to intentionally have multiple instances\n  /// of the same [HydratedBloc], then you must override [id]\n  /// and return a unique identifier for each [HydratedBloc] instance\n  /// in order to keep the caches independent of each other.\n  String get id => '';\n\n  /// Storage prefix which can be overridden to provide a custom\n  /// storage namespace.\n  /// Defaults to [runtimeType] but should be overridden in cases\n  /// where stored data should be resilient to obfuscation, minification\n  /// or persist between debug/release builds.\n  String get storagePrefix => runtimeType.toString();\n\n  /// `storageToken` is used as registration token for hydrated storage.\n  /// Composed of [storagePrefix] and [id].\n  @nonVirtual\n  String get storageToken => '$storagePrefix$id';\n\n  /// [clear] is used to wipe or invalidate the cache of a [HydratedBloc].\n  /// Calling [clear] will delete the cached state of the bloc\n  /// but will not modify the current state of the bloc.\n  Future<void> clear() => __storage.delete(storageToken);\n\n  /// Responsible for converting the `Map<String, dynamic>` representation\n  /// of the bloc state into a concrete instance of the bloc state.\n  State? fromJson(Map<String, dynamic> json);\n\n  /// Responsible for converting a concrete instance of the bloc state\n  /// into the the `Map<String, dynamic>` representation.\n  ///\n  /// If [toJson] returns `null`, then no state changes will be persisted.\n  Map<String, dynamic>? toJson(State state);\n}\n\n/// Reports that an object could not be serialized due to cyclic references.\n/// When the cycle is detected, a [HydratedCyclicError] is thrown.\nclass HydratedCyclicError extends HydratedUnsupportedError {\n  /// The first object that was detected as part of a cycle.\n  HydratedCyclicError(Object? object) : super(object);\n\n  @override\n  String toString() => 'Cyclic error while state traversing';\n}\n\n/// {@template storage_not_found}\n/// Exception thrown if there was no [HydratedStorage] specified.\n/// This is most likely due to forgetting to setup the [HydratedStorage]:\n///\n/// ```dart\n/// void main() async {\n///   WidgetsFlutterBinding.ensureInitialized();\n///   HydratedCubit.storage = await HydratedStorage.build();\n///   runApp(MyApp());\n/// }\n/// ```\n///\n/// {@endtemplate}\nclass StorageNotFound implements Exception {\n  /// {@macro storage_not_found}\n  const StorageNotFound();\n\n  @override\n  String toString() {\n    return 'Storage was accessed before it was initialized.\\n'\n        'Please ensure that storage has been initialized.\\n\\n'\n        'For example:\\n\\n'\n        'HydratedBloc.storage = await HydratedStorage.build();';\n  }\n}\n\n/// Reports that an object could not be serialized.\n/// The [unsupportedObject] field holds object that failed to be serialized.\n///\n/// If an object isn't directly serializable, the serializer calls the `toJson`\n/// method on the object. If that call fails, the error will be stored in the\n/// [cause] field. If the call returns an object that isn't directly\n/// serializable, the [cause] is null.\nclass HydratedUnsupportedError extends Error {\n  /// The object that failed to be serialized.\n  /// Error of attempt to serialize through `toJson` method.\n  HydratedUnsupportedError(\n    this.unsupportedObject, {\n    this.cause,\n  });\n\n  /// The object that could not be serialized.\n  final Object? unsupportedObject;\n\n  /// The exception thrown when trying to convert the object.\n  final Object? cause;\n\n  @override\n  String toString() {\n    final safeString = Error.safeToString(unsupportedObject);\n    final prefix = cause != null\n        ? 'Converting object to an encodable object failed:'\n        : 'Converting object did not return an encodable object:';\n    return '$prefix $safeString';\n  }\n}\n\n/// {@template NIL}\n/// Type which represents objects that do not support json encoding\n///\n/// This should never be used and is exposed only for testing purposes.\n/// {@endtemplate}\n@visibleForTesting\nclass NIL {\n  /// {@macro NIL}\n  const NIL();\n}\n\nenum _Outcome { atomic, complex }\n\nclass _Traversed {\n  _Traversed._({required this.outcome, required this.value});\n  _Traversed.atomic(dynamic value)\n      : this._(outcome: _Outcome.atomic, value: value);\n  _Traversed.complex(dynamic value)\n      : this._(outcome: _Outcome.complex, value: value);\n  final _Outcome outcome;\n  final dynamic value;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/src/hydrated_cipher.dart",
    "content": "import 'dart:typed_data';\n\nimport 'package:hive_ce/hive.dart';\n\n/// Abstract cipher can be implemented to customize encryption.\nabstract class HydratedCipher implements HiveCipher {\n  /// Calculate a hash of the key. Make sure to use a secure hash.\n  @override\n  int calculateKeyCrc();\n\n  /// The maximum size the input can have after it has been encrypted.\n  @override\n  int maxEncryptedSize(Uint8List inp);\n\n  /// Encrypt the given bytes.\n  @override\n  int encrypt(\n    Uint8List inp,\n    int inpOff,\n    int inpLength,\n    Uint8List out,\n    int outOff,\n  );\n\n  /// Decrypt the given bytes.\n  @override\n  int decrypt(\n    Uint8List inp,\n    int inpOff,\n    int inpLength,\n    Uint8List out,\n    int outOff,\n  );\n}\n\n/// Default encryption algorithm. Uses AES256 CBC with PKCS7 padding.\nclass HydratedAesCipher extends HiveAesCipher implements HydratedCipher {\n  /// Create a cipher with the given [key].\n  HydratedAesCipher(List<int> key) : super(key);\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/lib/src/hydrated_storage.dart",
    "content": "import 'dart:async';\n\nimport 'package:hive_ce/hive.dart';\n// ignore: implementation_imports\nimport 'package:hive_ce/src/hive_impl.dart';\nimport 'package:hydrated_bloc/src/_migration/_migration_stub.dart'\n    if (dart.library.io) 'package:hydrated_bloc/src/_migration/_migration_io.dart';\nimport 'package:hydrated_bloc/src/hydrated_cipher.dart';\nimport 'package:meta/meta.dart';\nimport 'package:synchronized/synchronized.dart';\n\n/// Interface which is used to persist and retrieve state changes.\nabstract class Storage {\n  /// Returns value for key\n  dynamic read(String key);\n\n  /// Persists key value pair\n  Future<void> write(String key, dynamic value);\n\n  /// Deletes key value pair\n  Future<void> delete(String key);\n\n  /// Clears all key value pairs from storage\n  Future<void> clear();\n\n  /// Close the storage instance which will free any allocated resources.\n  /// A storage instance can no longer be used once it is closed.\n  Future<void> close();\n}\n\n/// {@template hydrated_storage_directory}\n/// A platform-agnostic storage directory representation.\n/// {@endtemplate}\nclass HydratedStorageDirectory {\n  /// {@macro hydrated_storage_directory}\n  const HydratedStorageDirectory(this.path);\n\n  /// The path to the storage directory.\n  final String path;\n\n  /// Sentinel directory used to determine that web storage should be used\n  /// when initializing [HydratedStorage].\n  ///\n  /// ```dart\n  /// await HydratedStorage.build(\n  ///   storageDirectory: HydratedStorageDirectory.web,\n  /// );\n  /// ```\n  static const web = HydratedStorageDirectory('');\n}\n\n/// {@template hydrated_storage}\n/// Implementation of [Storage] which uses [package:hive_ce](https://pub.dev/packages/hive_ce)\n/// to persist and retrieve state changes from the local device.\n/// {@endtemplate}\nclass HydratedStorage implements Storage {\n  /// {@macro hydrated_storage}\n  @visibleForTesting\n  HydratedStorage(this._box);\n\n  /// Returns an instance of [HydratedStorage].\n  /// [storageDirectory] is required.\n  ///\n  /// For web, use [HydratedStorageDirectory.web] as the `storageDirectory`\n  ///\n  /// ```dart\n  /// import 'package:flutter/foundation.dart';\n  /// import 'package:flutter/material.dart';\n  ///\n  /// import 'package:hydrated_bloc/hydrated_bloc.dart';\n  /// import 'package:path_provider/path_provider.dart';\n  ///\n  /// Future<void> main() async {\n  ///   WidgetsFlutterBinding.ensureInitialized();\n  ///   HydratedBloc.storage = await HydratedStorage.build(\n  ///     storageDirectory: kIsWeb\n  ///         ? HydratedStorageDirectory.web\n  ///         : HydratedStorageDirectory((await getTemporaryDirectory()).path),\n  ///   );\n  ///   runApp(App());\n  /// }\n  /// ```\n  ///\n  /// With [encryptionCipher] you can provide custom encryption.\n  /// Following snippet shows how to make default one:\n  /// ```dart\n  /// import 'package:crypto/crypto.dart';\n  /// import 'package:hydrated_bloc/hydrated_bloc.dart';\n  ///\n  /// const password = 'hydration';\n  /// final byteskey = sha256.convert(utf8.encode(password)).bytes;\n  /// return HydratedAesCipher(byteskey);\n  /// ```\n  static Future<HydratedStorage> build({\n    required HydratedStorageDirectory storageDirectory,\n    HydratedCipher? encryptionCipher,\n  }) {\n    return _lock.synchronized(() async {\n      // Use HiveImpl directly to avoid conflicts with existing Hive.init\n      // https://github.com/hivedb/hive/issues/336\n      hive = HiveImpl();\n      Box<dynamic> box;\n\n      if (storageDirectory == HydratedStorageDirectory.web) {\n        box = await hive.openBox<dynamic>(\n          'hydrated_box',\n          encryptionCipher: encryptionCipher,\n        );\n      } else {\n        hive.init(storageDirectory.path);\n        box = await hive.openBox<dynamic>(\n          'hydrated_box',\n          encryptionCipher: encryptionCipher,\n        );\n        await migrate(storageDirectory.path, box);\n      }\n\n      return HydratedStorage(box);\n    });\n  }\n\n  /// Internal instance of [HiveImpl].\n  /// It should only be used for testing.\n  @visibleForTesting\n  static late HiveInterface hive;\n\n  static final _lock = Lock();\n\n  final Box<dynamic> _box;\n\n  @override\n  dynamic read(String key) => _box.isOpen ? _box.get(key) : null;\n\n  @override\n  Future<void> write(String key, dynamic value) async {\n    if (_box.isOpen) {\n      return _lock.synchronized(() => _box.put(key, value));\n    }\n  }\n\n  @override\n  Future<void> delete(String key) async {\n    if (_box.isOpen) {\n      return _lock.synchronized(() => _box.delete(key));\n    }\n  }\n\n  @override\n  Future<void> clear() async {\n    if (_box.isOpen) {\n      return _lock.synchronized(_box.clear);\n    }\n  }\n\n  @override\n  Future<void> close() async {\n    if (_box.isOpen) {\n      return _lock.synchronized(_box.close);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/pubspec.yaml",
    "content": "name: hydrated_bloc\ndescription: An extension to the bloc state management library which automatically persists and restores bloc states.\nversion: 10.1.1\nrepository: https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://bloclibrary.dev\ndocumentation: https://bloclibrary.dev/getting-started\ntopics: [bloc, cache, state-management]\nfunding: [https://github.com/sponsors/felangel]\n\nplatforms:\n  android:\n  ios:\n  linux:\n  macos:\n  web:\n  windows:\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  hive_ce: ^2.0.0\n  meta: ^1.3.0\n  synchronized: ^3.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  build_runner: ^2.3.3\n  collection: ^1.17.1\n  crypto: ^3.0.2\n  freezed: ^2.3.2\n  freezed_annotation: ^2.2.0\n  json_annotation: ^4.8.0\n  json_serializable: ^6.6.1\n  mocktail: ^1.0.0\n  path: ^1.8.3\n  test: ^1.22.2\n  uuid: ^3.0.7\n\nscreenshots:\n  - description: The hydrated bloc package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/hydrated_bloc/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/bad_cubit.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\n\nclass BadCubit extends HydratedCubit<BadState?> {\n  BadCubit() : super(null);\n\n  void setBad([dynamic badObject = Object]) => emit(BadState(badObject));\n\n  @override\n  Map<String, dynamic>? toJson(BadState? state) => state?.toJson();\n\n  @override\n  BadState? fromJson(Map<String, dynamic> json) => null;\n}\n\nclass BadState {\n  BadState(this.badObject);\n\n  final dynamic badObject;\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'bad_obj': badObject,\n    };\n  }\n}\n\nclass VeryBadObject {\n  dynamic toJson() {\n    return Object;\n  }\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/cubits.dart",
    "content": "export 'bad_cubit.dart';\nexport 'cyclic_cubit.dart';\nexport 'freezed_cubit.dart';\nexport 'from_json_state_cubit.dart';\nexport 'json_serializable_cubit.dart';\nexport 'list_cubit.dart';\nexport 'manual_cubit.dart';\nexport 'season_palette_cubit.dart';\nexport 'simple_cubit.dart';\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/cyclic_cubit.dart",
    "content": "// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes\n\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\nclass CyclicCubit extends HydratedCubit<Cycle1?> {\n  CyclicCubit() : super(null);\n\n  void setCyclic(Cycle1 cycle1) => emit(cycle1);\n\n  @override\n  Map<String, dynamic>? toJson(Cycle1? state) => state?.toJson();\n\n  @override\n  Cycle1 fromJson(Map<String, dynamic> json) => Cycle1.fromJson(json);\n}\n\nclass Cycle1 {\n  Cycle1([this.cycle2]);\n\n  factory Cycle1.fromJson(Map<String, dynamic> json) {\n    return Cycle1(\n      json['cycle2'] as Cycle2,\n    );\n  }\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'cycle2': cycle2,\n    };\n  }\n\n  Cycle2? cycle2;\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n    return o is Cycle1 && o.cycle2 == cycle2;\n  }\n\n  @override\n  int get hashCode => cycle2.hashCode;\n}\n\nclass Cycle2 {\n  Cycle2([this.cycle1]);\n\n  factory Cycle2.fromJson(Map<String, dynamic> json) {\n    return Cycle2(\n      json['cycle1'] as Cycle1,\n    );\n  }\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'cycle1': cycle1,\n    };\n  }\n\n  Cycle1? cycle1;\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n    return o is Cycle2 && o.cycle1 == cycle1;\n  }\n\n  @override\n  int get hashCode => cycle1.hashCode;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/freezed_cubit.dart",
    "content": "import 'package:freezed_annotation/freezed_annotation.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\n\npart 'freezed_cubit.freezed.dart';\npart 'freezed_cubit.g.dart';\n\nclass FreezedCubit extends HydratedCubit<Tree?> {\n  FreezedCubit() : super(null);\n\n  void setQuestion(Tree tree) => emit(tree);\n\n  @override\n  Map<String, dynamic>? toJson(Tree? state) => state?.toJson();\n\n  @override\n  Tree fromJson(Map<String, dynamic> json) => Tree.fromJson(json);\n}\n\n@freezed\nclass Question with _$Question {\n  const factory Question({\n    int? id,\n    String? question,\n  }) = _Question;\n\n  factory Question.fromJson(Map<String, dynamic> json) =>\n      _$QuestionFromJson(json);\n}\n\n@freezed\nclass Tree with _$Tree {\n  const factory Tree({\n    Question? question,\n    Tree? left,\n    Tree? right,\n  }) = _QTree;\n\n  factory Tree.fromJson(Map<String, dynamic> json) => _$TreeFromJson(json);\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/freezed_cubit.freezed.dart",
    "content": "// coverage:ignore-file\n// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: type=lint\n// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark\n\npart of 'freezed_cubit.dart';\n\n// **************************************************************************\n// FreezedGenerator\n// **************************************************************************\n\nT _$identity<T>(T value) => value;\n\nfinal _privateConstructorUsedError = UnsupportedError(\n    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');\n\nQuestion _$QuestionFromJson(Map<String, dynamic> json) {\n  return _Question.fromJson(json);\n}\n\n/// @nodoc\nmixin _$Question {\n  int? get id => throw _privateConstructorUsedError;\n  String? get question => throw _privateConstructorUsedError;\n\n  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;\n  @JsonKey(ignore: true)\n  $QuestionCopyWith<Question> get copyWith =>\n      throw _privateConstructorUsedError;\n}\n\n/// @nodoc\nabstract class $QuestionCopyWith<$Res> {\n  factory $QuestionCopyWith(Question value, $Res Function(Question) then) =\n      _$QuestionCopyWithImpl<$Res, Question>;\n  @useResult\n  $Res call({int? id, String? question});\n}\n\n/// @nodoc\nclass _$QuestionCopyWithImpl<$Res, $Val extends Question>\n    implements $QuestionCopyWith<$Res> {\n  _$QuestionCopyWithImpl(this._value, this._then);\n\n  // ignore: unused_field\n  final $Val _value;\n  // ignore: unused_field\n  final $Res Function($Val) _then;\n\n  @pragma('vm:prefer-inline')\n  @override\n  $Res call({\n    Object? id = freezed,\n    Object? question = freezed,\n  }) {\n    return _then(_value.copyWith(\n      id: freezed == id\n          ? _value.id\n          : id // ignore: cast_nullable_to_non_nullable\n              as int?,\n      question: freezed == question\n          ? _value.question\n          : question // ignore: cast_nullable_to_non_nullable\n              as String?,\n    ) as $Val);\n  }\n}\n\n/// @nodoc\nabstract class _$$_QuestionCopyWith<$Res> implements $QuestionCopyWith<$Res> {\n  factory _$$_QuestionCopyWith(\n          _$_Question value, $Res Function(_$_Question) then) =\n      __$$_QuestionCopyWithImpl<$Res>;\n  @override\n  @useResult\n  $Res call({int? id, String? question});\n}\n\n/// @nodoc\nclass __$$_QuestionCopyWithImpl<$Res>\n    extends _$QuestionCopyWithImpl<$Res, _$_Question>\n    implements _$$_QuestionCopyWith<$Res> {\n  __$$_QuestionCopyWithImpl(\n      _$_Question _value, $Res Function(_$_Question) _then)\n      : super(_value, _then);\n\n  @pragma('vm:prefer-inline')\n  @override\n  $Res call({\n    Object? id = freezed,\n    Object? question = freezed,\n  }) {\n    return _then(_$_Question(\n      id: freezed == id\n          ? _value.id\n          : id // ignore: cast_nullable_to_non_nullable\n              as int?,\n      question: freezed == question\n          ? _value.question\n          : question // ignore: cast_nullable_to_non_nullable\n              as String?,\n    ));\n  }\n}\n\n/// @nodoc\n@JsonSerializable()\nclass _$_Question implements _Question {\n  const _$_Question({this.id, this.question});\n\n  factory _$_Question.fromJson(Map<String, dynamic> json) =>\n      _$$_QuestionFromJson(json);\n\n  @override\n  final int? id;\n  @override\n  final String? question;\n\n  @override\n  String toString() {\n    return 'Question(id: $id, question: $question)';\n  }\n\n  @override\n  bool operator ==(dynamic other) {\n    return identical(this, other) ||\n        (other.runtimeType == runtimeType &&\n            other is _$_Question &&\n            (identical(other.id, id) || other.id == id) &&\n            (identical(other.question, question) ||\n                other.question == question));\n  }\n\n  @JsonKey(ignore: true)\n  @override\n  int get hashCode => Object.hash(runtimeType, id, question);\n\n  @JsonKey(ignore: true)\n  @override\n  @pragma('vm:prefer-inline')\n  _$$_QuestionCopyWith<_$_Question> get copyWith =>\n      __$$_QuestionCopyWithImpl<_$_Question>(this, _$identity);\n\n  @override\n  Map<String, dynamic> toJson() {\n    return _$$_QuestionToJson(\n      this,\n    );\n  }\n}\n\nabstract class _Question implements Question {\n  const factory _Question({final int? id, final String? question}) =\n      _$_Question;\n\n  factory _Question.fromJson(Map<String, dynamic> json) = _$_Question.fromJson;\n\n  @override\n  int? get id;\n  @override\n  String? get question;\n  @override\n  @JsonKey(ignore: true)\n  _$$_QuestionCopyWith<_$_Question> get copyWith =>\n      throw _privateConstructorUsedError;\n}\n\nTree _$TreeFromJson(Map<String, dynamic> json) {\n  return _QTree.fromJson(json);\n}\n\n/// @nodoc\nmixin _$Tree {\n  Question? get question => throw _privateConstructorUsedError;\n  Tree? get left => throw _privateConstructorUsedError;\n  Tree? get right => throw _privateConstructorUsedError;\n\n  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;\n  @JsonKey(ignore: true)\n  $TreeCopyWith<Tree> get copyWith => throw _privateConstructorUsedError;\n}\n\n/// @nodoc\nabstract class $TreeCopyWith<$Res> {\n  factory $TreeCopyWith(Tree value, $Res Function(Tree) then) =\n      _$TreeCopyWithImpl<$Res, Tree>;\n  @useResult\n  $Res call({Question? question, Tree? left, Tree? right});\n\n  $QuestionCopyWith<$Res>? get question;\n  $TreeCopyWith<$Res>? get left;\n  $TreeCopyWith<$Res>? get right;\n}\n\n/// @nodoc\nclass _$TreeCopyWithImpl<$Res, $Val extends Tree>\n    implements $TreeCopyWith<$Res> {\n  _$TreeCopyWithImpl(this._value, this._then);\n\n  // ignore: unused_field\n  final $Val _value;\n  // ignore: unused_field\n  final $Res Function($Val) _then;\n\n  @pragma('vm:prefer-inline')\n  @override\n  $Res call({\n    Object? question = freezed,\n    Object? left = freezed,\n    Object? right = freezed,\n  }) {\n    return _then(_value.copyWith(\n      question: freezed == question\n          ? _value.question\n          : question // ignore: cast_nullable_to_non_nullable\n              as Question?,\n      left: freezed == left\n          ? _value.left\n          : left // ignore: cast_nullable_to_non_nullable\n              as Tree?,\n      right: freezed == right\n          ? _value.right\n          : right // ignore: cast_nullable_to_non_nullable\n              as Tree?,\n    ) as $Val);\n  }\n\n  @override\n  @pragma('vm:prefer-inline')\n  $QuestionCopyWith<$Res>? get question {\n    if (_value.question == null) {\n      return null;\n    }\n\n    return $QuestionCopyWith<$Res>(_value.question!, (value) {\n      return _then(_value.copyWith(question: value) as $Val);\n    });\n  }\n\n  @override\n  @pragma('vm:prefer-inline')\n  $TreeCopyWith<$Res>? get left {\n    if (_value.left == null) {\n      return null;\n    }\n\n    return $TreeCopyWith<$Res>(_value.left!, (value) {\n      return _then(_value.copyWith(left: value) as $Val);\n    });\n  }\n\n  @override\n  @pragma('vm:prefer-inline')\n  $TreeCopyWith<$Res>? get right {\n    if (_value.right == null) {\n      return null;\n    }\n\n    return $TreeCopyWith<$Res>(_value.right!, (value) {\n      return _then(_value.copyWith(right: value) as $Val);\n    });\n  }\n}\n\n/// @nodoc\nabstract class _$$_QTreeCopyWith<$Res> implements $TreeCopyWith<$Res> {\n  factory _$$_QTreeCopyWith(_$_QTree value, $Res Function(_$_QTree) then) =\n      __$$_QTreeCopyWithImpl<$Res>;\n  @override\n  @useResult\n  $Res call({Question? question, Tree? left, Tree? right});\n\n  @override\n  $QuestionCopyWith<$Res>? get question;\n  @override\n  $TreeCopyWith<$Res>? get left;\n  @override\n  $TreeCopyWith<$Res>? get right;\n}\n\n/// @nodoc\nclass __$$_QTreeCopyWithImpl<$Res> extends _$TreeCopyWithImpl<$Res, _$_QTree>\n    implements _$$_QTreeCopyWith<$Res> {\n  __$$_QTreeCopyWithImpl(_$_QTree _value, $Res Function(_$_QTree) _then)\n      : super(_value, _then);\n\n  @pragma('vm:prefer-inline')\n  @override\n  $Res call({\n    Object? question = freezed,\n    Object? left = freezed,\n    Object? right = freezed,\n  }) {\n    return _then(_$_QTree(\n      question: freezed == question\n          ? _value.question\n          : question // ignore: cast_nullable_to_non_nullable\n              as Question?,\n      left: freezed == left\n          ? _value.left\n          : left // ignore: cast_nullable_to_non_nullable\n              as Tree?,\n      right: freezed == right\n          ? _value.right\n          : right // ignore: cast_nullable_to_non_nullable\n              as Tree?,\n    ));\n  }\n}\n\n/// @nodoc\n@JsonSerializable()\nclass _$_QTree implements _QTree {\n  const _$_QTree({this.question, this.left, this.right});\n\n  factory _$_QTree.fromJson(Map<String, dynamic> json) =>\n      _$$_QTreeFromJson(json);\n\n  @override\n  final Question? question;\n  @override\n  final Tree? left;\n  @override\n  final Tree? right;\n\n  @override\n  String toString() {\n    return 'Tree(question: $question, left: $left, right: $right)';\n  }\n\n  @override\n  bool operator ==(dynamic other) {\n    return identical(this, other) ||\n        (other.runtimeType == runtimeType &&\n            other is _$_QTree &&\n            (identical(other.question, question) ||\n                other.question == question) &&\n            (identical(other.left, left) || other.left == left) &&\n            (identical(other.right, right) || other.right == right));\n  }\n\n  @JsonKey(ignore: true)\n  @override\n  int get hashCode => Object.hash(runtimeType, question, left, right);\n\n  @JsonKey(ignore: true)\n  @override\n  @pragma('vm:prefer-inline')\n  _$$_QTreeCopyWith<_$_QTree> get copyWith =>\n      __$$_QTreeCopyWithImpl<_$_QTree>(this, _$identity);\n\n  @override\n  Map<String, dynamic> toJson() {\n    return _$$_QTreeToJson(\n      this,\n    );\n  }\n}\n\nabstract class _QTree implements Tree {\n  const factory _QTree(\n      {final Question? question,\n      final Tree? left,\n      final Tree? right}) = _$_QTree;\n\n  factory _QTree.fromJson(Map<String, dynamic> json) = _$_QTree.fromJson;\n\n  @override\n  Question? get question;\n  @override\n  Tree? get left;\n  @override\n  Tree? get right;\n  @override\n  @JsonKey(ignore: true)\n  _$$_QTreeCopyWith<_$_QTree> get copyWith =>\n      throw _privateConstructorUsedError;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/freezed_cubit.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n// ignore_for_file: implicit_dynamic_parameter, unnecessary_null_checks\n\npart of 'freezed_cubit.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\n_$_Question _$$_QuestionFromJson(Map<String, dynamic> json) => _$_Question(\n      id: json['id'] as int?,\n      question: json['question'] as String?,\n    );\n\nMap<String, dynamic> _$$_QuestionToJson(_$_Question instance) =>\n    <String, dynamic>{\n      'id': instance.id,\n      'question': instance.question,\n    };\n\n_$_QTree _$$_QTreeFromJson(Map<String, dynamic> json) => _$_QTree(\n      question: json['question'] == null\n          ? null\n          : Question.fromJson(json['question'] as Map<String, dynamic>),\n      left: json['left'] == null\n          ? null\n          : Tree.fromJson(json['left'] as Map<String, dynamic>),\n      right: json['right'] == null\n          ? null\n          : Tree.fromJson(json['right'] as Map<String, dynamic>),\n    );\n\nMap<String, dynamic> _$$_QTreeToJson(_$_QTree instance) => <String, dynamic>{\n      'question': instance.question,\n      'left': instance.left,\n      'right': instance.right,\n    };\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/from_json_state_cubit.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\n\nclass FromJsonStateCubit extends HydratedCubit<int> {\n  FromJsonStateCubit({required void Function(int) onFromJson})\n      : _onFromJson = onFromJson,\n        super(0);\n\n  final void Function(int) _onFromJson;\n\n  void increment() => emit(state + 1);\n\n  @override\n  Map<String, dynamic>? toJson(int state) {\n    return {'state': state};\n  }\n\n  @override\n  int? fromJson(Map<String, dynamic> json) {\n    _onFromJson.call(state);\n    return json['state'] as int?;\n  }\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/json_serializable_cubit.dart",
    "content": "import 'package:collection/collection.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:meta/meta.dart';\n\npart 'json_serializable_cubit.g.dart';\n\nclass JsonSerializableCubit extends HydratedCubit<User> {\n  JsonSerializableCubit() : super(const User.initial());\n\n  void updateFavoriteColor(Color color) =>\n      emit(state.copyWith(favoriteColor: color));\n\n  @override\n  User fromJson(Map<String, dynamic> json) => User.fromJson(json);\n\n  @override\n  Map<String, dynamic> toJson(User state) => state.toJson();\n}\n\n@immutable\n@JsonSerializable(explicitToJson: true)\nclass User {\n  const User(this.name, this.age, this.favoriteColor, this.todos);\n\n  const User.initial()\n      : this(\n          'John Doe',\n          42,\n          Color.green,\n          const <Todo>[Todo('0', 'wash car'), Todo('1', 'dishes')],\n        );\n\n  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);\n  Map<String, dynamic> toJson() => _$UserToJson(this);\n\n  final String name;\n  final int age;\n  final Color favoriteColor;\n  final List<Todo> todos;\n\n  User copyWith({\n    String? name,\n    int? age,\n    Color? favoriteColor,\n    List<Todo>? todos,\n  }) {\n    return User(\n      name ?? this.name,\n      age ?? this.age,\n      favoriteColor ?? this.favoriteColor,\n      todos ?? this.todos,\n    );\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n    final listEquals = const DeepCollectionEquality().equals;\n\n    return o is User &&\n        o.name == name &&\n        o.age == age &&\n        o.favoriteColor == favoriteColor &&\n        listEquals(o.todos, todos);\n  }\n\n  @override\n  int get hashCode {\n    return name.hashCode ^\n        age.hashCode ^\n        favoriteColor.hashCode ^\n        todos.hashCode;\n  }\n\n  @override\n  String toString() {\n    return '''User(name: $name, age: $age, favoriteColor: $favoriteColor, todos: $todos)''';\n  }\n}\n\nenum Color { red, green, blue }\n\n@immutable\n@JsonSerializable(explicitToJson: true)\nclass Todo {\n  const Todo(this.id, this.task);\n  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);\n  Map<String, dynamic> toJson() => _$TodoToJson(this);\n\n  final String id;\n  final String task;\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is Todo && o.id == id && o.task == task;\n  }\n\n  @override\n  int get hashCode => id.hashCode ^ task.hashCode;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/json_serializable_cubit.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n// ignore_for_file: implicit_dynamic_parameter, unnecessary_null_checks\n\npart of 'json_serializable_cubit.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nUser _$UserFromJson(Map<String, dynamic> json) => User(\n      json['name'] as String,\n      json['age'] as int,\n      $enumDecode(_$ColorEnumMap, json['favoriteColor']),\n      (json['todos'] as List<dynamic>)\n          .map((e) => Todo.fromJson(e as Map<String, dynamic>))\n          .toList(),\n    );\n\nMap<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{\n      'name': instance.name,\n      'age': instance.age,\n      'favoriteColor': _$ColorEnumMap[instance.favoriteColor]!,\n      'todos': instance.todos.map((e) => e.toJson()).toList(),\n    };\n\nconst _$ColorEnumMap = {\n  Color.red: 'red',\n  Color.green: 'green',\n  Color.blue: 'blue',\n};\n\nTodo _$TodoFromJson(Map<String, dynamic> json) => Todo(\n      json['id'] as String,\n      json['task'] as String,\n    );\n\nMap<String, dynamic> _$TodoToJson(Todo instance) => <String, dynamic>{\n      'id': instance.id,\n      'task': instance.task,\n    };\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/list_cubit.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:meta/meta.dart';\n\nclass ListCubit extends HydratedCubit<List<String>> {\n  ListCubit() : super(const <String>[]);\n\n  void addItem(String item) => emit(List.of(state)..add(item));\n\n  @override\n  Map<String, dynamic> toJson(List<String> state) {\n    return <String, dynamic>{'state': state};\n  }\n\n  @override\n  List<String> fromJson(Map<String, dynamic> json) {\n    return json['state'] as List<String>;\n  }\n}\n\nclass ListCubitMap<T extends ToJsonMap<E>, E> extends HydratedCubit<List<T>> {\n  ListCubitMap(this._fromJson, {this.explicit = false}) : super(<T>[]);\n  final T Function(Map<String, dynamic> json) _fromJson;\n  final bool explicit;\n\n  void addItem(T item) => emit(List.of(state)..add(item));\n\n  @override\n  Map<String, dynamic> toJson(List<T> state) {\n    final map = <String, dynamic>{\n      'state': explicit\n          ? List<Map<String, E>>.of(\n              state.map((x) => x.toJson()),\n            )\n          : state,\n    };\n    return map;\n  }\n\n  @override\n  List<T> fromJson(Map<String, dynamic> json) {\n    final list = (json['state'] as List)\n        .map((dynamic x) => x as Map<String, dynamic>)\n        .map(_fromJson)\n        .toList();\n    return list;\n  }\n}\n\nclass ListCubitList<T extends ToJsonList<E>, E> extends HydratedCubit<List<T>> {\n  ListCubitList(this._fromJson, {this.explicit = false}) : super(<T>[]);\n  final T Function(List<dynamic> json) _fromJson;\n  final bool explicit;\n\n  void addItem(T item) => emit(List.of(state)..add(item));\n  void reset() => emit(<T>[]);\n\n  @override\n  Map<String, dynamic> toJson(List<T> state) {\n    final map = <String, dynamic>{\n      'state': explicit\n          ? List<List<E>>.of(\n              state.map((x) => x.toJson()),\n            )\n          : state,\n    };\n    return map;\n  }\n\n  @override\n  List<T> fromJson(Map<String, dynamic> json) {\n    final list = (json['state'] as List)\n        .map((dynamic x) => x as List<dynamic>)\n        .map(_fromJson)\n        .toList();\n    return list;\n  }\n}\n\nmixin ToJsonMap<T> {\n  Map<String, T> toJson();\n}\n\n@immutable\nclass MapObject with ToJsonMap<int> {\n  const MapObject(this.value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static MapObject fromJson(Map<String, dynamic> map) {\n    return MapObject(map['value'] as int);\n  }\n\n  final int value;\n\n  @override\n  Map<String, int> toJson() {\n    return <String, int>{'value': value};\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is MapObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\n@immutable\nclass MapCustomObject with ToJsonMap<CustomObject> {\n  MapCustomObject(int value) : value = CustomObject(value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static MapCustomObject fromJson(Map<String, dynamic> map) {\n    return MapCustomObject(\n      CustomObject.fromJson(\n        map['value'] as Map<String, dynamic>,\n      ).value,\n    );\n  }\n\n  final CustomObject value;\n\n  @override\n  Map<String, CustomObject> toJson() {\n    return <String, CustomObject>{'value': value};\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is MapCustomObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\nmixin ToJsonList<T> {\n  List<T> toJson();\n}\n\n@immutable\nclass ListObject with ToJsonList<int> {\n  const ListObject(this.value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static ListObject fromJson(List<dynamic> list) {\n    return ListObject(list[0] as int);\n  }\n\n  final int value;\n\n  @override\n  List<int> toJson() {\n    return <int>[value];\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is ListObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\n@immutable\nclass ListMapObject with ToJsonList<MapObject> {\n  ListMapObject(int value) : value = MapObject(value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static ListMapObject fromJson(List<dynamic> list) {\n    return ListMapObject(\n      MapObject.fromJson(list[0] as Map<String, dynamic>).value,\n    );\n  }\n\n  final MapObject value;\n\n  @override\n  List<MapObject> toJson() {\n    return <MapObject>[value];\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is ListMapObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\n@immutable\nclass ListListObject with ToJsonList<ListObject> {\n  ListListObject(int value) : value = ListObject(value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static ListListObject fromJson(List<dynamic> list) {\n    return ListListObject(\n      ListObject.fromJson(list[0] as List<dynamic>).value,\n    );\n  }\n\n  final ListObject value;\n\n  @override\n  List<ListObject> toJson() {\n    return <ListObject>[value];\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is ListListObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\n@immutable\nclass ListCustomObject with ToJsonList<CustomObject> {\n  ListCustomObject(int value) : value = CustomObject(value);\n\n  // ignore: prefer_constructors_over_static_methods\n  static ListCustomObject fromJson(List<dynamic> list) {\n    return ListCustomObject(\n      CustomObject.fromJson(\n        list[0] as Map<String, dynamic>,\n      ).value,\n    );\n  }\n\n  final CustomObject value;\n\n  @override\n  List<CustomObject> toJson() {\n    return <CustomObject>[value];\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is ListCustomObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n\n@immutable\nclass CustomObject {\n  const CustomObject(this.value);\n\n  factory CustomObject.fromJson(Map<String, dynamic> json) {\n    return CustomObject(json['value'] as int);\n  }\n\n  final int value;\n\n  Map<String, dynamic> toJson() {\n    return <String, int>{'value': value};\n  }\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is CustomObject && o.value == value;\n  }\n\n  @override\n  int get hashCode => value.hashCode;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/manual_cubit.dart",
    "content": "import 'package:collection/collection.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:meta/meta.dart';\n\nclass ManualCubit extends HydratedCubit<Dog?> {\n  ManualCubit() : super(null);\n\n  void setDog(Dog dog) => emit(dog);\n\n  @override\n  Map<String, dynamic>? toJson(Dog? state) => state?.toJson();\n\n  @override\n  Dog fromJson(Map<String, dynamic> json) => Dog.fromJson(json);\n}\n\n@immutable\nclass Dog {\n  const Dog(this.name, this.age, this.toys);\n\n  factory Dog.fromJson(Map<String, dynamic> json) {\n    return Dog(\n      json['name'] as String,\n      json['age'] as int,\n      (json['toys'] as List)\n          .map(\n            (dynamic toy) =>\n                Toy.fromJson(Map<String, dynamic>.from(toy as Map)),\n          )\n          .toList(),\n    );\n  }\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'name': name,\n      'age': age,\n      'toys': toys.map<dynamic>((toy) => toy.toJson()).toList(),\n    };\n  }\n\n  final String name;\n  final int age;\n  final List<Toy> toys;\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n    final listEquals = const DeepCollectionEquality().equals;\n\n    return o is Dog &&\n        o.name == name &&\n        o.age == age &&\n        listEquals(o.toys, toys);\n  }\n\n  @override\n  int get hashCode => name.hashCode ^ age.hashCode ^ toys.hashCode;\n}\n\n@immutable\nclass Toy {\n  const Toy(this.name);\n\n  factory Toy.fromJson(Map<String, dynamic> json) {\n    return Toy(\n      json['name'] as String,\n    );\n  }\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'name': name,\n    };\n  }\n\n  final String name;\n\n  @override\n  bool operator ==(Object o) {\n    if (identical(this, o)) return true;\n\n    return o is Toy && o.name == name;\n  }\n\n  @override\n  int get hashCode => name.hashCode;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/season_palette_cubit.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:meta/meta.dart';\n\n/// A cubit that has a state which uses int key values in its serialized form.\n/// https://github.com/felangel/bloc/issues/3983\nclass SeasonPaletteCubit extends HydratedCubit<SeasonPalette> {\n  SeasonPaletteCubit() : super(const SeasonPalette({}));\n\n  void update(SeasonPalette palette) => emit(palette);\n\n  @override\n  Map<String, dynamic> toJson(SeasonPalette state) => state.toJson();\n\n  @override\n  SeasonPalette fromJson(Map<String, dynamic> json) {\n    return SeasonPalette.fromJson(json);\n  }\n}\n\n@immutable\nclass SeasonPalette {\n  const SeasonPalette(this.colors);\n\n  factory SeasonPalette.fromJson(Map<String, dynamic> json) {\n    final raw = json['colors'] as Map<String, dynamic>? ?? {};\n    return SeasonPalette(\n      raw.map(\n        (key, value) => MapEntry(\n          Season.fromJson(int.parse(key)),\n          value as String,\n        ),\n      ),\n    );\n  }\n\n  final Map<Season, String> colors;\n\n  Map<String, dynamic> toJson() {\n    return <String, dynamic>{\n      'colors': colors.map<int, String>(\n        (key, value) => MapEntry(key.toJson(), value),\n      ),\n    };\n  }\n\n  @override\n  bool operator ==(Object other) {\n    if (identical(this, other)) return true;\n    if (other is! SeasonPalette) return false;\n    if (colors.length != other.colors.length) return false;\n    for (final entry in colors.entries) {\n      if (other.colors[entry.key] != entry.value) return false;\n    }\n    return true;\n  }\n\n  @override\n  int get hashCode => colors.hashCode;\n}\n\n@immutable\nclass Season {\n  const Season._(this.index, this.name);\n\n  static const spring = Season._(0, 'spring');\n  static const summer = Season._(1, 'summer');\n  static const autumn = Season._(2, 'autumn');\n  static const winter = Season._(3, 'winter');\n\n  static const values = [spring, summer, autumn, winter];\n\n  final int index;\n  final String name;\n\n  int toJson() => index;\n\n  static Season fromJson(int value) {\n    return values.firstWhere((e) => e.index == value);\n  }\n\n  @override\n  bool operator ==(Object other) {\n    return identical(this, other) || other is Season && other.index == index;\n  }\n\n  @override\n  int get hashCode => index.hashCode;\n\n  @override\n  String toString() => 'Season.$name';\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/cubits/simple_cubit.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\n\nclass SimpleCubit extends HydratedCubit<int> {\n  SimpleCubit() : super(0);\n\n  void increment() => emit(state + 1);\n\n  @override\n  Map<String, dynamic> toJson(int state) => <String, dynamic>{'state': state};\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['state'] as int;\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/e2e_test.dart",
    "content": "import 'dart:io';\n\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:hydrated_bloc/src/hydrated_bloc.dart';\nimport 'package:path/path.dart' as path;\nimport 'package:test/test.dart';\n\nimport 'cubits/cubits.dart';\n\nFuture<void> sleep() => Future<void>.delayed(const Duration(milliseconds: 100));\n\nvoid main() {\n  group('E2E', () {\n    late Storage storage;\n\n    setUp(() async {\n      storage = await HydratedStorage.build(\n        storageDirectory: HydratedStorageDirectory(\n          path.join(Directory.current.path, '.cache'),\n        ),\n      );\n      HydratedBloc.storage = storage;\n    });\n\n    tearDown(() async {\n      await storage.clear();\n      try {\n        await HydratedStorage.hive.deleteFromDisk();\n        Directory(\n          path.join(Directory.current.path, '.cache'),\n        ).deleteSync(recursive: true);\n      } catch (_) {}\n    });\n\n    test('NIL constructor', () {\n      // ignore: prefer_const_constructors\n      NIL();\n    });\n\n    group('FreezedCubit', () {\n      test('persists and restores state correctly', () async {\n        const tree = Tree(\n          question: Question(id: 0, question: '?00'),\n          left: Tree(\n            question: Question(id: 1, question: '?01'),\n          ),\n          right: Tree(\n            question: Question(id: 2, question: '?02'),\n            left: Tree(question: Question(id: 3, question: '?03')),\n            right: Tree(question: Question(id: 4, question: '?04')),\n          ),\n        );\n        final cubit = FreezedCubit();\n        expect(cubit.state, isNull);\n        cubit.setQuestion(tree);\n        await sleep();\n        expect(FreezedCubit().state, tree);\n      });\n    });\n\n    group('JsonSerializableCubit', () {\n      test('persists and restores state correctly', () async {\n        final cubit = JsonSerializableCubit();\n        final expected = const User.initial().copyWith(\n          favoriteColor: Color.green,\n        );\n        expect(cubit.state, const User.initial());\n        cubit.updateFavoriteColor(Color.green);\n        await sleep();\n        expect(JsonSerializableCubit().state, expected);\n      });\n    });\n\n    group('ListCubit', () {\n      test('persists and restores string list correctly', () async {\n        const item = 'foo';\n        final cubit = ListCubit();\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(ListCubit().state, const <String>[item]);\n      });\n\n      test('persists and restores object->map list correctly', () async {\n        const item = MapObject(1);\n        const fromJson = MapObject.fromJson;\n        final cubit = ListCubitMap<MapObject, int>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitMap<MapObject, int>(fromJson).state,\n          const <MapObject>[item],\n        );\n      });\n\n      test('persists and restores object-*>map list correctly', () async {\n        const item = MapObject(1);\n        const fromJson = MapObject.fromJson;\n        final cubit = ListCubitMap<MapObject, int>(fromJson, explicit: true);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitMap<MapObject, int>(fromJson).state,\n          const <MapObject>[item],\n        );\n      });\n\n      test('persists and restores obj->map<custom> list correctly', () async {\n        final item = MapCustomObject(1);\n        const fromJson = MapCustomObject.fromJson;\n        final cubit = ListCubitMap<MapCustomObject, CustomObject>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitMap<MapCustomObject, CustomObject>(fromJson).state,\n          <MapCustomObject>[item],\n        );\n      });\n\n      test('persists and restores obj-*>map<custom> list correctly', () async {\n        final item = MapCustomObject(1);\n        const fromJson = MapCustomObject.fromJson;\n        final cubit = ListCubitMap<MapCustomObject, CustomObject>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitMap<MapCustomObject, CustomObject>(fromJson).state,\n          <MapCustomObject>[item],\n        );\n      });\n\n      test('persists and restores object->list list correctly', () async {\n        const item = ListObject(1);\n        const fromJson = ListObject.fromJson;\n        final cubit = ListCubitList<ListObject, int>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListObject, int>(fromJson).state,\n          const <ListObject>[item],\n        );\n      });\n\n      test('persists and restores object-*>list list correctly', () async {\n        const item = ListObject(1);\n        const fromJson = ListObject.fromJson;\n        final cubit = ListCubitList<ListObject, int>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListObject, int>(fromJson).state,\n          const <ListObject>[item],\n        );\n      });\n\n      test('persists and restores object->list<map> list correctly', () async {\n        final item = ListMapObject(1);\n        const fromJson = ListMapObject.fromJson;\n        final cubit = ListCubitList<ListMapObject, MapObject>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListMapObject, MapObject>(fromJson).state,\n          <ListMapObject>[item],\n        );\n      });\n\n      test('persists and restores obj-*>list<map> list correctly', () async {\n        final item = ListMapObject(1);\n        const fromJson = ListMapObject.fromJson;\n        final cubit = ListCubitList<ListMapObject, MapObject>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListMapObject, MapObject>(fromJson).state,\n          <ListMapObject>[item],\n        );\n      });\n\n      test('persists and restores obj->list<list> list correctly', () async {\n        final item = ListListObject(1);\n        const fromJson = ListListObject.fromJson;\n        final cubit = ListCubitList<ListListObject, ListObject>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListListObject, ListObject>(fromJson).state,\n          <ListListObject>[item],\n        );\n      });\n\n      test('persists and restores obj-*>list<list> list correctly', () async {\n        final item = ListListObject(1);\n        const fromJson = ListListObject.fromJson;\n        final cubit = ListCubitList<ListListObject, ListObject>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListListObject, ListObject>(fromJson).state,\n          <ListListObject>[item],\n        );\n      });\n\n      test('persists and restores obj->list<custom> list correctly', () async {\n        final item = ListCustomObject(1);\n        const fromJson = ListCustomObject.fromJson;\n        final cubit = ListCubitList<ListCustomObject, CustomObject>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListCustomObject, CustomObject>(fromJson).state,\n          <ListCustomObject>[item],\n        );\n      });\n\n      test('persists and restores obj-*>list<custom> list correctly', () async {\n        final item = ListCustomObject(1);\n        const fromJson = ListCustomObject.fromJson;\n        final cubit = ListCubitList<ListCustomObject, CustomObject>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.addItem(item);\n        await sleep();\n        expect(\n          ListCubitList<ListCustomObject, CustomObject>(fromJson).state,\n          <ListCustomObject>[item],\n        );\n      });\n\n      test('persists and restores obj->list<custom> empty list correctly',\n          () async {\n        const fromJson = ListCustomObject.fromJson;\n        final cubit = ListCubitList<ListCustomObject, CustomObject>(fromJson);\n        expect(cubit.state, isEmpty);\n        cubit.reset();\n        await sleep();\n        expect(\n          ListCubitList<ListCustomObject, CustomObject>(fromJson).state,\n          isEmpty,\n        );\n      });\n\n      test('persists and restores obj-*>list<custom> empty list correctly',\n          () async {\n        const fromJson = ListCustomObject.fromJson;\n        final cubit = ListCubitList<ListCustomObject, CustomObject>(\n          fromJson,\n          explicit: true,\n        );\n        expect(cubit.state, isEmpty);\n        cubit.reset();\n        await sleep();\n        expect(\n          ListCubitList<ListCustomObject, CustomObject>(fromJson).state,\n          isEmpty,\n        );\n      });\n    });\n\n    group('ManualCubit', () {\n      test('persists and restores state correctly', () async {\n        const dog = Dog('Rover', 5, [Toy('Ball')]);\n        final cubit = ManualCubit();\n        expect(cubit.state, isNull);\n        cubit.setDog(dog);\n        await sleep();\n        expect(ManualCubit().state, dog);\n      });\n    });\n\n    group('SimpleCubit', () {\n      test('persists and restores state correctly', () async {\n        final cubit = SimpleCubit();\n        expect(cubit.state, 0);\n        cubit.increment();\n        expect(cubit.state, 1);\n        await sleep();\n        expect(SimpleCubit().state, 1);\n      });\n\n      test('does not throw after clear', () async {\n        final cubit = SimpleCubit();\n        expect(cubit.state, 0);\n        cubit.increment();\n        expect(cubit.state, 1);\n        await storage.clear();\n        expect(SimpleCubit().state, 0);\n      });\n\n      test('can change storage', () async {\n        await HydratedBloc.storage.close();\n        final storageDirectoryA = Directory.systemTemp.createTempSync();\n        final storageA = await HydratedStorage.build(\n          storageDirectory: HydratedStorageDirectory(storageDirectoryA.path),\n        );\n\n        HydratedBloc.storage = storageA;\n        final cubit = SimpleCubit();\n        expect(cubit.state, 0);\n        cubit.increment();\n        expect(cubit.state, 1);\n        await sleep();\n        expect(SimpleCubit().state, 1);\n\n        final storageDirectoryB = Directory.systemTemp.createTempSync();\n        final storageB = await HydratedStorage.build(\n          storageDirectory: HydratedStorageDirectory(storageDirectoryB.path),\n        );\n        HydratedBloc.storage = storageB;\n\n        expect(SimpleCubit().state, 0);\n      });\n    });\n\n    group('CyclicCubit', () {\n      test('throws cyclic error', () async {\n        final cycle2 = Cycle2();\n        final cycle1 = Cycle1(cycle2);\n        cycle2.cycle1 = cycle1;\n        final cubit = CyclicCubit();\n        expect(cubit.state, isNull);\n        expect(\n          () => cubit.setCyclic(cycle1),\n          throwsA(\n            isA<HydratedUnsupportedError>().having(\n              (e) => e.cause,\n              'cycle2 -> cycle1 -> cycle2 ->',\n              isA<HydratedCyclicError>(),\n            ),\n          ),\n        );\n      });\n    });\n\n    group('BadCubit', () {\n      test('throws unsupported object: no `toJson`', () async {\n        final cubit = BadCubit();\n        expect(cubit.state, isNull);\n        expect(\n          cubit.setBad,\n          throwsA(\n            isA<HydratedUnsupportedError>().having(\n              (e) => e.cause,\n              'Object has no `toJson`',\n              isA<NoSuchMethodError>(),\n            ),\n          ),\n        );\n      });\n\n      test('throws unsupported object: bad `toJson`', () async {\n        final cubit = BadCubit();\n        expect(cubit.state, isNull);\n        expect(\n          () => cubit.setBad(VeryBadObject()),\n          throwsA(isA<HydratedUnsupportedError>()),\n        );\n      });\n    });\n\n    group('SeasonPaletteCubit', () {\n      test(\n          'persists and restores state '\n          'when serialized state uses non-string keys', () async {\n        final palette = SeasonPalette({\n          Season.spring: 'green',\n          Season.summer: 'yellow',\n          Season.autumn: 'orange',\n          Season.winter: 'white',\n        });\n        final cubit = SeasonPaletteCubit();\n        expect(cubit.state, const SeasonPalette({}));\n        cubit.update(palette);\n        await sleep();\n        expect(SeasonPaletteCubit().state, palette);\n      });\n    });\n\n    group('FromJsonStateCubit', () {\n      test('does not throw StackOverflow ', () async {\n        final fromJsonCalls = <int>[];\n        var cubit = FromJsonStateCubit(onFromJson: fromJsonCalls.add);\n\n        expect(fromJsonCalls, isEmpty);\n        expect(cubit.state, equals(0));\n\n        cubit.increment();\n        await sleep();\n\n        expect(cubit.state, equals(1));\n        expect(fromJsonCalls, isEmpty);\n\n        fromJsonCalls.clear();\n        cubit = FromJsonStateCubit(onFromJson: fromJsonCalls.add);\n        expect(fromJsonCalls, equals([0]));\n        expect(cubit.state, equals(1));\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hive_interference_test.dart",
    "content": "import 'dart:io';\n\nimport 'package:hive_ce/hive.dart';\nimport 'package:hive_ce/src/hive_impl.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('Hive interference', () {\n    tearDown(() {\n      Hive.close();\n      (Hive as HiveImpl).homePath = null;\n    });\n\n    test('odd singleton interference', () async {\n      final cwd = Directory.current.absolute.path;\n\n      var impl1 = HiveImpl()..init(cwd);\n      var box1 = await impl1.openBox<dynamic>('impl1');\n\n      final impl2 = HiveImpl()..init(cwd);\n      final box2 = await impl2.openBox<dynamic>('impl2');\n\n      await impl1.close();\n\n      expect(box1.isOpen, false);\n      expect(box2.isOpen, true);\n\n      impl1 = HiveImpl()..init(cwd);\n      box1 = await impl1.openBox<dynamic>('impl1');\n\n      Hive.init(cwd);\n      await box1.deleteFromDisk();\n      await box2.close();\n      await Hive.deleteBoxFromDisk('impl2');\n    });\n\n    test('two hive impls beside same directory', () async {\n      final cwd = Directory.current.absolute.path;\n\n      final impl1 = Hive..init(cwd);\n      var box1 = await impl1.openBox<dynamic>('impl1');\n\n      final impl2 = HiveImpl()..init(cwd);\n      var box2 = await impl2.openBox<dynamic>('impl2');\n\n      await box1.put('instance', 'impl1');\n      await box2.put('instance', 'impl2');\n\n      await impl1.close();\n      await impl2.close();\n\n      box1 = await impl1.openBox<dynamic>('impl1');\n      box2 = await impl2.openBox<dynamic>('impl2');\n\n      expect(box1.get('instance'), 'impl1');\n      expect(box2.get('instance'), 'impl2');\n\n      await impl1.deleteFromDisk();\n      expect(box1.isOpen, false);\n      expect(box2.isOpen, true);\n      expect(box2.get('instance'), 'impl2');\n\n      await impl2.deleteFromDisk();\n      expect(box2.isOpen, false);\n    });\n\n    test('two hive impls reside distinct directories', () async {\n      final cwd1 = p.join(Directory.current.absolute.path, 'cwd1');\n      final cwd2 = p.join(Directory.current.absolute.path, 'cwd2');\n\n      final impl1 = Hive..init(cwd1);\n      var box1 = await impl1.openBox<dynamic>('impl1');\n\n      final impl2 = HiveImpl()..init(cwd2);\n      var box2 = await impl2.openBox<dynamic>('impl2');\n\n      await box1.put('instance', 'impl1');\n      await box2.put('instance', 'impl2');\n\n      await impl1.close();\n      await impl2.close();\n\n      box1 = await impl1.openBox<dynamic>('impl1');\n      box2 = await impl2.openBox<dynamic>('impl2');\n\n      expect(box1.get('instance'), 'impl1');\n      expect(box2.get('instance'), 'impl2');\n\n      await impl1.deleteFromDisk();\n      expect(box1.isOpen, false);\n      expect(box2.isOpen, true);\n      expect(box2.get('instance'), 'impl2');\n\n      await impl2.deleteFromDisk();\n      expect(box2.isOpen, false);\n\n      await Directory(cwd1).delete();\n      await Directory(cwd2).delete();\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_aes_cipher_test.dart",
    "content": "import 'dart:convert';\n\nimport 'package:crypto/crypto.dart';\nimport 'package:hive_ce/hive.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('HydratedAesCipher', () {\n    const password = 'hydration';\n    final bytes = sha256.convert(utf8.encode(password)).bytes;\n    test('creates an instance', () {\n      expect(HydratedAesCipher(bytes), isNotNull);\n    });\n\n    test('is a HiveAesCipher', () {\n      expect(HydratedAesCipher(bytes), isA<HiveAesCipher>());\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_bloc_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'dart:async';\n\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\nimport 'package:uuid/uuid.dart';\n\nvoid unawaited(Future<void>? _) {}\n\nclass MockStorage extends Mock implements Storage {}\n\nclass MyUuidHydratedBloc extends HydratedBloc<String, String?> {\n  MyUuidHydratedBloc() : super(const Uuid().v4());\n\n  @override\n  Map<String, String?> toJson(String? state) => {'value': state};\n\n  @override\n  String? fromJson(dynamic json) {\n    try {\n      // ignore: avoid_dynamic_calls\n      return json['value'] as String;\n    } catch (_) {\n      return null;\n    }\n  }\n}\n\nclass MyHydratedBlocWithCustomStorage extends HydratedBloc<int, int> {\n  MyHydratedBlocWithCustomStorage(Storage storage) : super(0, storage: storage);\n\n  @override\n  Map<String, int>? toJson(int state) {\n    return {'value': state};\n  }\n\n  @override\n  int? fromJson(Map<String, dynamic> json) => json['value'] as int?;\n}\n\nabstract class CounterEvent {}\n\nclass Increment extends CounterEvent {}\n\nclass MyCallbackHydratedBloc extends HydratedBloc<CounterEvent, int> {\n  MyCallbackHydratedBloc({void Function(dynamic)? onFromJsonCalled})\n      : _onFromJsonCalled = onFromJsonCalled,\n        super(0) {\n    on<Increment>((event, emit) => emit(state + 1));\n  }\n\n  final void Function(dynamic)? _onFromJsonCalled;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n\n  @override\n  int? fromJson(Map<String, dynamic> json) {\n    _onFromJsonCalled?.call(json);\n    return json['value'] as int?;\n  }\n}\n\nclass MyHydratedBloc extends HydratedBloc<int, int> {\n  MyHydratedBloc([this._id, this._storagePrefix]) : super(0);\n\n  final String? _id;\n  final String? _storagePrefix;\n\n  @override\n  String get id => _id ?? '';\n\n  @override\n  String get storagePrefix => _storagePrefix ?? super.storagePrefix;\n\n  @override\n  Map<String, int>? toJson(int state) {\n    return {'value': state};\n  }\n\n  @override\n  int? fromJson(Map<String, dynamic> json) => json['value'] as int?;\n}\n\nclass MyMultiHydratedBloc extends HydratedBloc<int, int> {\n  MyMultiHydratedBloc(String id)\n      : _id = id,\n        super(0);\n\n  final String _id;\n\n  @override\n  String get id => _id;\n\n  @override\n  Map<String, int> toJson(int state) {\n    return {'value': state};\n  }\n\n  @override\n  // ignore: avoid_dynamic_calls\n  int? fromJson(dynamic json) => json['value'] as int?;\n}\n\nclass MyErrorThrowingBloc extends HydratedBloc<Object, int> {\n  MyErrorThrowingBloc({\n    void Function(Object error, StackTrace stackTrace)? onErrorCallback,\n    bool superOnError = true,\n  })  : _onErrorCallback = onErrorCallback,\n        _superOnError = superOnError,\n        super(0) {\n    on<Object>((event, emit) => emit(state + 1));\n  }\n\n  final void Function(Object error, StackTrace stackTrace)? _onErrorCallback;\n  final bool _superOnError;\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    _onErrorCallback?.call(error, stackTrace);\n    if (_superOnError) super.onError(error, stackTrace);\n  }\n\n  @override\n  Map<String, dynamic> toJson(int state) {\n    return <String, Object>{'key': Object};\n  }\n\n  @override\n  int fromJson(dynamic json) {\n    return 0;\n  }\n}\n\nvoid main() {\n  group('HydratedBloc', () {\n    late Storage storage;\n\n    setUpAll(() {\n      registerFallbackValue(StackTrace.empty);\n      registerFallbackValue(const <String, String?>{});\n    });\n\n    setUp(() {\n      storage = MockStorage();\n      when<dynamic>(() => storage.read(any())).thenReturn(<String, dynamic>{});\n      when(() => storage.write(any(), any<dynamic>())).thenAnswer((_) async {});\n      when(() => storage.delete(any())).thenAnswer((_) async {});\n      when(() => storage.clear()).thenAnswer((_) async {});\n      HydratedBloc.storage = storage;\n    });\n\n    test('storage getter returns correct storage instance', () {\n      final storage = MockStorage();\n      HydratedBloc.storage = storage;\n      expect(HydratedBloc.storage, storage);\n    });\n\n    test('reads from storage once upon initialization', () {\n      MyCallbackHydratedBloc();\n      verify<dynamic>(() => storage.read('MyCallbackHydratedBloc')).called(1);\n    });\n\n    test(\n        'reads from storage once upon initialization w/custom storagePrefix/id',\n        () {\n      const storagePrefix = '__storagePrefix__';\n      const id = '__id__';\n      MyHydratedBloc(id, storagePrefix);\n      verify<dynamic>(() => storage.read('$storagePrefix$id')).called(1);\n    });\n\n    test('writes to storage when onChange is called w/custom storagePrefix/id',\n        () {\n      const change = Change(\n        currentState: 0,\n        nextState: 0,\n      );\n      const expected = <String, int>{'value': 0};\n      const storagePrefix = '__storagePrefix__';\n      const id = '__id__';\n      MyHydratedBloc(id, storagePrefix).onChange(change);\n      verify(() => storage.write('$storagePrefix$id', expected)).called(2);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache value exists', () async {\n      when<dynamic>(() => storage.read(any())).thenReturn({'value': 42});\n      final bloc = MyCallbackHydratedBloc();\n      expect(bloc.state, 42);\n      bloc.add(Increment());\n      await expectLater(bloc.stream, emitsInOrder(const <int>[43]));\n      verify<dynamic>(() => storage.read('MyCallbackHydratedBloc')).called(1);\n    });\n\n    test(\n        'does not deserialize state on subsequent state changes '\n        'when cache value exists', () async {\n      final fromJsonCalls = <dynamic>[];\n      when<dynamic>(() => storage.read(any())).thenReturn({'value': 42});\n      final bloc = MyCallbackHydratedBloc(\n        onFromJsonCalled: fromJsonCalls.add,\n      );\n      expect(bloc.state, 42);\n      bloc.add(Increment());\n      await expectLater(bloc.stream, emitsInOrder(const <int>[43]));\n      expect(fromJsonCalls, [\n        {'value': 42},\n      ]);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache is empty', () async {\n      when<dynamic>(() => storage.read(any())).thenReturn(null);\n      final bloc = MyCallbackHydratedBloc();\n      expect(bloc.state, 0);\n      bloc.add(Increment());\n      await expectLater(bloc.stream, emitsInOrder(const <int>[1]));\n      verify<dynamic>(() => storage.read('MyCallbackHydratedBloc')).called(1);\n    });\n\n    test('does not deserialize state when cache is empty', () async {\n      final fromJsonCalls = <dynamic>[];\n      when<dynamic>(() => storage.read(any())).thenReturn(null);\n      final bloc = MyCallbackHydratedBloc(\n        onFromJsonCalled: fromJsonCalls.add,\n      );\n      expect(bloc.state, 0);\n      bloc.add(Increment());\n      await expectLater(bloc.stream, emitsInOrder(const <int>[1]));\n      expect(fromJsonCalls, isEmpty);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache is malformed', () async {\n      unawaited(\n        runZonedGuarded(() async {\n          when<dynamic>(() => storage.read(any())).thenReturn('{');\n          MyCallbackHydratedBloc().add(Increment());\n        }, (_, __) {\n          verify<dynamic>(() => storage.read('MyCallbackHydratedBloc'))\n              .called(1);\n        }),\n      );\n    });\n\n    test('does not deserialize state when cache is malformed', () async {\n      final fromJsonCalls = <dynamic>[];\n      unawaited(\n        runZonedGuarded(() async {\n          when<dynamic>(() => storage.read(any())).thenReturn('{');\n          MyCallbackHydratedBloc(\n            onFromJsonCalled: fromJsonCalls.add,\n          ).add(Increment());\n          expect(fromJsonCalls, isEmpty);\n        }, (_, __) {\n          expect(fromJsonCalls, isEmpty);\n        }),\n      );\n    });\n\n    group('SingleHydratedBloc', () {\n      test('should call storage.write when onChange is called', () {\n        const change = Change(\n          currentState: 0,\n          nextState: 0,\n        );\n        const expected = <String, int>{'value': 0};\n        MyHydratedBloc().onChange(change);\n        verify(() => storage.write('MyHydratedBloc', expected)).called(2);\n      });\n\n      test('should call storage.write when onChange is called with bloc id',\n          () {\n        final bloc = MyHydratedBloc('A');\n        const change = Change(\n          currentState: 0,\n          nextState: 0,\n        );\n        const expected = <String, int>{'value': 0};\n        bloc.onChange(change);\n        verify(() => storage.write('MyHydratedBlocA', expected)).called(2);\n      });\n\n      test('should call onError when storage.write throws', () {\n        runZonedGuarded(() async {\n          final expectedError = Exception('oops');\n          const change = Change(\n            currentState: 0,\n            nextState: 0,\n          );\n          final bloc = MyHydratedBloc();\n          when(\n            () => storage.write(any(), any<dynamic>()),\n          ).thenThrow(expectedError);\n          bloc.onChange(change);\n          await Future<void>.delayed(const Duration(milliseconds: 300));\n          // ignore: invalid_use_of_protected_member\n          verify(() => bloc.onError(expectedError, any())).called(2);\n        }, (error, _) {\n          expect(error.toString(), 'Exception: oops');\n        });\n      });\n\n      test('stores initial state when instantiated', () {\n        MyHydratedBloc();\n        verify(\n          () => storage.write('MyHydratedBloc', {'value': 0}),\n        ).called(1);\n      });\n\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyHydratedBloc().state, 0);\n        verify<dynamic>(() => storage.read('MyHydratedBloc')).called(1);\n      });\n\n      test('initial state should return 101 when fromJson returns 101', () {\n        when<dynamic>(() => storage.read(any())).thenReturn({'value': 101});\n        expect(MyHydratedBloc().state, 101);\n        verify<dynamic>(() => storage.read('MyHydratedBloc')).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on storage', () async {\n          await MyHydratedBloc().clear();\n          verify(() => storage.delete('MyHydratedBloc')).called(1);\n        });\n      });\n    });\n\n    group('MultiHydratedBloc', () {\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyMultiHydratedBloc('A').state, 0);\n        verify<dynamic>(() => storage.read('MyMultiHydratedBlocA')).called(1);\n\n        expect(MyMultiHydratedBloc('B').state, 0);\n        verify<dynamic>(() => storage.read('MyMultiHydratedBlocB')).called(1);\n      });\n\n      test('initial state should return 101/102 when fromJson returns 101/102',\n          () {\n        when<dynamic>(\n          () => storage.read('MyMultiHydratedBlocA'),\n        ).thenReturn({'value': 101});\n        expect(MyMultiHydratedBloc('A').state, 101);\n        verify<dynamic>(() => storage.read('MyMultiHydratedBlocA')).called(1);\n\n        when<dynamic>(\n          () => storage.read('MyMultiHydratedBlocB'),\n        ).thenReturn({'value': 102});\n        expect(MyMultiHydratedBloc('B').state, 102);\n        verify<dynamic>(() => storage.read('MyMultiHydratedBlocB')).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on storage', () async {\n          await MyMultiHydratedBloc('A').clear();\n          verify(() => storage.delete('MyMultiHydratedBlocA')).called(1);\n          verifyNever(() => storage.delete('MyMultiHydratedBlocB'));\n\n          await MyMultiHydratedBloc('B').clear();\n          verify(() => storage.delete('MyMultiHydratedBlocB')).called(1);\n        });\n      });\n    });\n\n    group('MyUuidHydratedBloc', () {\n      test('stores initial state when instantiated', () async {\n        when(\n          () => storage.write(any<String>(), any<Map<String, String?>>()),\n        ).thenAnswer((_) async {});\n        MyUuidHydratedBloc();\n        await untilCalled(\n          () => storage.write(any<String>(), any<Map<String, String?>>()),\n        );\n        verify(\n          () => storage.write(\n            'MyUuidHydratedBloc',\n            any<Map<String, String?>>(),\n          ),\n        ).called(1);\n      });\n\n      test('correctly caches computed initial state', () async {\n        dynamic cachedState;\n        when<dynamic>(() => storage.read(any())).thenReturn(cachedState);\n        when(\n          () => storage.write(any(), any<dynamic>()),\n        ).thenAnswer((_) => Future<void>.value());\n        MyUuidHydratedBloc();\n        final captured = verify(\n          () => storage.write('MyUuidHydratedBloc', captureAny<dynamic>()),\n        ).captured;\n        cachedState = captured.first;\n        when<dynamic>(() => storage.read(any())).thenReturn(cachedState);\n        MyUuidHydratedBloc();\n        final secondCaptured = verify(\n          () => storage.write('MyUuidHydratedBloc', captureAny<dynamic>()),\n        ).captured;\n        final dynamic initialStateB = secondCaptured.first;\n\n        expect(initialStateB, cachedState);\n      });\n    });\n\n    group('MyErrorThrowingBloc', () {\n      test('continues to emit new states when serialization fails', () async {\n        await runZonedGuarded(\n          () async {\n            final bloc = MyErrorThrowingBloc();\n            final expectedStates = [0, 1, emitsDone];\n            unawaited(expectLater(bloc.stream, emitsInOrder(expectedStates)));\n            bloc.add(Object);\n            await bloc.close();\n          },\n          (_, __) {},\n        );\n      });\n\n      test('calls onError when json decode fails', () async {\n        Object? lastError;\n        StackTrace? lastStackTrace;\n        await runZonedGuarded(() async {\n          when<dynamic>(() => storage.read(any())).thenReturn('invalid json');\n          MyErrorThrowingBloc(\n            onErrorCallback: (error, stackTrace) {\n              lastError = error;\n              lastStackTrace = stackTrace;\n            },\n          );\n        }, (_, __) {\n          expect(lastStackTrace, isNotNull);\n          expect(\n            lastError.toString().startsWith(\n              '''Unhandled error type 'String' is not a subtype of type 'Map<dynamic, dynamic>?' in type cast''',\n            ),\n            isTrue,\n          );\n        });\n      });\n\n      test('returns super.state when json decode fails', () async {\n        MyErrorThrowingBloc? bloc;\n        await runZonedGuarded(() async {\n          when<dynamic>(() => storage.read(any())).thenReturn('invalid json');\n          bloc = MyErrorThrowingBloc(superOnError: false);\n        }, (_, __) {\n          expect(bloc?.state, 0);\n        });\n      });\n\n      test('calls onError when storage.write fails', () async {\n        Object? lastError;\n        StackTrace? lastStackTrace;\n        final exception = Exception('oops');\n        await runZonedGuarded(() async {\n          when(() => storage.write(any(), any<dynamic>())).thenThrow(exception);\n          MyErrorThrowingBloc(\n            onErrorCallback: (error, stackTrace) {\n              lastError = error;\n              lastStackTrace = stackTrace;\n            },\n          );\n        }, (error, _) {\n          expect(lastError, isA<HydratedUnsupportedError>());\n          expect(lastStackTrace, isNotNull);\n          expect(\n            error.toString(),\n            '''Converting object to an encodable object failed: Object''',\n          );\n        });\n      });\n\n      test('calls onError when json encode fails', () async {\n        await runZonedGuarded(\n          () async {\n            Object? lastError;\n            StackTrace? lastStackTrace;\n            final bloc = MyErrorThrowingBloc(\n              onErrorCallback: (error, stackTrace) {\n                lastError = error;\n                lastStackTrace = stackTrace;\n              },\n            )..add(Object);\n            await bloc.close();\n            expect(\n              '$lastError',\n              'Converting object to an encodable object failed: Object',\n            );\n            expect(lastStackTrace, isNotNull);\n          },\n          (_, __) {},\n        );\n      });\n    });\n\n    group('MyHydratedBlocWithCustomStorage', () {\n      setUp(() {\n        HydratedBloc.storage = null;\n      });\n\n      test('should call storage.write when onChange is called', () {\n        const expected = <String, int>{'value': 0};\n        const change = Change(currentState: 0, nextState: 0);\n        MyHydratedBlocWithCustomStorage(storage).onChange(change);\n        verify(\n          () => storage.write('MyHydratedBlocWithCustomStorage', expected),\n        ).called(2);\n      });\n\n      test('should call onError when storage.write throws', () {\n        runZonedGuarded(() async {\n          final expectedError = Exception('oops');\n          const change = Change(currentState: 0, nextState: 0);\n          final bloc = MyHydratedBlocWithCustomStorage(storage);\n          when(\n            () => storage.write(any(), any<dynamic>()),\n          ).thenThrow(expectedError);\n          bloc.onChange(change);\n          await Future<void>.delayed(const Duration(milliseconds: 300));\n          // ignore: invalid_use_of_protected_member\n          verify(() => bloc.onError(expectedError, any())).called(2);\n        }, (error, stackTrace) {\n          expect(error.toString(), 'Exception: oops');\n          expect(stackTrace, isNotNull);\n        });\n      });\n\n      test('stores initial state when instantiated', () {\n        MyHydratedBlocWithCustomStorage(storage);\n        verify(\n          () => storage.write('MyHydratedBlocWithCustomStorage', {'value': 0}),\n        ).called(1);\n      });\n\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyHydratedBlocWithCustomStorage(storage).state, 0);\n        verify<dynamic>(\n          () => storage.read('MyHydratedBlocWithCustomStorage'),\n        ).called(1);\n      });\n\n      test('initial state should return 101 when fromJson returns 101', () {\n        when<dynamic>(() => storage.read(any())).thenReturn({'value': 101});\n        expect(MyHydratedBlocWithCustomStorage(storage).state, 101);\n        verify<dynamic>(\n          () => storage.read('MyHydratedBlocWithCustomStorage'),\n        ).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on custom storage', () async {\n          await MyHydratedBlocWithCustomStorage(storage).clear();\n          verify(\n            () => storage.delete('MyHydratedBlocWithCustomStorage'),\n          ).called(1);\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_cubit_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'dart:async';\n\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\nimport 'package:uuid/uuid.dart';\n\nclass MockStorage extends Mock implements Storage {}\n\nclass MyUuidHydratedCubit extends HydratedCubit<String> {\n  MyUuidHydratedCubit() : super(const Uuid().v4());\n\n  @override\n  Map<String, String> toJson(String state) => {'value': state};\n\n  @override\n  String? fromJson(Map<String, dynamic> json) => json['value'] as String?;\n}\n\nclass MyCallbackHydratedCubit extends HydratedCubit<int> {\n  MyCallbackHydratedCubit({void Function(dynamic)? onFromJsonCalled})\n      : _onFromJsonCalled = onFromJsonCalled,\n        super(0);\n\n  final void Function(dynamic)? _onFromJsonCalled;\n\n  void increment() => emit(state + 1);\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n\n  @override\n  int? fromJson(dynamic json) {\n    _onFromJsonCalled?.call(json);\n    // ignore: avoid_dynamic_calls\n    return json['value'] as int?;\n  }\n\n  @override\n  // ignore: must_call_super\n  void onError(Object error, StackTrace stackTrace) {}\n}\n\nclass MyHydratedCubit extends HydratedCubit<int> {\n  MyHydratedCubit([\n    this._id,\n    // ignore: avoid_positional_boolean_parameters\n    this._callSuper = true,\n    this._storagePrefix,\n  ]) : super(0);\n\n  final String? _id;\n  final bool _callSuper;\n  final String? _storagePrefix;\n\n  @override\n  String get id => _id ?? '';\n\n  @override\n  String get storagePrefix => _storagePrefix ?? super.storagePrefix;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n\n  @override\n  // ignore: avoid_dynamic_calls\n  int? fromJson(dynamic json) => json['value'] as int?;\n\n  @override\n  void onError(Object error, StackTrace stackTrace) {\n    if (_callSuper) super.onError(error, stackTrace);\n  }\n}\n\nclass MyMultiHydratedCubit extends HydratedCubit<int> {\n  MyMultiHydratedCubit(String id)\n      : _id = id,\n        super(0);\n\n  final String _id;\n\n  @override\n  String get id => _id;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n\n  @override\n  // ignore: avoid_dynamic_calls\n  int? fromJson(dynamic json) => json['value'] as int?;\n}\n\nclass MyIntKeyMapCubit extends HydratedCubit<Map<int, String>> {\n  MyIntKeyMapCubit() : super(const {});\n\n  @override\n  Map<String, dynamic> toJson(Map<int, String> state) {\n    return {'data': state};\n  }\n\n  @override\n  Map<int, String> fromJson(Map<String, dynamic> json) {\n    final raw = json['data'] as Map<String, dynamic>? ?? {};\n    return raw.map((key, value) => MapEntry(int.parse(key), value as String));\n  }\n}\n\nclass MyHydratedCubitWithCustomStorage extends HydratedCubit<int> {\n  MyHydratedCubitWithCustomStorage(Storage storage)\n      : super(0, storage: storage);\n\n  @override\n  Map<String, int>? toJson(int state) {\n    return {'value': state};\n  }\n\n  @override\n  int? fromJson(Map<String, dynamic> json) => json['value'] as int?;\n}\n\nvoid main() {\n  group('HydratedCubit', () {\n    late Storage storage;\n\n    setUp(() {\n      storage = MockStorage();\n      when(() => storage.write(any(), any<dynamic>())).thenAnswer((_) async {});\n      when<dynamic>(() => storage.read(any())).thenReturn(<String, dynamic>{});\n      when(() => storage.delete(any())).thenAnswer((_) async {});\n      when(() => storage.clear()).thenAnswer((_) async {});\n      HydratedBloc.storage = storage;\n    });\n\n    test('reads from storage once upon initialization', () {\n      MyCallbackHydratedCubit();\n      verify<dynamic>(() => storage.read('MyCallbackHydratedCubit')).called(1);\n    });\n\n    test(\n        'reads from storage once upon initialization w/custom storagePrefix/id',\n        () {\n      const storagePrefix = '__storagePrefix__';\n      const id = '__id__';\n      MyHydratedCubit(id, true, storagePrefix);\n      verify<dynamic>(() => storage.read('$storagePrefix$id')).called(1);\n    });\n\n    test('writes to storage when onChange is called w/custom storagePrefix/id',\n        () {\n      const change = Change(\n        currentState: 0,\n        nextState: 0,\n      );\n      const expected = <String, int>{'value': 0};\n      const storagePrefix = '__storagePrefix__';\n      const id = '__id__';\n      MyHydratedCubit(id, true, storagePrefix).onChange(change);\n      verify(() => storage.write('$storagePrefix$id', expected)).called(2);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache value exists', () {\n      when<dynamic>(() => storage.read(any())).thenReturn({'value': 42});\n      final cubit = MyCallbackHydratedCubit();\n      expect(cubit.state, 42);\n      cubit.increment();\n      expect(cubit.state, 43);\n      verify<dynamic>(() => storage.read('MyCallbackHydratedCubit')).called(1);\n    });\n\n    test(\n        'does not deserialize state on subsequent state changes '\n        'when cache value exists', () {\n      final fromJsonCalls = <dynamic>[];\n      when<dynamic>(() => storage.read(any())).thenReturn({'value': 42});\n      final cubit = MyCallbackHydratedCubit(\n        onFromJsonCalled: fromJsonCalls.add,\n      );\n      expect(cubit.state, 42);\n      cubit.increment();\n      expect(cubit.state, 43);\n      expect(fromJsonCalls, [\n        {'value': 42},\n      ]);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache is empty', () {\n      when<dynamic>(() => storage.read(any())).thenReturn(null);\n      final cubit = MyCallbackHydratedCubit();\n      expect(cubit.state, 0);\n      cubit.increment();\n      expect(cubit.state, 1);\n      verify<dynamic>(() => storage.read('MyCallbackHydratedCubit')).called(1);\n    });\n\n    test('does not deserialize state when cache is empty', () {\n      final fromJsonCalls = <dynamic>[];\n      when<dynamic>(() => storage.read(any())).thenReturn(null);\n      final cubit = MyCallbackHydratedCubit(\n        onFromJsonCalled: fromJsonCalls.add,\n      );\n      expect(cubit.state, 0);\n      cubit.increment();\n      expect(cubit.state, 1);\n      expect(fromJsonCalls, isEmpty);\n    });\n\n    test(\n        'does not read from storage on subsequent state changes '\n        'when cache is malformed', () {\n      when<dynamic>(() => storage.read(any())).thenReturn('{');\n      final cubit = MyCallbackHydratedCubit();\n      expect(cubit.state, 0);\n      cubit.increment();\n      expect(cubit.state, 1);\n      verify<dynamic>(() => storage.read('MyCallbackHydratedCubit')).called(1);\n    });\n\n    test('does not deserialize state when cache is malformed', () {\n      final fromJsonCalls = <dynamic>[];\n      runZonedGuarded(\n        () {\n          when<dynamic>(() => storage.read(any())).thenReturn('{');\n          MyCallbackHydratedCubit(onFromJsonCalled: fromJsonCalls.add);\n        },\n        (_, __) {\n          expect(fromJsonCalls, isEmpty);\n        },\n      );\n    });\n\n    group('SingleHydratedCubit', () {\n      test('should throw StorageNotFound when storage is null', () {\n        HydratedBloc.storage = null;\n        expect(\n          () => MyHydratedCubit(),\n          throwsA(isA<StorageNotFound>()),\n        );\n      });\n\n      test('StorageNotFound overrides toString', () {\n        expect(\n          // ignore: prefer_const_constructors\n          StorageNotFound().toString(),\n          'Storage was accessed before it was initialized.\\n'\n          'Please ensure that storage has been initialized.\\n'\n          '\\n'\n          'For example:\\n\\n'\n          'HydratedBloc.storage = await HydratedStorage.build();',\n        );\n      });\n\n      test('storage getter returns correct storage instance', () {\n        final storage = MockStorage();\n        HydratedBloc.storage = storage;\n        expect(HydratedBloc.storage, storage);\n      });\n\n      test('should call storage.write when onChange is called', () {\n        const transition = Change<int>(\n          currentState: 0,\n          nextState: 0,\n        );\n        final expected = <String, int>{'value': 0};\n        MyHydratedCubit().onChange(transition);\n        verify(() => storage.write('MyHydratedCubit', expected)).called(2);\n      });\n\n      test('should call storage.write when onChange is called with cubit id',\n          () {\n        final cubit = MyHydratedCubit('A');\n        const transition = Change<int>(\n          currentState: 0,\n          nextState: 0,\n        );\n        final expected = <String, int>{'value': 0};\n        cubit.onChange(transition);\n        verify(() => storage.write('MyHydratedCubitA', expected)).called(2);\n      });\n\n      test('should throw BlocUnhandledErrorException when storage.write throws',\n          () {\n        runZonedGuarded(\n          () async {\n            final expectedError = Exception('oops');\n            const transition = Change<int>(\n              currentState: 0,\n              nextState: 0,\n            );\n            when(\n              () => storage.write(any(), any<dynamic>()),\n            ).thenThrow(expectedError);\n            MyHydratedCubit().onChange(transition);\n            await Future<void>.delayed(const Duration(seconds: 300));\n            fail('should throw');\n          },\n          (error, _) {\n            expect(error.toString(), 'Exception: oops');\n          },\n        );\n      });\n\n      test('stores initial state when instantiated', () {\n        MyHydratedCubit();\n        verify(\n          () => storage.write('MyHydratedCubit', {'value': 0}),\n        ).called(1);\n      });\n\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyHydratedCubit().state, 0);\n        verify<dynamic>(() => storage.read('MyHydratedCubit')).called(1);\n      });\n\n      test('initial state should return 0 when deserialization fails', () {\n        when<dynamic>(() => storage.read(any())).thenThrow(Exception('oops'));\n        expect(MyHydratedCubit('', false).state, 0);\n      });\n\n      test('initial state should return 101 when fromJson returns 101', () {\n        when<dynamic>(() => storage.read(any())).thenReturn({'value': 101});\n        expect(MyHydratedCubit().state, 101);\n        verify<dynamic>(() => storage.read('MyHydratedCubit')).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on storage', () async {\n          await MyHydratedCubit().clear();\n          verify(() => storage.delete('MyHydratedCubit')).called(1);\n        });\n      });\n    });\n\n    group('MultiHydratedCubit', () {\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyMultiHydratedCubit('A').state, 0);\n        verify<dynamic>(\n          () => storage.read('MyMultiHydratedCubitA'),\n        ).called(1);\n\n        expect(MyMultiHydratedCubit('B').state, 0);\n        verify<dynamic>(\n          () => storage.read('MyMultiHydratedCubitB'),\n        ).called(1);\n      });\n\n      test('initial state should return 101/102 when fromJson returns 101/102',\n          () {\n        when<dynamic>(\n          () => storage.read('MyMultiHydratedCubitA'),\n        ).thenReturn({'value': 101});\n        expect(MyMultiHydratedCubit('A').state, 101);\n        verify<dynamic>(\n          () => storage.read('MyMultiHydratedCubitA'),\n        ).called(1);\n\n        when<dynamic>(\n          () => storage.read('MyMultiHydratedCubitB'),\n        ).thenReturn({'value': 102});\n        expect(MyMultiHydratedCubit('B').state, 102);\n        verify<dynamic>(\n          () => storage.read('MyMultiHydratedCubitB'),\n        ).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on storage', () async {\n          await MyMultiHydratedCubit('A').clear();\n          verify(() => storage.delete('MyMultiHydratedCubitA')).called(1);\n          verifyNever(() => storage.delete('MyMultiHydratedCubitB'));\n\n          await MyMultiHydratedCubit('B').clear();\n          verify(() => storage.delete('MyMultiHydratedCubitB')).called(1);\n        });\n      });\n    });\n\n    group('MyUuidHydratedCubit', () {\n      test('stores initial state when instantiated', () {\n        MyUuidHydratedCubit();\n        verify(\n          () => storage.write('MyUuidHydratedCubit', any<dynamic>()),\n        ).called(1);\n      });\n\n      test('correctly caches computed initial state', () {\n        dynamic cachedState;\n        when<dynamic>(() => storage.read(any())).thenReturn(cachedState);\n        when(\n          () => storage.write(any(), any<dynamic>()),\n        ).thenAnswer((_) => Future<void>.value());\n        MyUuidHydratedCubit();\n        final captured = verify(\n          () => storage.write('MyUuidHydratedCubit', captureAny<dynamic>()),\n        ).captured;\n        cachedState = captured.first;\n        when<dynamic>(() => storage.read(any())).thenReturn(cachedState);\n        MyUuidHydratedCubit();\n        final secondCaptured = verify(\n          () => storage.write('MyUuidHydratedCubit', captureAny<dynamic>()),\n        ).captured;\n        final dynamic initialStateB = secondCaptured.first;\n\n        expect(initialStateB, cachedState);\n      });\n    });\n\n    group('MyIntKeyMapCubit', () {\n      test('serializes non-string keys', () {\n        final cubit = MyIntKeyMapCubit();\n        const data = {0: 'a', 1: 'b', 2: 'c'};\n        const change = Change(currentState: <int, String>{}, nextState: data);\n        cubit.onChange(change);\n        verify(\n          () => storage.write('MyIntKeyMapCubit', {\n            'data': {'0': 'a', '1': 'b', '2': 'c'},\n          }),\n        ).called(1);\n      });\n\n      test('restores state when cache has stringified int keys', () {\n        when<dynamic>(() => storage.read(any())).thenReturn({\n          'data': {'0': 'a', '1': 'b'},\n        });\n        final cubit = MyIntKeyMapCubit();\n        expect(cubit.state, {0: 'a', 1: 'b'});\n      });\n    });\n\n    group('MyHydratedCubitWithCustomStorage', () {\n      setUp(() {\n        HydratedBloc.storage = null;\n      });\n\n      test('should call storage.write when onChange is called', () {\n        const expected = <String, int>{'value': 0};\n        const change = Change(currentState: 0, nextState: 0);\n        MyHydratedCubitWithCustomStorage(storage).onChange(change);\n        verify(\n          () => storage.write('MyHydratedCubitWithCustomStorage', expected),\n        ).called(2);\n      });\n\n      test('should call onError when storage.write throws', () {\n        runZonedGuarded(() async {\n          final expectedError = Exception('oops');\n          const change = Change(currentState: 0, nextState: 0);\n          final cubit = MyHydratedCubitWithCustomStorage(storage);\n          when(\n            () => storage.write(any(), any<dynamic>()),\n          ).thenThrow(expectedError);\n          cubit.onChange(change);\n          await Future<void>.delayed(const Duration(milliseconds: 300));\n          // ignore: invalid_use_of_protected_member\n          verify(() => cubit.onError(expectedError, any())).called(2);\n        }, (error, stackTrace) {\n          expect(error.toString(), 'Exception: oops');\n          expect(stackTrace, isNotNull);\n        });\n      });\n\n      test('stores initial state when instantiated', () {\n        MyHydratedCubitWithCustomStorage(storage);\n        verify(\n          () => storage.write('MyHydratedCubitWithCustomStorage', {'value': 0}),\n        ).called(1);\n      });\n\n      test('initial state should return 0 when fromJson returns null', () {\n        when<dynamic>(() => storage.read(any())).thenReturn(null);\n        expect(MyHydratedCubitWithCustomStorage(storage).state, 0);\n        verify<dynamic>(\n          () => storage.read('MyHydratedCubitWithCustomStorage'),\n        ).called(1);\n      });\n\n      test('initial state should return 101 when fromJson returns 101', () {\n        when<dynamic>(() => storage.read(any())).thenReturn({'value': 101});\n        expect(MyHydratedCubitWithCustomStorage(storage).state, 101);\n        verify<dynamic>(\n          () => storage.read('MyHydratedCubitWithCustomStorage'),\n        ).called(1);\n      });\n\n      group('clear', () {\n        test('calls delete on custom storage', () async {\n          await MyHydratedCubitWithCustomStorage(storage).clear();\n          verify(\n            () => storage.delete('MyHydratedCubitWithCustomStorage'),\n          ).called(1);\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_cyclic_error_test.dart",
    "content": "import 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:test/test.dart';\n\nvoid main() {\n  group('HydratedCyclicError', () {\n    test('toString override is correct', () {\n      expect(\n        HydratedCyclicError(<String, dynamic>{}).toString(),\n        'Cyclic error while state traversing',\n      );\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_mixin_test.dart",
    "content": "// ignore_for_file: prefer_file_naming_conventions\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:test/test.dart';\n\nclass MockStorage extends Mock implements Storage {}\n\nclass MyCubit extends Cubit<int> with HydratedMixin<int> {\n  MyCubit() : super(0);\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, dynamic> toJson(int state) => {'value': state};\n}\n\nvoid main() {\n  group('HydratedMixin', () {\n    late Storage storage;\n\n    setUp(() {\n      storage = MockStorage();\n    });\n\n    group('hydrate', () {\n      test('restores state from cache', () {\n        final cubit = MyCubit();\n        when(() => storage.read('MyCubit')).thenReturn({'value': 42});\n        cubit.hydrate(storage: storage);\n        expect(cubit.state, 42);\n      });\n\n      test('uses initial state on error by default', () {\n        when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n        final cubit = MyCubit()..hydrate(storage: storage);\n        expect(cubit.state, 0);\n        verify(() => storage.write('MyCubit', {'value': 0})).called(1);\n      });\n\n      group('onError', () {\n        test('is called when an exception occurs during hydration', () {\n          final cubit = MyCubit();\n          var onErrorCallCount = 0;\n\n          when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n\n          cubit.hydrate(\n            storage: storage,\n            onError: (_, __) {\n              onErrorCallCount++;\n              return HydrationErrorBehavior.overwrite;\n            },\n          );\n          expect(onErrorCallCount, equals(1));\n        });\n\n        group('when error behavior is HydrationErrorBehavior.overwrite', () {\n          test('storage.write is always called', () {\n            final cubit = MyCubit();\n            when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n            cubit.hydrate(\n              storage: storage,\n              onError: (_, __) => HydrationErrorBehavior.overwrite,\n            );\n            expect(cubit.state, 0);\n            verify(() => storage.write('MyCubit', {'value': 0})).called(1);\n          });\n\n          test('states emitted in onError are persisted', () {\n            final cubit = MyCubit();\n            when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n\n            cubit.hydrate(\n              storage: storage,\n              onError: (_, __) {\n                cubit.emit(-1);\n                return HydrationErrorBehavior.overwrite;\n              },\n            );\n            expect(cubit.state, -1);\n            verify(() => storage.write('MyCubit', {'value': -1})).called(1);\n          });\n        });\n\n        group('when error behavior is HydrationErrorBehavior.retain', () {\n          test('storage.write is never called', () {\n            final cubit = MyCubit();\n            when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n            cubit.hydrate(\n              storage: storage,\n              onError: (_, __) => HydrationErrorBehavior.retain,\n            );\n            expect(cubit.state, 0);\n            verifyNever(() => storage.write(any(), any<dynamic>()));\n          });\n\n          test('states emitted in onError are not persisted', () {\n            final cubit = MyCubit();\n            when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n\n            cubit.hydrate(\n              storage: storage,\n              onError: (_, __) {\n                cubit.emit(-1);\n                return HydrationErrorBehavior.retain;\n              },\n            );\n\n            expect(cubit.state, -1);\n            verifyNever(() => storage.write(any(), any<dynamic>()));\n          });\n\n          test('state changes are not persisted until hydration succeeds', () {\n            when(() => storage.read('MyCubit')).thenReturn({'value': '42'});\n            var cubit = MyCubit();\n\n            cubit\n              ..hydrate(\n                storage: storage,\n                onError: (_, __) {\n                  cubit.emit(-1);\n                  return HydrationErrorBehavior.retain;\n                },\n              )\n              ..emit(10);\n\n            expect(cubit.state, 10);\n            verifyNever(() => storage.write(any(), any<dynamic>()));\n\n            when(() => storage.read('MyCubit')).thenReturn({'value': 42});\n            cubit = MyCubit()\n              ..hydrate(\n                storage: storage,\n                onError: (_, __) => HydrationErrorBehavior.retain,\n              );\n\n            expect(cubit.state, 42);\n            verify(() => storage.write('MyCubit', {'value': 42})).called(1);\n          });\n        });\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/hydrated_bloc/test/hydrated_storage_test.dart",
    "content": "import 'dart:async';\nimport 'dart:convert';\nimport 'dart:io';\n\nimport 'package:hive_ce/hive.dart';\nimport 'package:hive_ce/src/hive_impl.dart';\nimport 'package:hydrated_bloc/hydrated_bloc.dart';\nimport 'package:mocktail/mocktail.dart';\nimport 'package:path/path.dart' as p;\nimport 'package:test/test.dart';\n\nclass MockBox extends Mock implements Box<dynamic> {}\n\nvoid main() {\n  group('HydratedStorage', () {\n    final cwd = Directory.current.absolute.path;\n    final storageDirectory = HydratedStorageDirectory(cwd);\n\n    late Storage storage;\n\n    tearDown(() async {\n      await storage.clear();\n      try {\n        await HydratedStorage.hive.deleteFromDisk();\n      } catch (_) {}\n    });\n\n    group('migration', () {\n      test('returns correct value when file exists', () async {\n        File('${storageDirectory.path}/.hydrated_bloc.json').writeAsStringSync(\n          json.encode({\n            'CounterBloc': json.encode({'value': 4}),\n          }),\n        );\n        storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        // ignore: avoid_dynamic_calls\n        expect(storage.read('CounterBloc')['value'] as int, 4);\n      });\n    });\n\n    group('build', () {\n      setUp(() async {\n        final storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        await storage.clear();\n        await storage.close();\n      });\n\n      test('returns new instance when called multiple times', () async {\n        final instanceA = storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        final instanceB = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        expect(instanceA, isNot(instanceB));\n      });\n\n      test('creates new instance if storage was closed', () async {\n        final instanceA = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        await instanceA.close();\n        final instanceB = storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        expect(instanceA, isNot(instanceB));\n      });\n\n      test(\n          'does not call Hive.init '\n          'when storageDirectory is webStorageDirectory', () async {\n        final completer = Completer<void>();\n        await runZonedGuarded(\n          () {\n            HydratedStorage.build(\n              storageDirectory: HydratedStorageDirectory.web,\n            ).whenComplete(completer.complete);\n            return completer.future;\n          },\n          (Object _, StackTrace __) {},\n        );\n        expect(HiveImpl().homePath, isNull);\n        storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n      });\n\n      test('creates internal HiveImpl with correct directory', () async {\n        storage = await HydratedStorage.build(\n          storageDirectory: storageDirectory,\n        );\n        final box = HydratedStorage.hive.box<dynamic>('hydrated_box');\n        expect(box, isNotNull);\n        expect(box.path, p.join(storageDirectory.path, 'hydrated_box.hive'));\n      });\n    });\n\n    group('default constructor', () {\n      const key = '__key__';\n      const value = '__value__';\n      late Box<dynamic> box;\n\n      setUp(() {\n        box = MockBox();\n        when(() => box.clear()).thenAnswer((_) async => 0);\n        when(() => box.close()).thenAnswer((_) async {});\n        storage = HydratedStorage(box);\n      });\n\n      group('read', () {\n        test('returns null when box is not open', () {\n          when(() => box.isOpen).thenReturn(false);\n          expect(storage.read(key), isNull);\n        });\n\n        test('returns correct value when box is open', () {\n          when(() => box.isOpen).thenReturn(true);\n          when<dynamic>(() => box.get(any<dynamic>())).thenReturn(value);\n          expect(storage.read(key), value);\n          verify<dynamic>(() => box.get(key)).called(1);\n        });\n      });\n\n      group('write', () {\n        test('does nothing when box is not open', () async {\n          when(() => box.isOpen).thenReturn(false);\n          await storage.write(key, value);\n          verifyNever(() => box.put(any<dynamic>(), any<dynamic>()));\n        });\n\n        test('puts key/value in box when box is open', () async {\n          when(() => box.isOpen).thenReturn(true);\n          when(\n            () => box.put(any<dynamic>(), any<dynamic>()),\n          ).thenAnswer((_) => Future<void>.value());\n          await storage.write(key, value);\n          verify(() => box.put(key, value)).called(1);\n        });\n      });\n\n      group('delete', () {\n        test('does nothing when box is not open', () async {\n          when(() => box.isOpen).thenReturn(false);\n          await storage.delete(key);\n          verifyNever(() => box.delete(any<dynamic>()));\n        });\n\n        test('puts key/value in box when box is open', () async {\n          when(() => box.isOpen).thenReturn(true);\n          when(\n            () => box.delete(any<dynamic>()),\n          ).thenAnswer((_) => Future<void>.value());\n          await storage.delete(key);\n          verify(() => box.delete(key)).called(1);\n        });\n      });\n\n      group('clear', () {\n        test('does nothing when box is not open', () async {\n          when(() => box.isOpen).thenReturn(false);\n          await storage.clear();\n          verifyNever(() => box.clear());\n        });\n\n        test('clears box when box is open', () async {\n          when(() => box.isOpen).thenReturn(true);\n          await storage.clear();\n          verify(() => box.clear()).called(1);\n        });\n      });\n    });\n\n    group('During heavy load', () {\n      test('writes key/value pairs correctly', () async {\n        const token = 'token';\n        storage = await HydratedStorage.build(\n          storageDirectory: HydratedStorageDirectory(cwd),\n        );\n        await Stream.fromIterable(\n          Iterable.generate(120, (i) => i),\n        ).asyncMap((i) async {\n          final record = Iterable.generate(\n            i,\n            (i) => Iterable.generate(i, (j) => 'Point($i,$j);').toList(),\n          ).toList();\n\n          // ignore: unawaited_futures\n          storage.write(token, record);\n\n          storage = await HydratedStorage.build(\n            storageDirectory: HydratedStorageDirectory(cwd),\n          );\n\n          final written = storage.read(token);\n          expect(written, isNotNull);\n          expect(written, record);\n        }).drain<dynamic>();\n      });\n    });\n\n    group('Storage interference', () {\n      final temp = p.join(cwd, 'temp');\n      final docs = p.join(cwd, 'docs');\n\n      tearDown(() async {\n        await storage.clear();\n        await storage.close();\n        await Hive.close();\n        await Directory(temp).delete(recursive: true);\n        await Directory(docs).delete(recursive: true);\n      });\n\n      test('Hive and Hydrated default directories', () async {\n        Hive.init(docs);\n        final tempDir = Directory(temp)..createSync();\n        storage = await HydratedStorage.build(\n          storageDirectory: HydratedStorageDirectory(tempDir.path),\n        );\n\n        var box = await Hive.openBox<String>('hive');\n        await box.put('name', 'hive');\n        expect(box.get('name'), 'hive');\n        await Hive.close();\n\n        // https://github.com/hivedb/hive/pull/521#issuecomment-767903897\n        (Hive as HiveImpl).homePath = null;\n\n        Hive.init(docs);\n        box = await Hive.openBox<String>('hive');\n        expect(box.get('name'), isNotNull);\n        expect(box.get('name'), 'hive');\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/replay_bloc/.gitignore",
    "content": "# Don’t commit the following directories created by pub.\n.dart_tool\n.packages\npubspec.lock\n\n# Don’t commit the following directories generated by package:test\ncoverage/\n.test_coverage.dart\n*.lcov\n\n.DS_Store\n\n# Exceptions to the above rules\n!coverage_badge.svg"
  },
  {
    "path": "packages/replay_bloc/CHANGELOG.md",
    "content": "# 0.3.0\n\n- refactor: upgrade to `bloc ^9.0.0` ([#4137](https://github.com/felangel/bloc/pull/4137))\n- chore: add `funding` to `pubspec.yaml` ([#4200](https://github.com/felangel/bloc/pull/4200))\n- chore: update sponsors\n\n# 0.2.7\n\n- refactor: use additional lint rules ([#4083](https://github.com/felangel/bloc/pull/4083))\n- chore: update copyright year\n- chore: update sponsors\n\n# 0.2.6\n\n- chore: update sponsors ([#4054](https://github.com/felangel/bloc/pull/4054))\n\n# 0.2.5\n\n- docs: fix inline doc comment macro ([#4007](https://github.com/felangel/bloc/pull/4007))\n- chore: add `topics` to `pubspec.yaml` ([#3914](https://github.com/felangel/bloc/pull/3914))\n\n# 0.2.4\n\n- docs: upgrade to Dart 3 ([#3825](https://github.com/felangel/bloc/pull/3825))\n- refactor: standardize analysis_options ([#3825](https://github.com/felangel/bloc/pull/3825))\n- chore: update sdk constraints and fix analysis warnings ([#3825](https://github.com/felangel/bloc/pull/3825))\n\n# 0.2.3\n\n- chore: add screenshots to pubspec.yaml ([#3728](https://github.com/felangel/bloc/pull/3728))\n- chore: update example to Dart 2.19 ([#3727](https://github.com/felangel/bloc/pull/3727))\n- chore: upgrade to `bloc ^8.1.1` ([#3726](https://github.com/felangel/bloc/pull/3726))\n- refactor: upgrade to Dart 2.19 ([#3699](https://github.com/felangel/bloc/pull/3699))\n  - remove deprecated `invariant_booleans` lint rule\n\n# 0.2.2\n\n- chore: upgrade to `bloc v8.1.0`\n\n# 0.2.1\n\n- chore: upgrade to `bloc v8.0.1`\n\n# 0.2.0\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0`\n\n# 0.2.0-dev.3\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.5`\n\n# 0.2.0-dev.2\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.3`\n\n# 0.2.0-dev.1\n\n- **BREAKING**: feat: upgrade to `bloc v8.0.0-dev.2`\n\n# 0.1.0\n\n- feat: upgrade to `bloc ^7.2.0`\n\n# 0.0.2\n\n- feat: add support for conditional replays via `shouldReplay`\n\n# 0.0.1\n\n- **BREAKING**: opt into null safety\n  - feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n  - feat!: upgrade to `bloc ^7.0.0`\n  - chore: upgrade example to support sound null safety\n\n# 0.0.1-nullsafety.3\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.4`\n\n# 0.0.1-nullsafety.2\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.3`\n\n# 0.0.1-nullsafety.1\n\n- chore: upgrade to `bloc ^7.0.0-nullsafety.2`\n\n# 0.0.1-nullsafety.0\n\n- **BREAKING**: opt into null safety\n  - feat!: upgrade Dart SDK constraints to `>=2.12.0-0 <3.0.0`\n- **BREAKING**: refactor: upgrade to `bloc ^7.0.0-nullsafety.1`\n- chore: upgrade example to support sound null safety\n- docs: minor updates to README\n\n# 0.0.1-dev.4\n\n- feat: export `package:bloc/bloc.dart`\n- deps: update to `bloc: ^6.1.0`\n- deps: require `dart >=2.6.0`\n\n# 0.0.1-dev.3\n\n- docs: add inline documentation to mixins\n\n# 0.0.1-dev.2\n\n- **BREAKING**: `ReplayMixin` renamed to `ReplayCubitMixin`\n- feat: `ReplayBloc` and `ReplayBlocMixin` support\n\n# 0.0.1-dev.1\n\n- feat: `ReplayCubit` and `ReplayMixin` dev release\n"
  },
  {
    "path": "packages/replay_bloc/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Felix Angelov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/replay_bloc/README.md",
    "content": "<p align=\"center\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/logos/replay_bloc.png\" height=\"100\" alt=\"ReplayBloc\"></p>\n\n<p align=\"center\">\n  <a href=\"https://pub.dev/packages/replay_bloc\"><img src=\"https://img.shields.io/pub/v/replay_bloc.svg\" alt=\"Pub\"></a>\n  <a href=\"https://github.com/felangel/bloc/actions\"><img src=\"https://github.com/felangel/bloc/actions/workflows/main.yaml/badge.svg\" alt=\"build\"></a>\n  <a href=\"https://codecov.io/gh/felangel/bloc\"><img src=\"https://codecov.io/gh/felangel/Bloc/branch/master/graph/badge.svg\" alt=\"codecov\"></a>\n  <a href=\"https://github.com/felangel/bloc\"><img src=\"https://img.shields.io/github/stars/felangel/bloc.svg?style=flat&logo=github&colorB=deeppink&label=stars\" alt=\"Star on Github\"></a>\n  <a href=\"https://pub.dev/packages/bloc_lint\"><img src=\"https://img.shields.io/badge/style-bloc_lint-20FFE4.svg\" alt=\"style: bloc lint\"></a>\n  <a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx\"><img src=\"https://img.shields.io/badge/flutter-website-deepskyblue.svg\" alt=\"Flutter Website\"></a>\n  <a href=\"https://github.com/Solido/awesome-flutter#standard\"><img src=\"https://img.shields.io/badge/awesome-flutter-blue.svg?longCache=true\" alt=\"Awesome Flutter\"></a>\n  <a href=\"https://fluttersamples.com\"><img src=\"https://img.shields.io/badge/flutter-samples-teal.svg?longCache=true\" alt=\"Flutter Samples\"></a>\n  <a href=\"https://opensource.org/licenses/MIT\"><img src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"></a>\n  <a href=\"https://discord.gg/bloc\"><img src=\"https://img.shields.io/discord/649708778631200778.svg?logo=discord&color=blue\" alt=\"Discord\"></a>\n  <a href=\"https://github.com/felangel/bloc\"><img src=\"https://tinyurl.com/bloc-library\" alt=\"Bloc Library\"></a>\n</p>\n\nAn extension to [package:bloc](https://github.com/felangel/bloc) which adds automatic undo and redo support to bloc and cubit states. Built to work with [package:bloc](https://pub.dev/packages/bloc).\n\n**Learn more at [bloclibrary.dev](https://bloclibrary.dev)!**\n\n---\n\n## Sponsors\n\nOur top sponsors are shown below! [[Become a Sponsor](https://github.com/sponsors/felangel)]\n\n<table style=\"background-color: white; border: 1px solid black\">\n    <tbody>\n        <tr>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://shorebird.dev\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/shorebird.png\" width=\"225\"/></a>\n            </td>            \n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://getstream.io/chat/flutter/tutorial/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_FlutterChat&utm_term=bloc\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/stream.png\" width=\"225\"/></a>\n            </td>\n            <td align=\"center\" style=\"border: 1px solid black\">\n                <a href=\"https://rettelgame.com/\"><img src=\"https://raw.githubusercontent.com/felangel/bloc/master/assets/sponsors/rettel.png\" width=\"225\"/></a>\n            </td>\n        </tr>\n    </tbody>\n</table>\n\n---\n\n## Creating a ReplayCubit\n\n```dart\nclass CounterCubit extends ReplayCubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n}\n```\n\n## Using a ReplayCubit\n\n```dart\nvoid main() {\n  final cubit = CounterCubit();\n\n  // trigger a state change\n  cubit.increment();\n  print(cubit.state); // 1\n\n  // undo the change\n  cubit.undo();\n  print(cubit.state); // 0\n\n  // redo the change\n  cubit.redo();\n  print(cubit.state); // 1\n}\n```\n\n## ReplayCubitMixin\n\nIf you wish to be able to use a `ReplayCubit` in conjuction with a different type of cubit like `HydratedCubit`, you can use the `ReplayCubitMixin`.\n\n```dart\nclass CounterCubit extends HydratedCubit<int> with ReplayCubitMixin {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n}\n```\n\n## Creating a ReplayBloc\n\n```dart\nclass CounterEvent extends ReplayEvent {}\n\nclass CounterIncrementPressed extends CounterEvent {}\n\nclass CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends ReplayBloc<CounterEvent, int> {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n}\n```\n\n## Using a ReplayBloc\n\n```dart\nvoid main() {\n  // trigger a state change\n  final bloc = CounterBloc()..add(CounterIncrementPressed());\n\n  // wait for state to update\n  await bloc.stream.first;\n  print(bloc.state); // 1\n\n  // undo the change\n  bloc.undo();\n  print(bloc.state); // 0\n\n  // redo the change\n  bloc.redo();\n  print(bloc.state); // 1\n}\n```\n\n## ReplayBlocMixin\n\nIf you wish to be able to use a `ReplayBloc` in conjuction with a different type of cubit like `HydratedBloc`, you can use the `ReplayBlocMixin`.\n\n```dart\nsealed class CounterEvent with ReplayEvent {}\n\nfinal class CounterIncrementPressed extends CounterEvent {}\n\nfinal class CounterDecrementPressed extends CounterEvent {}\n\nclass CounterBloc extends HydratedBloc<CounterEvent, int> with ReplayBlocMixin {\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n  }\n\n  @override\n  int fromJson(Map<String, dynamic> json) => json['value'] as int;\n\n  @override\n  Map<String, int> toJson(int state) => {'value': state};\n}\n```\n\n## Dart Versions\n\n- Dart 2: >= 2.14\n\n## Maintainers\n\n- [Felix Angelov](https://github.com/felangel)\n"
  },
  {
    "path": "packages/replay_bloc/analysis_options.yaml",
    "content": "include:\n  - package:bloc_lint/recommended.yaml\n  - ../../analysis_options.yaml\n"
  },
  {
    "path": "packages/replay_bloc/example/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Web related\nlib/generated_plugin_registrant.dart\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Exceptions to above rules.\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": "packages/replay_bloc/example/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: \"17025dd88227cd9532c33fa78f5250d548d87e9a\"\n  channel: \"stable\"\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n    - platform: web\n      create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n      base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "packages/replay_bloc/example/README.md",
    "content": "# Replay Bloc Example\n\nA sample project that showcases how to use package:replay_bloc.\n"
  },
  {
    "path": "packages/replay_bloc/example/analysis_options.yaml",
    "content": "include: ../../../analysis_options.yaml\n"
  },
  {
    "path": "packages/replay_bloc/example/lib/main.dart",
    "content": "// ignore_for_file: avoid_print\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:replay_bloc/replay_bloc.dart';\n\nvoid main() {\n  Bloc.observer = const AppBlocObserver();\n  runApp(const App());\n}\n\n/// {@template app_bloc_observer}\n/// Custom [BlocObserver] that observes all bloc and cubit state changes.\n/// {@endtemplate}\nclass AppBlocObserver extends BlocObserver {\n  /// {@macro app_bloc_observer}\n  const AppBlocObserver();\n\n  @override\n  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {\n    super.onChange(bloc, change);\n    if (bloc is Cubit) print(change);\n  }\n\n  @override\n  void onTransition(\n    Bloc<dynamic, dynamic> bloc,\n    Transition<dynamic, dynamic> transition,\n  ) {\n    super.onTransition(bloc, transition);\n    print(transition);\n  }\n}\n\n/// {@template app}\n/// A [StatelessWidget] that:\n/// * uses [replay_bloc](https://pub.dev/packages/replay_bloc)\n/// and [flutter_bloc](https://pub.dev/packages/flutter_bloc)\n/// to manage the state of a counter.\n/// {@endtemplate}\nclass App extends StatelessWidget {\n  /// {@macro app}\n  const App({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => CounterBloc(),\n      child: const MaterialApp(\n        home: CounterPage(),\n      ),\n    );\n  }\n}\n\n/// {@template counter_page}\n/// A [StatelessWidget] that:\n/// * demonstrates how to consume and interact with a [ReplayBloc]/[ReplayCubit].\n/// {@endtemplate}\nclass CounterPage extends StatelessWidget {\n  /// {@macro counter_page}\n  const CounterPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('Counter'),\n        actions: [\n          BlocBuilder<CounterBloc, int>(\n            builder: (context, state) {\n              final bloc = context.read<CounterBloc>();\n              return IconButton(\n                icon: const Icon(Icons.undo),\n                onPressed: bloc.canUndo ? bloc.undo : null,\n              );\n            },\n          ),\n          BlocBuilder<CounterBloc, int>(\n            builder: (context, state) {\n              final bloc = context.read<CounterBloc>();\n              return IconButton(\n                icon: const Icon(Icons.redo),\n                onPressed: bloc.canRedo ? bloc.redo : null,\n              );\n            },\n          ),\n        ],\n      ),\n      body: Center(\n        child: BlocBuilder<CounterBloc, int>(\n          builder: (context, state) {\n            return Text('$state', style: textTheme.displayMedium);\n          },\n        ),\n      ),\n      floatingActionButton: Column(\n        crossAxisAlignment: CrossAxisAlignment.end,\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: <Widget>[\n          FloatingActionButton(\n            child: const Icon(Icons.add),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterIncrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.remove),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterDecrementPressed());\n            },\n          ),\n          const SizedBox(height: 4),\n          FloatingActionButton(\n            child: const Icon(Icons.delete_forever),\n            onPressed: () {\n              context.read<CounterBloc>().add(CounterResetPressed());\n            },\n          ),\n        ],\n      ),\n    );\n  }\n}\n\n/// Base event class for the [CounterBloc].\nsealed class CounterEvent extends ReplayEvent {}\n\n/// Notifies [CounterBloc] to increment its state.\nfinal class CounterIncrementPressed extends CounterEvent {\n  @override\n  String toString() => 'CounterIncrementPressed';\n}\n\n/// Notifies [CounterBloc] to decrement its state.\nfinal class CounterDecrementPressed extends CounterEvent {\n  @override\n  String toString() => 'CounterDecrementPressed';\n}\n\n/// Notifies [CounterBloc] to reset its state.\nfinal class CounterResetPressed extends CounterEvent {\n  @override\n  String toString() => 'CounterResetPressed';\n}\n\n/// {@template replay_counter_bloc}\n/// A simple [ReplayBloc] which manages an `int` as its state\n/// and reacts to three events:\n/// * [CounterIncrementPressed]\n/// * [CounterDecrementPressed]\n/// * [CounterResetPressed]\n/// {@endtemplate}\nclass CounterBloc extends ReplayBloc<CounterEvent, int> {\n  /// {@macro replay_counter_bloc}\n  CounterBloc() : super(0) {\n    on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n    on<CounterDecrementPressed>((event, emit) => emit(state - 1));\n    on<CounterResetPressed>((event, emit) => emit(0));\n  }\n}\n"
  },
  {
    "path": "packages/replay_bloc/example/pubspec.yaml",
    "content": "name: example\ndescription: A sample project that showcases how to use package:replay_bloc.\nversion: 1.0.0+1\npublish_to: none\n\nenvironment:\n  sdk: \">=3.0.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n  flutter:\n    sdk: flutter\n  flutter_bloc: ^9.1.0\n  replay_bloc: ^0.3.0\n\nflutter:\n  uses-material-design: true\n"
  },
  {
    "path": "packages/replay_bloc/example/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../../bloc\n  flutter_bloc:\n    path: ../../flutter_bloc\n  replay_bloc:\n    path: ../\n"
  },
  {
    "path": "packages/replay_bloc/example/web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"example\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>example</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n</head>\n<body>\n  <script src=\"flutter_bootstrap.js\" async></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/replay_bloc/example/web/manifest.json",
    "content": "{\n    \"name\": \"example\",\n    \"short_name\": \"example\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "packages/replay_bloc/lib/replay_bloc.dart",
    "content": "/// An extension to the [bloc state management library](https://pub.dev/packages/bloc)\n/// which adds support for undo and redo.\n///\n/// Get started at [bloclibrary.dev](https://bloclibrary.dev) 🚀\nlibrary replay_bloc;\n\nexport 'package:bloc/bloc.dart';\nexport 'src/replay_cubit.dart';\n"
  },
  {
    "path": "packages/replay_bloc/lib/src/change_stack.dart",
    "content": "part of 'replay_cubit.dart';\n\ntypedef _Predicate<T> = bool Function(T);\n\nclass _ChangeStack<T> {\n  _ChangeStack({required _Predicate<T> shouldReplay, this.limit})\n      : _shouldReplay = shouldReplay;\n\n  final Queue<_Change<T>> _history = ListQueue();\n  final Queue<_Change<T>> _redos = ListQueue();\n  final _Predicate<T> _shouldReplay;\n\n  int? limit;\n\n  bool get canRedo => _redos.any((c) => _shouldReplay(c._newValue));\n  bool get canUndo => _history.any((c) => _shouldReplay(c._oldValue));\n\n  void add(_Change<T> change) {\n    if (limit != null && limit == 0) return;\n\n    _history.addLast(change);\n    _redos.clear();\n\n    if (limit != null && _history.length > limit!) {\n      if (limit! > 0) _history.removeFirst();\n    }\n  }\n\n  void clear() {\n    _history.clear();\n    _redos.clear();\n  }\n\n  void redo() {\n    if (canRedo) {\n      final change = _redos.removeFirst();\n      _history.addLast(change);\n      return _shouldReplay(change._newValue) ? change.execute() : redo();\n    }\n  }\n\n  void undo() {\n    if (canUndo) {\n      final change = _history.removeLast();\n      _redos.addFirst(change);\n      return _shouldReplay(change._oldValue) ? change.undo() : undo();\n    }\n  }\n}\n\nclass _Change<T> {\n  _Change(\n    this._oldValue,\n    this._newValue,\n    this._execute,\n    this._undo,\n  );\n\n  final T _oldValue;\n  final T _newValue;\n  final void Function() _execute;\n  final void Function(T oldValue) _undo;\n\n  void execute() => _execute();\n  void undo() => _undo(_oldValue);\n}\n"
  },
  {
    "path": "packages/replay_bloc/lib/src/replay_bloc.dart",
    "content": "part of 'replay_cubit.dart';\n\n/// {@template replay_event}\n/// Base event class for all [ReplayBloc] events.\n/// {@endtemplate}\nabstract class ReplayEvent {\n  /// {@macro replay_event}\n  const ReplayEvent();\n}\n\n/// Notifies a [ReplayBloc] of a Redo.\nclass _Redo extends ReplayEvent {\n  @override\n  String toString() => 'Redo';\n}\n\n/// Notifies a [ReplayBloc] of an Undo.\nclass _Undo extends ReplayEvent {\n  @override\n  String toString() => 'Undo';\n}\n\n/// {@template replay_bloc}\n/// A specialized [Bloc] which supports `undo` and `redo` operations.\n///\n/// [ReplayBloc] accepts an optional `limit` which determines\n/// the max size of the history.\n///\n/// A custom [ReplayBloc] can be created by extending [ReplayBloc].\n///\n/// ```dart\n/// abstract class CounterEvent {}\n/// class CounterIncrementPressed extends CounterEvent {}\n///\n/// class CounterBloc extends ReplayBloc<CounterEvent, int> {\n///   CounterBloc() : super(0) {\n///     on<CounterIncrementPressed>((event, emit) => emit(state + 1));\n///   }\n/// }\n/// ```\n///\n/// Then the built-in `undo` and `redo` operations can be used.\n///\n/// ```dart\n/// final bloc = CounterBloc();\n///\n/// bloc.add(CounterIncrementPressed());\n///\n/// bloc.undo();\n///\n/// bloc.redo();\n/// ```\n///\n/// The undo/redo history can be destroyed at any time by calling `clear`.\n///\n/// See also:\n///\n/// * [Bloc] for information about the [ReplayBloc] superclass.\n///\n/// {@endtemplate}\nabstract class ReplayBloc<Event extends ReplayEvent, State>\n    extends Bloc<Event, State> with ReplayBlocMixin<Event, State> {\n  /// {@macro replay_bloc}\n  ReplayBloc(State state, {int? limit}) : super(state) {\n    if (limit != null) {\n      this.limit = limit;\n    }\n  }\n}\n\n/// A mixin which enables `undo` and `redo` operations\n/// for [Bloc] classes.\nmixin ReplayBlocMixin<Event extends ReplayEvent, State> on Bloc<Event, State> {\n  late final _changeStack = _ChangeStack<State>(shouldReplay: shouldReplay);\n\n  BlocObserver get _observer => Bloc.observer;\n\n  /// Sets the internal `undo`/`redo` size limit.\n  /// By default there is no limit.\n  set limit(int limit) => _changeStack.limit = limit;\n\n  @override\n  // ignore: must_call_super\n  void onTransition(covariant Transition<ReplayEvent, State> transition) {\n    // ignore: invalid_use_of_protected_member\n    _observer.onTransition(this, transition);\n  }\n\n  @override\n  // ignore: must_call_super\n  void onEvent(covariant ReplayEvent event) {\n    // ignore: invalid_use_of_protected_member\n    _observer.onEvent(this, event);\n  }\n\n  @override\n  void emit(State state) {\n    _changeStack.add(\n      _Change<State>(\n        this.state,\n        state,\n        () {\n          final event = _Redo();\n          onEvent(event);\n          onTransition(\n            Transition(\n              currentState: this.state,\n              event: event,\n              nextState: state,\n            ),\n          );\n          // ignore: invalid_use_of_visible_for_testing_member\n          super.emit(state);\n        },\n        (val) {\n          final event = _Undo();\n          onEvent(event);\n          onTransition(\n            Transition(\n              currentState: this.state,\n              event: event,\n              nextState: val,\n            ),\n          );\n          // ignore: invalid_use_of_visible_for_testing_member\n          super.emit(val);\n        },\n      ),\n    );\n    // ignore: invalid_use_of_visible_for_testing_member\n    super.emit(state);\n  }\n\n  /// Undo the last change.\n  void undo() => _changeStack.undo();\n\n  /// Redo the previous change.\n  void redo() => _changeStack.redo();\n\n  /// Checks whether the undo/redo stack is empty.\n  bool get canUndo => _changeStack.canUndo;\n\n  /// Checks wether the undo/redo stack is at the current change.\n  bool get canRedo => _changeStack.canRedo;\n\n  /// Clear undo/redo history.\n  void clearHistory() => _changeStack.clear();\n\n  /// Checks whether the given state should be replayed from the undo/redo stack.\n  ///\n  /// This is called at the time the state is being restored.\n  /// By default [shouldReplay] always returns `true`.\n  bool shouldReplay(State state) => true;\n}\n"
  },
  {
    "path": "packages/replay_bloc/lib/src/replay_cubit.dart",
    "content": "import 'dart:collection';\n\nimport 'package:bloc/bloc.dart';\n\npart 'change_stack.dart';\npart 'replay_bloc.dart';\n\n/// {@template replay_cubit}\n/// A specialized [Cubit] which supports `undo` and `redo` operations.\n///\n/// [ReplayCubit] accepts an optional `limit` which determines\n/// the max size of the history.\n///\n/// A custom [ReplayCubit] can be created by extending [ReplayCubit].\n///\n/// ```dart\n/// class CounterCubit extends ReplayCubit<int> {\n///   CounterCubit() : super(0);\n///\n///   void increment() => emit(state + 1);\n/// }\n/// ```\n///\n/// Then the built-in `undo` and `redo` operations can be used.\n///\n/// ```dart\n/// final cubit = CounterCubit();\n///\n/// cubit.increment();\n/// print(cubit.state); // 1\n///\n/// cubit.undo();\n/// print(cubit.state); // 0\n///\n/// cubit.redo();\n/// print(cubit.state); // 1\n/// ```\n///\n/// The undo/redo history can be destroyed at any time by calling `clear`.\n///\n/// See also:\n///\n/// * [Cubit] for information about the [ReplayCubit] superclass.\n///\n/// {@endtemplate}\nabstract class ReplayCubit<State> extends Cubit<State>\n    with ReplayCubitMixin<State> {\n  /// {@macro replay_cubit}\n  ReplayCubit(State state, {int? limit}) : super(state) {\n    if (limit != null) {\n      this.limit = limit;\n    }\n  }\n}\n\n/// A mixin which enables `undo` and `redo` operations\n/// for [Cubit] classes.\nmixin ReplayCubitMixin<State> on Cubit<State> {\n  late final _changeStack = _ChangeStack<State>(shouldReplay: shouldReplay);\n\n  /// Sets the internal `undo`/`redo` size limit.\n  /// By default there is no limit.\n  set limit(int limit) => _changeStack.limit = limit;\n\n  @override\n  void emit(State state) {\n    _changeStack.add(\n      _Change<State>(\n        this.state,\n        state,\n        () => super.emit(state),\n        (val) => super.emit(val),\n      ),\n    );\n    super.emit(state);\n  }\n\n  /// Undo the last change.\n  void undo() => _changeStack.undo();\n\n  /// Redo the previous change.\n  void redo() => _changeStack.redo();\n\n  /// Checks whether the undo/redo stack is empty.\n  bool get canUndo => _changeStack.canUndo;\n\n  /// Checks whether the undo/redo stack is at the current change.\n  bool get canRedo => _changeStack.canRedo;\n\n  /// Clear undo/redo history.\n  void clearHistory() => _changeStack.clear();\n\n  /// Checks whether the given state should be replayed from the undo/redo stack.\n  ///\n  /// This is called at the time the state is being restored.\n  /// By default [shouldReplay] always returns `true`.\n  bool shouldReplay(State state) => true;\n}\n"
  },
  {
    "path": "packages/replay_bloc/pubspec.yaml",
    "content": "name: replay_bloc\ndescription: An extension to the bloc state management library which adds support for undo and redo.\nversion: 0.3.0\nrepository: https://github.com/felangel/bloc/tree/master/packages/replay_bloc\nissue_tracker: https://github.com/felangel/bloc/issues\nhomepage: https://bloclibrary.dev\ntopics: [bloc, state-management, replay, undo, redo]\nfunding: [https://github.com/sponsors/felangel]\n\nenvironment:\n  sdk: \">=2.14.0 <4.0.0\"\n\ndependencies:\n  bloc: ^9.0.0\n\ndev_dependencies:\n  bloc_lint: ^0.3.2\n  test: ^1.16.0\n\nscreenshots:\n  - description: The replay bloc package logo.\n    path: screenshots/logo.png\n"
  },
  {
    "path": "packages/replay_bloc/pubspec_overrides.yaml",
    "content": "dependency_overrides:\n  bloc:\n    path: ../bloc\n"
  },
  {
    "path": "packages/replay_bloc/test/blocs/counter_bloc.dart",
    "content": "import 'package:replay_bloc/replay_bloc.dart';\n\nabstract class CounterEvent extends ReplayEvent {}\n\nclass Increment extends CounterEvent {}\n\nclass Decrement extends CounterEvent {}\n\nclass CounterBloc extends ReplayBloc<CounterEvent, int> {\n  CounterBloc({\n    int? limit,\n    void Function(ReplayEvent)? onEventCallback,\n    void Function(Transition<ReplayEvent, int>)? onTransitionCallback,\n    bool Function(int)? shouldReplayCallback,\n  })  : _onEventCallback = onEventCallback,\n        _onTransitionCallback = onTransitionCallback,\n        _shouldReplayCallback = shouldReplayCallback,\n        super(0, limit: limit) {\n    on<Increment>((event, emit) => emit(state + 1));\n    on<Decrement>((event, emit) => emit(state - 1));\n  }\n\n  final void Function(ReplayEvent)? _onEventCallback;\n  final void Function(Transition<ReplayEvent, int>)? _onTransitionCallback;\n  final bool Function(int)? _shouldReplayCallback;\n\n  @override\n  void onEvent(ReplayEvent event) {\n    _onEventCallback?.call(event);\n    super.onEvent(event);\n  }\n\n  @override\n  void onTransition(Transition<ReplayEvent, int> transition) {\n    _onTransitionCallback?.call(transition);\n    super.onTransition(transition);\n  }\n\n  @override\n  bool shouldReplay(int state) {\n    return _shouldReplayCallback?.call(state) ?? super.shouldReplay(state);\n  }\n}\n\n// ignore: prefer_file_naming_conventions\nclass CounterBlocMixin extends Bloc<CounterEvent, int>\n    with ReplayBlocMixin<CounterEvent, int> {\n  CounterBlocMixin({int? limit}) : super(0) {\n    if (limit != null) this.limit = limit;\n    on<Increment>((event, emit) => emit(state + 1));\n    on<Decrement>((event, emit) => emit(state - 1));\n  }\n}\n"
  },
  {
    "path": "packages/replay_bloc/test/cubits/counter_cubit.dart",
    "content": "import 'package:replay_bloc/replay_bloc.dart';\n\nclass CounterCubit extends ReplayCubit<int> {\n  CounterCubit({int? limit, bool Function(int)? shouldReplayCallback})\n      : _shouldReplayCallback = shouldReplayCallback,\n        super(0, limit: limit);\n\n  final bool Function(int)? _shouldReplayCallback;\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n\n  @override\n  bool shouldReplay(int state) {\n    return _shouldReplayCallback?.call(state) ?? super.shouldReplay(state);\n  }\n}\n\n// ignore: prefer_file_naming_conventions\nclass CounterCubitMixin extends Cubit<int> with ReplayCubitMixin<int> {\n  CounterCubitMixin({int? limit}) : super(0) {\n    if (limit != null) {\n      this.limit = limit;\n    }\n  }\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n"
  },
  {
    "path": "packages/replay_bloc/test/main.dart",
    "content": "import 'replay_bloc_test.dart' as replay_bloc_test;\nimport 'replay_cubit_test.dart' as replay_cubit_test;\n\nvoid main() {\n  replay_cubit_test.main();\n  replay_bloc_test.main();\n}\n"
  },
  {
    "path": "packages/replay_bloc/test/replay_bloc_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:replay_bloc/replay_bloc.dart';\nimport 'package:test/test.dart';\n\nimport 'blocs/counter_bloc.dart';\n\nvoid main() {\n  group('ReplayBloc', () {\n    group('initial state', () {\n      test('is correct', () {\n        expect(CounterBloc().state, 0);\n      });\n    });\n\n    group('canUndo', () {\n      test('is false when no state changes have occurred', () async {\n        final bloc = CounterBloc();\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n\n      test('is true when a single state change has occurred', () async {\n        final bloc = CounterBloc()..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        expect(bloc.canUndo, isTrue);\n        await bloc.close();\n      });\n\n      test('is false when undos have been exhausted', () async {\n        final bloc = CounterBloc()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('canRedo', () {\n      test('is false when no state changes have occurred', () async {\n        final bloc = CounterBloc();\n        expect(bloc.canRedo, isFalse);\n        await bloc.close();\n      });\n\n      test('is true when a single undo has occurred', () async {\n        final bloc = CounterBloc()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        expect(bloc.canRedo, isTrue);\n        await bloc.close();\n      });\n\n      test('is false when redos have been exhausted', () async {\n        final bloc = CounterBloc()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        await Future<void>.delayed(Duration.zero, bloc.redo);\n        expect(bloc.canRedo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('clearHistory', () {\n      test('clears history and redos on new bloc', () async {\n        final bloc = CounterBloc()..clearHistory();\n        expect(bloc.canRedo, isFalse);\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('undo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when limit is 0', () async {\n        final states = <int>[];\n        final bloc = CounterBloc(limit: 0);\n        final subscription = bloc.stream.listen(states.add);\n        bloc.add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1]);\n      });\n\n      test('skips states filtered out by shouldReplay at undo time', () async {\n        final states = <int>[];\n        final bloc = CounterBloc(shouldReplayCallback: (i) => !i.isEven);\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..undo()\n          ..undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1]);\n      });\n\n      test(\n          \"doesn't skip states that would be filtered out by shouldReplay \"\n          'at transition time but not at undo time', () async {\n        var replayEvens = false;\n        final states = <int>[];\n        final bloc = CounterBloc(\n          shouldReplayCallback: (i) => !i.isEven || replayEvens,\n        );\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        replayEvens = true;\n        bloc\n          ..undo()\n          ..undo()\n          ..undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 2, 1, 0]);\n      });\n\n      test('loses history outside of limit', () async {\n        final states = <int>[];\n        final bloc = CounterBloc(limit: 1);\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n\n      test('reverts to initial state', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 0]);\n      });\n\n      test('reverts to previous state with multiple state changes ', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n\n      test('triggers onEvent', () async {\n        final onEventCalls = <ReplayEvent>[];\n        final bloc = CounterBloc(onEventCallback: onEventCalls.add)\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        expect(onEventCalls.length, 2);\n        expect(onEventCalls.last.toString(), 'Undo');\n      });\n\n      test('triggers onTransition', () async {\n        final onTransitionCalls = <Transition<ReplayEvent, int>>[];\n        final bloc = CounterBloc(onTransitionCallback: onTransitionCalls.add)\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        expect(onTransitionCalls.length, 2);\n        expect(\n          onTransitionCalls.last.toString(),\n          'Transition { currentState: 1, event: Undo, nextState: 0 }',\n        );\n      });\n    });\n\n    group('redo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when no undos have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2]);\n      });\n\n      test('works when one undo has occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test('triggers onEvent', () async {\n        final onEventCalls = <ReplayEvent>[];\n        final bloc = CounterBloc(onEventCallback: onEventCalls.add)\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo();\n        await bloc.close();\n        expect(onEventCalls.length, 3);\n        expect(onEventCalls.last.toString(), 'Redo');\n      });\n\n      test('triggers onTransition', () async {\n        final onTransitionCalls = <Transition<ReplayEvent, int>>[];\n        final bloc = CounterBloc(onTransitionCallback: onTransitionCalls.add)\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo();\n        await bloc.close();\n        expect(onTransitionCalls.length, 3);\n        expect(\n          onTransitionCalls.last.toString(),\n          'Transition { currentState: 0, event: Redo, nextState: 1 }',\n        );\n      });\n\n      test('does nothing when undos have been exhausted', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test(\n          'does nothing when undos has occurred '\n          'followed by a new state change', () async {\n        final states = <int>[];\n        final bloc = CounterBloc();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..add(Decrement());\n        await Future<void>.delayed(Duration.zero);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 0]);\n      });\n\n      test(\n          'redo does not redo states which were'\n          ' filtered out by shouldReplay at undo time', () async {\n        final states = <int>[];\n        final bloc = CounterBloc(shouldReplayCallback: (i) => !i.isEven);\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..undo()\n          ..undo()\n          ..redo()\n          ..redo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1, 3]);\n      });\n\n      test(\n          'redo does not redo states which were'\n          ' filtered out by shouldReplay at transition time', () async {\n        var replayEvens = false;\n        final states = <int>[];\n        final bloc = CounterBloc(\n          shouldReplayCallback: (i) => !i.isEven || replayEvens,\n        );\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..undo()\n          ..undo();\n        replayEvens = true;\n        bloc\n          ..redo()\n          ..redo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1, 2, 3]);\n      });\n    });\n  });\n\n  group('ReplayBlocMixin', () {\n    group('initial state', () {\n      test('is correct', () {\n        expect(CounterBlocMixin().state, 0);\n      });\n    });\n\n    group('canUndo', () {\n      test('is false when no state changes have occurred', () async {\n        final bloc = CounterBlocMixin();\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n\n      test('is true when a single state change has occurred', () async {\n        final bloc = CounterBlocMixin()..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        expect(bloc.canUndo, isTrue);\n        await bloc.close();\n      });\n\n      test('is false when undos have been exhausted', () async {\n        final bloc = CounterBlocMixin()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('canRedo', () {\n      test('is false when no state changes have occurred', () async {\n        final bloc = CounterBlocMixin();\n        expect(bloc.canRedo, isFalse);\n        await bloc.close();\n      });\n\n      test('is true when a single undo has occurred', () async {\n        final bloc = CounterBlocMixin()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        expect(bloc.canRedo, isTrue);\n        await bloc.close();\n      });\n\n      test('is false when redos have been exhausted', () async {\n        final bloc = CounterBlocMixin()..add(Increment());\n        await Future<void>.delayed(Duration.zero, bloc.undo);\n        await Future<void>.delayed(Duration.zero, bloc.redo);\n        expect(bloc.canRedo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('clearHistory', () {\n      test('clears history and redos on new bloc', () async {\n        final bloc = CounterBlocMixin()..clearHistory();\n        expect(bloc.canRedo, isFalse);\n        expect(bloc.canUndo, isFalse);\n        await bloc.close();\n      });\n    });\n\n    group('undo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when limit is 0', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin(limit: 0);\n        final subscription = bloc.stream.listen(states.add);\n        bloc.add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1]);\n      });\n\n      test('loses history outside of limit', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin(limit: 1);\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n\n      test('reverts to initial state', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 0]);\n      });\n\n      test('reverts to previous state with multiple state changes ', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.undo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n    });\n\n    group('redo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when no undos have occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2]);\n      });\n\n      test('works when one undo has occurred', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test('does nothing when undos have been exhausted', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..redo()\n          ..redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test(\n          'does nothing when undos has occurred '\n          'followed by a new state change', () async {\n        final states = <int>[];\n        final bloc = CounterBlocMixin();\n        final subscription = bloc.stream.listen(states.add);\n        bloc\n          ..add(Increment())\n          ..add(Increment());\n        await Future<void>.delayed(Duration.zero);\n        bloc\n          ..undo()\n          ..add(Decrement());\n        await Future<void>.delayed(Duration.zero);\n        bloc.redo();\n        await bloc.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 0]);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/replay_bloc/test/replay_cubit_test.dart",
    "content": "import 'dart:async';\n\nimport 'package:test/test.dart';\n\nimport 'cubits/counter_cubit.dart';\n\nvoid main() {\n  group('ReplayCubit', () {\n    group('initial state', () {\n      test('is correct', () {\n        expect(CounterCubit().state, 0);\n      });\n    });\n\n    group('canUndo', () {\n      test('is false when no state changes have occurred', () async {\n        final cubit = CounterCubit();\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n\n      test('is true when a single state change has occurred', () async {\n        final cubit = CounterCubit();\n        await Future<void>.delayed(Duration.zero, cubit.increment);\n        expect(cubit.canUndo, isTrue);\n        await cubit.close();\n      });\n\n      test('is false when undos have been exhausted', () async {\n        final cubit = CounterCubit();\n        await Future<void>.delayed(Duration.zero, cubit.increment);\n        await Future<void>.delayed(Duration.zero, cubit.undo);\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('canRedo', () {\n      test('is false when no state changes have occurred', () async {\n        final cubit = CounterCubit();\n        expect(cubit.canRedo, isFalse);\n        await cubit.close();\n      });\n\n      test('is true when a single undo has occurred', () async {\n        final cubit = CounterCubit();\n        await Future<void>.delayed(Duration.zero, cubit.increment);\n        await Future<void>.delayed(Duration.zero, cubit.undo);\n        expect(cubit.canRedo, isTrue);\n        await cubit.close();\n      });\n\n      test('is false when redos have been exhausted', () async {\n        final cubit = CounterCubit();\n        await Future<void>.delayed(Duration.zero, cubit.increment);\n        await Future<void>.delayed(Duration.zero, cubit.undo);\n        await Future<void>.delayed(Duration.zero, cubit.redo);\n        expect(cubit.canRedo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('clearHistory', () {\n      test('clears history and redos on new cubit', () async {\n        final cubit = CounterCubit()..clearHistory();\n        expect(cubit.canRedo, isFalse);\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('undo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit.undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when limit is 0', () async {\n        final states = <int>[];\n        final cubit = CounterCubit(limit: 0);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1]);\n      });\n\n      test('skips states filtered out by shouldReplay at undo time', () async {\n        final states = <int>[];\n        final cubit = CounterCubit(shouldReplayCallback: (i) => !i.isEven);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..increment();\n        await Future<void>.delayed(Duration.zero);\n        cubit\n          ..undo()\n          ..undo()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1]);\n      });\n\n      test(\n          \"doesn't skip states that would be filtered out by shouldReplay \"\n          'at transition time but not at undo time', () async {\n        var replayEvens = false;\n        final states = <int>[];\n        final cubit = CounterCubit(\n          shouldReplayCallback: (i) => !i.isEven || replayEvens,\n        );\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..increment();\n        await Future<void>.delayed(Duration.zero);\n        replayEvens = true;\n        cubit\n          ..undo()\n          ..undo()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 2, 1, 0]);\n      });\n\n      test('loses history outside of limit', () async {\n        final states = <int>[];\n        final cubit = CounterCubit(limit: 1);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n\n      test('reverts to initial state', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 0]);\n      });\n\n      test('reverts to previous state with multiple state changes ', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n    });\n\n    group('redo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit.redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when no undos have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2]);\n      });\n\n      test('works when one undo has occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test('does nothing when undos have been exhausted', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..redo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test(\n          'does nothing when undos has occurred '\n          'followed by a new state change', () async {\n        final states = <int>[];\n        final cubit = CounterCubit();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..decrement()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 0]);\n      });\n\n      test(\n          'redo does not redo states which were'\n          ' filtered out by shouldReplay at undo time', () async {\n        final states = <int>[];\n        final cubit = CounterCubit(shouldReplayCallback: (i) => !i.isEven);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..increment()\n          ..undo()\n          ..undo()\n          ..undo()\n          ..redo()\n          ..redo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1, 3]);\n      });\n\n      test(\n          'redo does not redo states which were'\n          ' filtered out by shouldReplay at transition time', () async {\n        var replayEvens = false;\n        final states = <int>[];\n        final cubit = CounterCubit(\n          shouldReplayCallback: (i) => !i.isEven || replayEvens,\n        );\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..increment()\n          ..undo()\n          ..undo()\n          ..undo();\n        replayEvens = true;\n        cubit\n          ..redo()\n          ..redo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 3, 1, 2, 3]);\n      });\n    });\n  });\n\n  group('ReplayMixin', () {\n    group('initial state', () {\n      test('is correct', () {\n        expect(CounterCubitMixin().state, 0);\n      });\n    });\n\n    group('canUndo', () {\n      test('is false when no state changes have occurred', () async {\n        final cubit = CounterCubitMixin();\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n\n      test('is true when a single state change has occurred', () async {\n        final cubit = CounterCubitMixin()..increment();\n        expect(cubit.canUndo, isTrue);\n        await cubit.close();\n      });\n\n      test('is false when undos have been exhausted', () async {\n        final cubit = CounterCubitMixin()\n          ..increment()\n          ..undo();\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('canRedo', () {\n      test('is false when no state changes have occurred', () async {\n        final cubit = CounterCubitMixin();\n        expect(cubit.canRedo, isFalse);\n        await cubit.close();\n      });\n\n      test('is true when a single undo has occurred', () async {\n        final cubit = CounterCubitMixin()\n          ..increment()\n          ..undo();\n        expect(cubit.canRedo, isTrue);\n        await cubit.close();\n      });\n\n      test('is false when redos have been exhausted', () async {\n        final cubit = CounterCubitMixin()\n          ..increment()\n          ..undo()\n          ..redo();\n        expect(cubit.canRedo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('clearHistory', () {\n      test('clears history and redos on new cubit', () async {\n        final cubit = CounterCubitMixin()..clearHistory();\n        expect(cubit.canRedo, isFalse);\n        expect(cubit.canUndo, isFalse);\n        await cubit.close();\n      });\n    });\n\n    group('undo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit.undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when limit is 0', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin(limit: 0);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1]);\n      });\n\n      test('loses history outside of limit', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin(limit: 1);\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n\n      test('reverts to initial state', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 0]);\n      });\n\n      test('reverts to previous state with multiple state changes ', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1]);\n      });\n    });\n\n    group('redo', () {\n      test('does nothing when no state changes have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        await Future<void>.delayed(Duration.zero, cubit.redo);\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, isEmpty);\n      });\n\n      test('does nothing when no undos have occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2]);\n      });\n\n      test('works when one undo has occurred', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test('does nothing when undos have been exhausted', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..redo()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 2]);\n      });\n\n      test(\n          'does nothing when undos has occurred '\n          'followed by a new state change', () async {\n        final states = <int>[];\n        final cubit = CounterCubitMixin();\n        final subscription = cubit.stream.listen(states.add);\n        cubit\n          ..increment()\n          ..increment()\n          ..undo()\n          ..decrement()\n          ..redo();\n        await cubit.close();\n        await subscription.cancel();\n        expect(states, const <int>[1, 2, 1, 0]);\n      });\n    });\n  });\n}\n"
  }
]