├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── example_request.md │ └── feature_request.md ├── dependabot.yaml └── workflows │ ├── build.yml │ └── project.yml ├── .gitignore ├── .vscode └── launch.json ├── all_lint_rules.yaml ├── analysis_options.yaml ├── build_devtool.sh ├── packages ├── provider │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── devtools_options.yaml │ │ ├── lib │ │ │ └── main.dart │ │ ├── pubspec.yaml │ │ ├── test │ │ │ └── widget_test.dart │ │ └── test_driver │ │ │ ├── app.dart │ │ │ └── app_test.dart │ ├── extension │ │ └── devtools │ │ │ ├── .pubignore │ │ │ └── config.yaml │ ├── lib │ │ ├── provider.dart │ │ ├── single_child_widget.dart │ │ └── src │ │ │ ├── async_provider.dart │ │ │ ├── change_notifier_provider.dart │ │ │ ├── consumer.dart │ │ │ ├── deferred_inherited_provider.dart │ │ │ ├── devtool.dart │ │ │ ├── inherited_provider.dart │ │ │ ├── listenable_provider.dart │ │ │ ├── provider.dart │ │ │ ├── proxy_provider.dart │ │ │ ├── reassemble_handler.dart │ │ │ ├── selector.dart │ │ │ └── value_listenable_provider.dart │ ├── pubspec.yaml │ └── test │ │ ├── flutter_test_config.dart │ │ └── null_safe │ │ ├── builder_test.dart │ │ ├── change_notifier_provider_test.dart │ │ ├── common.dart │ │ ├── consumer_test.dart │ │ ├── context_test.dart │ │ ├── devtool_test.dart │ │ ├── future_provider_test.dart │ │ ├── inherited_provider_test.dart │ │ ├── listenable_provider_test.dart │ │ ├── listenable_proxy_provider_test.dart │ │ ├── matchers.dart │ │ ├── multi_provider_test.dart │ │ ├── provider_test.dart │ │ ├── proxy_provider_test.dart │ │ ├── reassemble_test.dart │ │ ├── selector_test.dart │ │ ├── stateful_provider_test.dart │ │ ├── stream_provider_test.dart │ │ └── value_listenable_test.dart └── provider_devtools_extension │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── README.md │ ├── analysis_options.yaml │ ├── integration_test │ └── provider_integration_test.dart │ ├── lib │ ├── main.dart │ └── src │ │ ├── instance_viewer │ │ ├── eval.dart │ │ ├── fake_freezed_annotation.dart │ │ ├── instance_details.dart │ │ ├── instance_details.freezed.dart │ │ ├── instance_providers.dart │ │ ├── instance_viewer.dart │ │ ├── result.dart │ │ └── result.freezed.dart │ │ ├── provider_list.dart │ │ ├── provider_nodes.dart │ │ ├── provider_screen.dart │ │ └── utils │ │ ├── riverpod_error_logger_observer.dart │ │ └── sliver_iterable_child_delegate.dart │ ├── pubspec.yaml │ ├── test │ ├── provider_screen_test.dart │ └── test_utils.dart │ ├── test_driver │ └── integration_test.dart │ └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── resources ├── devtools_providers.jpg ├── expanded_devtools.jpg ├── flutter_favorite.png └── translations ├── bn_BD └── README.md ├── es_MX └── README.md ├── fr_FR └── README.md ├── it_IT └── README.md ├── ja_JP └── README.md ├── ko-KR └── README.md ├── pt_br └── README.md ├── tr_TR └── README.md └── zh-CN └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rrousselGit 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: There is a problem in how provider behaves 4 | title: "" 5 | labels: bug, needs triage 6 | assignees: 7 | - rrousselGit 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a problem and I need help 4 | url: https://github.com/rrousselGit/provider/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/example_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation improvement request 3 | about: >- 4 | Suggest a new example/documentation or ask for clarification about an 5 | existing one. 6 | title: "" 7 | labels: documentation, needs triage 8 | assignees: 9 | - rrousselGit 10 | --- 11 | 12 | **Describe what scenario you think is uncovered by the existing examples/articles** 13 | A clear and concise description of the problem that you want explained. 14 | 15 | **Describe why existing examples/articles do not cover this case** 16 | Explain which examples/articles you have seen before making this request, and 17 | why they did not help you with your problem. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the documentation request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement, needs triage 6 | assignees: 7 | - rrousselGit 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | enable-beta-ecosystems: true 3 | updates: 4 | - package-ecosystem: "pub" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "**.md" 7 | push: 8 | branches: 9 | - master 10 | paths-ignore: 11 | - "**.md" 12 | schedule: 13 | # runs the CI everyday at 10AM 14 | - cron: "0 10 * * *" 15 | 16 | jobs: 17 | flutter: 18 | runs-on: ubuntu-latest 19 | 20 | defaults: 21 | run: 22 | working-directory: packages/provider 23 | 24 | strategy: 25 | matrix: 26 | channel: 27 | - master 28 | 29 | steps: 30 | - uses: actions/checkout@v3.1.0 31 | 32 | - uses: subosito/flutter-action@v2.7.1 33 | with: 34 | channel: ${{ matrix.channel }} 35 | 36 | - name: Add pub cache bin to PATH 37 | run: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH 38 | - name: Add pub cache to PATH 39 | run: echo "PUB_CACHE="$HOME/.pub-cache"" >> $GITHUB_ENV 40 | 41 | - name: Install dependencies 42 | run: flutter pub get 43 | 44 | - run: dart format lib test --set-exit-if-changed 45 | if: matrix.channel == 'master' 46 | 47 | - run: flutter analyze --no-current-package 48 | 49 | - run: flutter test --no-pub --coverage test/null_safe 50 | -------------------------------------------------------------------------------- /.github/workflows/project.yml: -------------------------------------------------------------------------------- 1 | name: Add new issues to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | pull_request: 9 | types: 10 | - opened 11 | - reopened 12 | 13 | jobs: 14 | add-to-project: 15 | name: Add issue to project 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/add-to-project@v0.5.0 19 | with: 20 | project-url: https://github.com/users/rrousselGit/projects/8 21 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | android/ 4 | ios/ 5 | macos/ 6 | .packages 7 | build 8 | 9 | # Remove the following pattern if you wish to check in your lock file 10 | pubspec.lock 11 | 12 | # Conventional directory for build outputs 13 | /build/ 14 | coverage/ 15 | 16 | # Directory created by dartdoc 17 | doc/api/ 18 | 19 | # IntelliJ 20 | .idea/ 21 | 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "provider", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "provider (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "provider (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | }, 24 | { 25 | "name": "example", 26 | "cwd": "example", 27 | "request": "launch", 28 | "type": "dart" 29 | }, 30 | { 31 | "name": "example (profile mode)", 32 | "cwd": "example", 33 | "request": "launch", 34 | "type": "dart", 35 | "flutterMode": "profile" 36 | }, 37 | { 38 | "name": "example (release mode)", 39 | "cwd": "example", 40 | "request": "launch", 41 | "type": "dart", 42 | "flutterMode": "release" 43 | }, 44 | { 45 | "name": "provider_devtools_extension", 46 | "cwd": "packages/provider_devtools_extension", 47 | "request": "launch", 48 | "type": "dart" 49 | }, 50 | { 51 | "name": "provider_devtools_extension + simulated environment", 52 | "program": "packages/provider_devtools_extension/lib/main.dart", 53 | "request": "launch", 54 | "type": "dart", 55 | "args": [ 56 | "--dart-define=use_simulated_environment=true" 57 | ], 58 | }, 59 | { 60 | "name": "provider_devtools_extension (profile mode)", 61 | "cwd": "packages/provider_devtools_extension", 62 | "request": "launch", 63 | "type": "dart", 64 | "flutterMode": "profile" 65 | }, 66 | { 67 | "name": "provider_devtools_extension (release mode)", 68 | "cwd": "packages/provider_devtools_extension", 69 | "request": "launch", 70 | "type": "dart", 71 | "flutterMode": "release" 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /all_lint_rules.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - always_declare_return_types 4 | - always_put_control_body_on_new_line 5 | - always_put_required_named_parameters_first 6 | - always_require_non_null_named_parameters 7 | - always_specify_types 8 | - always_use_package_imports 9 | - annotate_overrides 10 | - avoid_annotating_with_dynamic 11 | - avoid_bool_literals_in_conditional_expressions 12 | - avoid_catches_without_on_clauses 13 | - avoid_catching_errors 14 | - avoid_classes_with_only_static_members 15 | - avoid_double_and_int_checks 16 | - avoid_dynamic_calls 17 | - avoid_empty_else 18 | - avoid_equals_and_hash_code_on_mutable_classes 19 | - avoid_escaping_inner_quotes 20 | - avoid_field_initializers_in_const_classes 21 | - avoid_function_literals_in_foreach_calls 22 | - avoid_implementing_value_types 23 | - avoid_init_to_null 24 | - avoid_js_rounded_ints 25 | - avoid_null_checks_in_equality_operators 26 | - avoid_positional_boolean_parameters 27 | - avoid_print 28 | - avoid_private_typedef_functions 29 | - avoid_redundant_argument_values 30 | - avoid_relative_lib_imports 31 | - avoid_renaming_method_parameters 32 | - avoid_return_types_on_setters 33 | - avoid_returning_null 34 | - avoid_returning_null_for_future 35 | - avoid_returning_null_for_void 36 | - avoid_returning_this 37 | - avoid_setters_without_getters 38 | - avoid_shadowing_type_parameters 39 | - avoid_single_cascade_in_expression_statements 40 | - avoid_slow_async_io 41 | - avoid_type_to_string 42 | - avoid_types_as_parameter_names 43 | - avoid_types_on_closure_parameters 44 | - avoid_unnecessary_containers 45 | - avoid_unused_constructor_parameters 46 | - avoid_void_async 47 | - avoid_web_libraries_in_flutter 48 | - await_only_futures 49 | - camel_case_extensions 50 | - camel_case_types 51 | - cancel_subscriptions 52 | - cascade_invocations 53 | - cast_nullable_to_non_nullable 54 | - close_sinks 55 | - comment_references 56 | - constant_identifier_names 57 | - control_flow_in_finally 58 | - curly_braces_in_flow_control_structures 59 | - diagnostic_describe_all_properties 60 | - directives_ordering 61 | - do_not_use_environment 62 | - empty_catches 63 | - empty_constructor_bodies 64 | - empty_statements 65 | - exhaustive_cases 66 | - file_names 67 | - flutter_style_todos 68 | - hash_and_equals 69 | - implementation_imports 70 | - invariant_booleans 71 | - iterable_contains_unrelated_type 72 | - join_return_with_assignment 73 | - leading_newlines_in_multiline_strings 74 | - library_names 75 | - library_prefixes 76 | - lines_longer_than_80_chars 77 | - list_remove_unrelated_type 78 | - literal_only_boolean_expressions 79 | - missing_whitespace_between_adjacent_strings 80 | - no_adjacent_strings_in_list 81 | - no_default_cases 82 | - no_duplicate_case_values 83 | - no_logic_in_create_state 84 | - no_runtimeType_toString 85 | - non_constant_identifier_names 86 | - null_check_on_nullable_type_parameter 87 | - null_closures 88 | - omit_local_variable_types 89 | - one_member_abstracts 90 | - only_throw_errors 91 | - overridden_fields 92 | - package_api_docs 93 | - package_names 94 | - package_prefixed_library_names 95 | - parameter_assignments 96 | - prefer_adjacent_string_concatenation 97 | - prefer_asserts_in_initializer_lists 98 | - prefer_asserts_with_message 99 | - prefer_collection_literals 100 | - prefer_conditional_assignment 101 | - prefer_const_constructors 102 | - prefer_const_constructors_in_immutables 103 | - prefer_const_declarations 104 | - prefer_const_literals_to_create_immutables 105 | - prefer_constructors_over_static_methods 106 | - prefer_contains 107 | - prefer_double_quotes 108 | - prefer_equal_for_default_values 109 | - prefer_expression_function_bodies 110 | - prefer_final_fields 111 | - prefer_final_in_for_each 112 | - prefer_final_locals 113 | - prefer_for_elements_to_map_fromIterable 114 | - prefer_foreach 115 | - prefer_function_declarations_over_variables 116 | - prefer_generic_function_type_aliases 117 | - prefer_if_elements_to_conditional_expressions 118 | - prefer_if_null_operators 119 | - prefer_initializing_formals 120 | - prefer_inlined_adds 121 | - prefer_int_literals 122 | - prefer_interpolation_to_compose_strings 123 | - prefer_is_empty 124 | - prefer_is_not_empty 125 | - prefer_is_not_operator 126 | - prefer_iterable_whereType 127 | - prefer_mixin 128 | - prefer_null_aware_operators 129 | - prefer_relative_imports 130 | - prefer_single_quotes 131 | - prefer_spread_collections 132 | - prefer_typing_uninitialized_variables 133 | - prefer_void_to_null 134 | - provide_deprecation_message 135 | - public_member_api_docs 136 | - recursive_getters 137 | - sized_box_for_whitespace 138 | - slash_for_doc_comments 139 | - sort_child_properties_last 140 | - sort_constructors_first 141 | - sort_pub_dependencies 142 | - sort_unnamed_constructors_first 143 | - test_types_in_equals 144 | - throw_in_finally 145 | - tighten_type_of_initializing_formals 146 | - type_annotate_public_apis 147 | - type_init_formals 148 | - unawaited_futures 149 | - unnecessary_await_in_return 150 | - unnecessary_brace_in_string_interps 151 | - unnecessary_const 152 | - unnecessary_final 153 | - unnecessary_getters_setters 154 | - unnecessary_lambdas 155 | - unnecessary_new 156 | - unnecessary_null_aware_assignments 157 | - unnecessary_null_checks 158 | - unnecessary_null_in_if_null_operators 159 | - unnecessary_nullable_for_final_variable_declarations 160 | - unnecessary_overrides 161 | - unnecessary_parenthesis 162 | - unnecessary_raw_strings 163 | - unnecessary_statements 164 | - unnecessary_string_escapes 165 | - unnecessary_string_interpolations 166 | - unnecessary_this 167 | - unrelated_type_equality_checks 168 | - unsafe_html 169 | - use_full_hex_values_for_flutter_colors 170 | - use_function_type_syntax_for_parameters 171 | - use_is_even_rather_than_modulo 172 | - use_key_in_widget_constructors 173 | - use_late_for_private_fields_and_variables 174 | - use_raw_strings 175 | - use_rethrow_when_possible 176 | - use_setters_to_change_properties 177 | - use_string_buffers 178 | - use_to_and_as_if_applicable 179 | - valid_regexps 180 | - void_checks -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: all_lint_rules.yaml 2 | analyzer: 3 | exclude: 4 | - "**/*.g.dart" 5 | language: 6 | strict-casts: true 7 | strict-inference: true 8 | strict-raw-types: true 9 | errors: 10 | # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. 11 | # We explicitly enabled even conflicting rules and are fixing the conflict 12 | # in this file 13 | included_file_warning: ignore 14 | linter: 15 | rules: 16 | # Personal preference. I don't find it more readable 17 | cascade_invocations: false 18 | 19 | # Conflicts with `prefer_single_quotes` 20 | # Single quotes are easier to type and don't compromise on readability. 21 | prefer_double_quotes: false 22 | 23 | # Conflicts with `omit_local_variable_types` and other rules. 24 | # As per Dart guidelines, we want to avoid unnecessary types to make the code 25 | # more readable. 26 | # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables 27 | always_specify_types: false 28 | 29 | # Incompatible with `prefer_final_locals` 30 | # Having immutable local variables makes larger functions more predictible 31 | # so we will use `prefer_final_locals` instead. 32 | unnecessary_final: false 33 | 34 | # Not quite suitable for Flutter, which may have a `build` method with a single 35 | # return, but that return is still complex enough that a "body" is worth it. 36 | prefer_expression_function_bodies: false 37 | 38 | # Conflicts with the convention used by flutter, which puts `Key key` 39 | # and `@required Widget child` last. 40 | always_put_required_named_parameters_first: false 41 | 42 | # This project doesn't use Flutter-style todos 43 | flutter_style_todos: false 44 | 45 | # There are situations where we voluntarily want to catch everything, 46 | # especially as a library. 47 | avoid_catches_without_on_clauses: false 48 | 49 | # Boring as it sometimes force a line of 81 characters to be split in two. 50 | # As long as we try to respect that 80 characters limit, going slightly 51 | # above is fine. 52 | lines_longer_than_80_chars: false 53 | 54 | # Conflicts with disabling `implicit-dynamic` 55 | avoid_annotating_with_dynamic: false 56 | 57 | # conflicts with `prefer_relative_imports` 58 | always_use_package_imports: false 59 | 60 | # Disabled for now until we have NNBD as it otherwise conflicts with `missing_return` 61 | no_default_cases: false 62 | 63 | # False positive, null checks don't need a message 64 | prefer_asserts_with_message: false 65 | 66 | # Cumbersome with `context.select` 67 | avoid_types_on_closure_parameters: false 68 | 69 | # Too many false positive (builders) 70 | diagnostic_describe_all_properties: false 71 | 72 | # false positives (setter-like functions) 73 | avoid_positional_boolean_parameters: false 74 | 75 | # Does not apply to providers 76 | prefer_const_constructors_in_immutables: false 77 | -------------------------------------------------------------------------------- /build_devtool.sh: -------------------------------------------------------------------------------- 1 | pushd packages/provider 2 | 3 | rm -rf extension/devtools/build 4 | mkdir extension/devtools/build 5 | 6 | popd 7 | 8 | pushd packages/provider_devtools_extension 9 | 10 | flutter pub get && 11 | dart run devtools_extensions build_and_copy \ 12 | --source=. \ 13 | --dest=../provider/extension/devtools 14 | 15 | popd 16 | -------------------------------------------------------------------------------- /packages/provider/.gitignore: -------------------------------------------------------------------------------- 1 | extension/devtools/build -------------------------------------------------------------------------------- /packages/provider/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Remi Rousselet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/provider/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../../analysis_options.yaml 2 | linter: 3 | rules: 4 | public_member_api_docs: false 5 | lines_longer_than_80_chars: false -------------------------------------------------------------------------------- /packages/provider/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /packages/provider/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "d25349eb1c7a0dbae4f3a734d34f07ddda74adaf" 8 | channel: "master" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 17 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 18 | - platform: android 19 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 20 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 21 | - platform: ios 22 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 23 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 24 | - platform: linux 25 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 26 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 27 | - platform: macos 28 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 29 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 30 | - platform: web 31 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 32 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 33 | - platform: windows 34 | create_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 35 | base_revision: d25349eb1c7a0dbae4f3a734d34f07ddda74adaf 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /packages/provider/example/devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | - provider: true -------------------------------------------------------------------------------- /packages/provider/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | /// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier]. 6 | 7 | void main() { 8 | runApp( 9 | /// Providers are above [MyApp] instead of inside it, so that tests 10 | /// can use [MyApp] while mocking the providers 11 | MultiProvider( 12 | providers: [ 13 | ChangeNotifierProvider(create: (_) => Counter()), 14 | ], 15 | child: const MyApp(), 16 | ), 17 | ); 18 | } 19 | 20 | /// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool 21 | // ignore: prefer_mixin 22 | class Counter with ChangeNotifier, DiagnosticableTreeMixin { 23 | int _count = 0; 24 | 25 | int get count => _count; 26 | 27 | void increment() { 28 | _count++; 29 | notifyListeners(); 30 | } 31 | 32 | /// Makes `Counter` readable inside the devtools by listing all of its properties 33 | @override 34 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 35 | super.debugFillProperties(properties); 36 | properties.add(IntProperty('count', count)); 37 | } 38 | } 39 | 40 | class MyApp extends StatelessWidget { 41 | const MyApp({Key? key}) : super(key: key); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return const MaterialApp( 46 | home: MyHomePage(), 47 | ); 48 | } 49 | } 50 | 51 | class MyHomePage extends StatelessWidget { 52 | const MyHomePage({Key? key}) : super(key: key); 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | appBar: AppBar( 58 | title: const Text('Example'), 59 | ), 60 | body: const Center( 61 | child: Column( 62 | mainAxisSize: MainAxisSize.min, 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | children: [ 65 | Text('You have pushed the button this many times:'), 66 | 67 | /// Extracted as a separate widget for performance optimization. 68 | /// As a separate widget, it will rebuild independently from [MyHomePage]. 69 | /// 70 | /// This is totally optional (and rarely needed). 71 | /// Similarly, we could also use [Consumer] or [Selector]. 72 | Count(), 73 | ], 74 | ), 75 | ), 76 | floatingActionButton: FloatingActionButton( 77 | key: const Key('increment_floatingActionButton'), 78 | 79 | /// Calls `context.read` instead of `context.watch` so that it does not rebuild 80 | /// when [Counter] changes. 81 | onPressed: () => context.read().increment(), 82 | tooltip: 'Increment', 83 | child: const Icon(Icons.add), 84 | ), 85 | ); 86 | } 87 | } 88 | 89 | class Count extends StatelessWidget { 90 | const Count({Key? key}) : super(key: key); 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return Text( 95 | /// Calls `context.watch` to make [Count] rebuild when [Counter] changes. 96 | '${context.watch().count}', 97 | key: const Key('counterState'), 98 | style: Theme.of(context).textTheme.headlineMedium, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/provider/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | publish_to: "none" 3 | 4 | environment: 5 | sdk: ">=2.12.0-0 <3.0.0" 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | provider: 11 | 12 | dev_dependencies: 13 | flutter_driver: 14 | sdk: flutter 15 | flutter_test: 16 | sdk: flutter 17 | test: ^1.15.7 18 | 19 | dependency_overrides: 20 | provider: 21 | path: ../ 22 | 23 | flutter: 24 | uses-material-design: true 25 | -------------------------------------------------------------------------------- /packages/provider/example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:example/main.dart' as example; 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_test/flutter_test.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (tester) async { 15 | // Build our app and trigger a frame. 16 | 17 | example.main(); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | test('Counter toString()', () { 32 | final counter = example.Counter(); 33 | 34 | expect(counter.toString(), '${describeIdentity(counter)}(count: 0)'); 35 | 36 | counter.increment(); 37 | 38 | expect(counter.toString(), '${describeIdentity(counter)}(count: 1)'); 39 | }); 40 | test('test coverage', () { 41 | // remove when https://github.com/dart-lang/sdk/issues/38934 is closed 42 | const example.Count(); 43 | const example.MyHomePage(); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /packages/provider/example/test_driver/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/main.dart' as app; 2 | import 'package:flutter_driver/driver_extension.dart'; 3 | 4 | void main() { 5 | // This line enables the extension. 6 | enableFlutterDriverExtension(); 7 | 8 | // Call the `main()` function of the app, or call `runApp` with 9 | // any widget you are interested in testing. 10 | app.main(); 11 | } 12 | -------------------------------------------------------------------------------- /packages/provider/example/test_driver/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('counter app', () { 6 | FlutterDriver? _driver; 7 | 8 | final incrementFloatingButton = 9 | find.byValueKey('increment_floatingActionButton'); 10 | final appBarText = find.text('Example'); 11 | final counterState = find.byValueKey('counterState'); 12 | 13 | /// connect to [FlutterDriver] 14 | setUpAll(() async { 15 | _driver = await FlutterDriver.connect(); 16 | }); 17 | 18 | /// close the driver 19 | tearDownAll(() async { 20 | await _driver?.close(); 21 | }); 22 | 23 | test('AppBar is Flutter Demo Home Page', () async { 24 | expect(await _driver!.getText(appBarText), 'Example'); 25 | }); 26 | 27 | test('counterText is started with 0', () async { 28 | expect(await _driver!.getText(counterState), '0'); 29 | }); 30 | 31 | test('pressed increment floating action button twice', () async { 32 | // tap floating action button 33 | await _driver!.tap(incrementFloatingButton); 34 | expect(await _driver!.getText(counterState), '1'); 35 | 36 | // tap floating action button 37 | await _driver!.tap(incrementFloatingButton); 38 | expect(await _driver!.getText(counterState), '2'); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /packages/provider/extension/devtools/.pubignore: -------------------------------------------------------------------------------- 1 | !build -------------------------------------------------------------------------------- /packages/provider/extension/devtools/config.yaml: -------------------------------------------------------------------------------- 1 | name: provider 2 | issueTracker: https://github.com/rrousselGit/provider/issues 3 | version: 0.0.1 4 | materialIconCodePoint: "0xe0b1" 5 | -------------------------------------------------------------------------------- /packages/provider/lib/provider.dart: -------------------------------------------------------------------------------- 1 | library provider; 2 | 3 | export 'src/async_provider.dart' 4 | show FutureProvider, StreamProvider, ErrorBuilder; 5 | export 'src/change_notifier_provider.dart' 6 | show 7 | ChangeNotifierProvider, 8 | ChangeNotifierProxyProvider, 9 | ChangeNotifierProxyProvider0, 10 | ChangeNotifierProxyProvider2, 11 | ChangeNotifierProxyProvider3, 12 | ChangeNotifierProxyProvider4, 13 | ChangeNotifierProxyProvider5, 14 | ChangeNotifierProxyProvider6; 15 | export 'src/consumer.dart' 16 | show Consumer, Consumer2, Consumer3, Consumer4, Consumer5, Consumer6; 17 | export 'src/listenable_provider.dart' 18 | show 19 | ListenableProvider, 20 | ListenableProxyProvider, 21 | ListenableProxyProvider0, 22 | ListenableProxyProvider2, 23 | ListenableProxyProvider3, 24 | ListenableProxyProvider4, 25 | ListenableProxyProvider5, 26 | ListenableProxyProvider6; 27 | export 'src/provider.dart' 28 | show 29 | DeferredInheritedProvider, 30 | InheritedContext, 31 | InheritedProvider, 32 | MultiProvider, 33 | Provider, 34 | ProviderBinding, 35 | ProviderNotFoundException, 36 | ProviderNullException, 37 | Create, 38 | DeferredStartListening, 39 | Dispose, 40 | Locator, 41 | ReadContext, 42 | SelectContext, 43 | StartListening, 44 | UpdateShouldNotify, 45 | WatchContext; 46 | export 'src/proxy_provider.dart' 47 | show 48 | ProxyProvider, 49 | ProxyProvider0, 50 | ProxyProvider2, 51 | ProxyProvider3, 52 | ProxyProvider4, 53 | ProxyProvider5, 54 | ProxyProvider6, 55 | ProviderBuilder, 56 | ProxyProviderBuilder, 57 | ProxyProviderBuilder2, 58 | ProxyProviderBuilder3, 59 | ProxyProviderBuilder4, 60 | ProxyProviderBuilder5, 61 | ProxyProviderBuilder6; 62 | export 'src/reassemble_handler.dart' show ReassembleHandler; 63 | export 'src/selector.dart' 64 | show 65 | Selector, 66 | Selector0, 67 | Selector2, 68 | Selector3, 69 | Selector4, 70 | Selector5, 71 | Selector6, 72 | ShouldRebuild; 73 | export 'src/value_listenable_provider.dart' show ValueListenableProvider; 74 | -------------------------------------------------------------------------------- /packages/provider/lib/single_child_widget.dart: -------------------------------------------------------------------------------- 1 | export 'package:nested/nested.dart' hide Nested; 2 | -------------------------------------------------------------------------------- /packages/provider/lib/src/async_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | import 'provider.dart'; 6 | 7 | /// A callback used to build a valid value from an error. 8 | /// 9 | /// See also: 10 | /// 11 | /// * [StreamProvider] and [FutureProvider], which both uses [ErrorBuilder] to 12 | /// handle respectively `Stream.catchError` and [Future.catch]. 13 | typedef ErrorBuilder = T Function(BuildContext context, Object? error); 14 | 15 | DeferredStartListening?, T> _streamStartListening({ 16 | required T initialData, 17 | ErrorBuilder? catchError, 18 | }) { 19 | return (e, setState, controller, __) { 20 | if (!e.hasValue) { 21 | setState(initialData); 22 | } 23 | if (controller == null) { 24 | return () {}; 25 | } 26 | final sub = controller.listen( 27 | setState, 28 | onError: (Object? error) { 29 | if (catchError != null) { 30 | setState(catchError(e, error)); 31 | } else { 32 | FlutterError.reportError( 33 | FlutterErrorDetails( 34 | library: 'provider', 35 | exception: FlutterError(''' 36 | An exception was throw by ${controller.runtimeType} listened by 37 | StreamProvider<$T>, but no `catchError` was provided. 38 | 39 | Exception: 40 | $error 41 | '''), 42 | ), 43 | ); 44 | } 45 | }, 46 | ); 47 | 48 | return sub.cancel; 49 | }; 50 | } 51 | 52 | /// Listens to a [Stream] and exposes its content to `child` and descendants. 53 | /// 54 | /// Its main use-case is to provide to a large number of a widget the content 55 | /// of a [Stream], without caring about reacting to events. 56 | /// A typical example would be to expose the battery level, or a Firebase query. 57 | /// 58 | /// Trying to use [Stream] to replace [ChangeNotifier] is outside of the scope 59 | /// of this class. 60 | /// 61 | /// It is considered an error to pass a stream that can emit errors without 62 | /// providing a `catchError` method. 63 | /// 64 | /// `initialData` determines the value exposed until the [Stream] emits a value. 65 | /// 66 | /// By default, [StreamProvider] considers that the [Stream] listened uses 67 | /// immutable data. As such, it will not rebuild dependents if the previous and 68 | /// the new value are `==`. 69 | /// To change this behavior, pass a custom `updateShouldNotify`. 70 | /// 71 | /// See also: 72 | /// 73 | /// * [Stream], which is listened by [StreamProvider]. 74 | /// * [StreamController], to create a [Stream]. 75 | class StreamProvider extends DeferredInheritedProvider?, T> { 76 | /// Creates a [Stream] using `create` and subscribes to it. 77 | /// 78 | /// The parameter `create` must not be `null`. 79 | StreamProvider({ 80 | Key? key, 81 | required Create?> create, 82 | required T initialData, 83 | ErrorBuilder? catchError, 84 | UpdateShouldNotify? updateShouldNotify, 85 | bool? lazy, 86 | TransitionBuilder? builder, 87 | Widget? child, 88 | }) : super( 89 | key: key, 90 | lazy: lazy, 91 | builder: builder, 92 | create: create, 93 | updateShouldNotify: updateShouldNotify, 94 | startListening: _streamStartListening( 95 | catchError: catchError, 96 | initialData: initialData, 97 | ), 98 | child: child, 99 | ); 100 | 101 | /// Listens to `value` and expose it to all of [StreamProvider] descendants. 102 | StreamProvider.value({ 103 | Key? key, 104 | required Stream? value, 105 | required T initialData, 106 | ErrorBuilder? catchError, 107 | UpdateShouldNotify? updateShouldNotify, 108 | bool? lazy, 109 | TransitionBuilder? builder, 110 | Widget? child, 111 | }) : super.value( 112 | key: key, 113 | lazy: lazy, 114 | builder: builder, 115 | value: value, 116 | updateShouldNotify: updateShouldNotify, 117 | startListening: _streamStartListening( 118 | catchError: catchError, 119 | initialData: initialData, 120 | ), 121 | child: child, 122 | ); 123 | } 124 | 125 | DeferredStartListening?, T> _futureStartListening({ 126 | required T initialData, 127 | ErrorBuilder? catchError, 128 | }) { 129 | // ignore: void_checks, false positive 130 | return (e, setState, controller, __) { 131 | if (!e.hasValue) { 132 | setState(initialData); 133 | } 134 | 135 | var canceled = false; 136 | controller?.then( 137 | (value) { 138 | if (canceled) { 139 | return; 140 | } 141 | setState(value); 142 | }, 143 | onError: (Object? error) { 144 | if (canceled) { 145 | return; 146 | } 147 | if (catchError != null) { 148 | setState(catchError(e, error)); 149 | } else { 150 | FlutterError.reportError( 151 | FlutterErrorDetails( 152 | library: 'provider', 153 | exception: FlutterError(''' 154 | An exception was throw by ${controller.runtimeType} listened by 155 | FutureProvider<$T>, but no `catchError` was provided. 156 | 157 | Exception: 158 | $error 159 | '''), 160 | ), 161 | ); 162 | } 163 | }, 164 | ); 165 | 166 | return () => canceled = true; 167 | }; 168 | } 169 | 170 | /// Listens to a [Future] and exposes its result to `child` and its descendants. 171 | /// 172 | /// It is considered an error to pass a future that can emit errors without 173 | /// providing a `catchError` method. 174 | /// 175 | /// {@macro provider.updateshouldnotify} 176 | /// 177 | /// See also: 178 | /// 179 | /// * [Future], which is listened by [FutureProvider]. 180 | class FutureProvider extends DeferredInheritedProvider?, T> { 181 | /// Creates a [Future] from `create` and subscribes to it. 182 | /// 183 | /// `create` must not be `null`. 184 | FutureProvider({ 185 | Key? key, 186 | required Create?> create, 187 | required T initialData, 188 | ErrorBuilder? catchError, 189 | UpdateShouldNotify? updateShouldNotify, 190 | bool? lazy, 191 | TransitionBuilder? builder, 192 | Widget? child, 193 | }) : super( 194 | key: key, 195 | lazy: lazy, 196 | builder: builder, 197 | create: create, 198 | updateShouldNotify: updateShouldNotify, 199 | startListening: _futureStartListening( 200 | catchError: catchError, 201 | initialData: initialData, 202 | ), 203 | child: child, 204 | ); 205 | 206 | /// Listens to `value` and expose it to all of [FutureProvider] descendants. 207 | FutureProvider.value({ 208 | Key? key, 209 | required Future? value, 210 | required T initialData, 211 | ErrorBuilder? catchError, 212 | UpdateShouldNotify? updateShouldNotify, 213 | TransitionBuilder? builder, 214 | Widget? child, 215 | }) : super.value( 216 | key: key, 217 | builder: builder, 218 | lazy: false, 219 | value: value, 220 | updateShouldNotify: updateShouldNotify, 221 | startListening: _futureStartListening( 222 | catchError: catchError, 223 | initialData: initialData, 224 | ), 225 | child: child, 226 | ); 227 | } 228 | -------------------------------------------------------------------------------- /packages/provider/lib/src/consumer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:nested/nested.dart'; 3 | 4 | import 'provider.dart'; 5 | import 'selector.dart' show Selector; 6 | 7 | /// {@template provider.consumer} 8 | /// Obtains [Provider] from its ancestors and passes its value to [builder]. 9 | /// 10 | /// The [Consumer] widget doesn't do any fancy work. It just calls [Provider.of] 11 | /// in a new widget, and delegates its `build` implementation to [builder]. 12 | /// 13 | /// [builder] must not be null and may be called multiple times (such as when 14 | /// the provided value change). 15 | /// 16 | /// The [Consumer] widget has two main purposes: 17 | /// 18 | /// * It allows obtaining a value from a provider when we don't have a 19 | /// [BuildContext] that is a descendant of said provider, and therefore 20 | /// cannot use [Provider.of]. 21 | /// 22 | /// This scenario typically happens when the widget that creates the provider 23 | /// is also one of its consumers, like in the following example: 24 | /// 25 | /// ```dart 26 | /// @override 27 | /// Widget build(BuildContext context) { 28 | /// return ChangeNotifierProvider( 29 | /// create: (_) => Foo(), 30 | /// child: Text(Provider.of(context).value), 31 | /// ); 32 | /// } 33 | /// ``` 34 | /// 35 | /// This example will throw a [ProviderNotFoundException], because [Provider.of] 36 | /// is called with a [BuildContext] that is an ancestor of the provider. 37 | /// 38 | /// Instead, we can use the [Consumer] widget, that will call [Provider.of] 39 | /// with its own [BuildContext]. 40 | /// 41 | /// Using [Consumer], the previous example will become: 42 | /// 43 | /// ```dart 44 | /// @override 45 | /// Widget build(BuildContext context) { 46 | /// return ChangeNotifierProvider( 47 | /// create: (_) => Foo(), 48 | /// child: Consumer( 49 | /// builder: (_, foo, __) => Text(foo.value), 50 | /// }, 51 | /// ); 52 | /// } 53 | /// ``` 54 | /// 55 | /// This won't throw a [ProviderNotFoundException] and will correctly build the 56 | /// [Text]. It will also update the [Text] whenever the value `foo` changes. 57 | /// 58 | /// 59 | /// * It helps with performance optimization by providing more granular rebuilds. 60 | /// 61 | /// Unless `listen: false` is passed to [Provider.of], the widget 62 | /// associated with the [BuildContext] passed to [Provider.of] will rebuild 63 | /// whenever the obtained value changes. This is the expected behavior, 64 | /// but sometimes it may rebuild more widgets than needed. 65 | /// 66 | /// Here's an example: 67 | /// 68 | /// ```dart 69 | /// @override 70 | /// Widget build(BuildContext context) { 71 | /// return FooWidget( 72 | /// child: BarWidget( 73 | /// bar: Provider.of(context), 74 | /// ), 75 | /// ); 76 | /// } 77 | /// ``` 78 | /// 79 | /// In the above code, only `BarWidget` depends on the value returned by 80 | /// [Provider.of]. But when `Bar` changes, then both `BarWidget` _and_ 81 | /// `FooWidget` will rebuild. 82 | /// 83 | /// Ideally, only `BarWidget` should be rebuilt. One 84 | /// solution to achieve that is to use [Consumer]. 85 | /// 86 | /// To do so, we will wrap _only_ the widgets that depends on a provider into 87 | /// a [Consumer]: 88 | /// 89 | /// ```dart 90 | /// @override 91 | /// Widget build(BuildContext context) { 92 | /// return FooWidget( 93 | /// child: Consumer( 94 | /// builder: (_, bar, __) => BarWidget(bar: bar), 95 | /// ), 96 | /// ); 97 | /// } 98 | /// ``` 99 | /// 100 | /// In this situation, if `Bar` were to update, only `BarWidget` would rebuild. 101 | /// 102 | /// But what if it was `FooWidget` that depended on a provider? Example: 103 | /// 104 | /// ```dart 105 | /// @override 106 | /// Widget build(BuildContext context) { 107 | /// return FooWidget( 108 | /// foo: Provider.of(context), 109 | /// child: BarWidget(), 110 | /// ); 111 | /// } 112 | /// ``` 113 | /// 114 | /// Using [Consumer], we can handle this kind of scenario using the optional 115 | /// `child` argument: 116 | /// 117 | /// ```dart 118 | /// @override 119 | /// Widget build(BuildContext context) { 120 | /// return Consumer( 121 | /// builder: (_, foo, child) => FooWidget(foo: foo, child: child), 122 | /// child: BarWidget(), 123 | /// ); 124 | /// } 125 | /// ``` 126 | /// 127 | /// In that example, `BarWidget` is built outside of [builder]. Then, the 128 | /// `BarWidget` instance is passed to [builder] as the last parameter. 129 | /// 130 | /// This means that when [builder] is called again with new values, a new 131 | /// instance of `BarWidget` will not be created. 132 | /// This lets Flutter know that it doesn't have to rebuild `BarWidget`. 133 | /// Therefore in such a configuration, only `FooWidget` will rebuild 134 | /// if `Foo` changes. 135 | /// 136 | /// ## Note: 137 | /// 138 | /// The [Consumer] widget can also be used inside [MultiProvider]. To do so, it 139 | /// must return the `child` passed to [builder] in the widget tree it creates. 140 | /// 141 | /// ```dart 142 | /// MultiProvider( 143 | /// providers: [ 144 | /// Provider(create: (_) => Foo()), 145 | /// Consumer( 146 | /// builder: (context, foo, child) => 147 | /// Provider.value(value: foo.bar, child: child), 148 | /// ) 149 | /// ], 150 | /// ); 151 | /// ``` 152 | /// 153 | /// See also: 154 | /// * [Selector], a [Consumer] that can filter updates. 155 | /// {@endtemplate} 156 | class Consumer extends SingleChildStatelessWidget { 157 | /// {@template provider.consumer.constructor} 158 | /// Consumes a [Provider] 159 | /// {@endtemplate} 160 | Consumer({ 161 | Key? key, 162 | required this.builder, 163 | Widget? child, 164 | }) : super(key: key, child: child); 165 | 166 | /// {@template provider.consumer.builder} 167 | /// Build a widget tree based on the value from a [Provider]. 168 | /// 169 | /// Must not be `null`. 170 | /// {@endtemplate} 171 | final Widget Function( 172 | BuildContext context, 173 | T value, 174 | Widget? child, 175 | ) builder; 176 | 177 | @override 178 | Widget buildWithChild(BuildContext context, Widget? child) { 179 | return builder( 180 | context, 181 | Provider.of(context), 182 | child, 183 | ); 184 | } 185 | } 186 | 187 | /// {@macro provider.consumer} 188 | class Consumer2 extends SingleChildStatelessWidget { 189 | /// {@macro provider.consumer.constructor} 190 | Consumer2({ 191 | Key? key, 192 | required this.builder, 193 | Widget? child, 194 | }) : super(key: key, child: child); 195 | 196 | /// {@macro provider.consumer.builder} 197 | final Widget Function( 198 | BuildContext context, 199 | A value, 200 | B value2, 201 | Widget? child, 202 | ) builder; 203 | 204 | @override 205 | Widget buildWithChild(BuildContext context, Widget? child) { 206 | return builder( 207 | context, 208 | Provider.of(context), 209 | Provider.of(context), 210 | child, 211 | ); 212 | } 213 | } 214 | 215 | /// {@macro provider.consumer} 216 | class Consumer3 extends SingleChildStatelessWidget { 217 | /// {@macro provider.consumer.constructor} 218 | Consumer3({ 219 | Key? key, 220 | required this.builder, 221 | Widget? child, 222 | }) : super(key: key, child: child); 223 | 224 | /// {@macro provider.consumer.builder} 225 | final Widget Function( 226 | BuildContext context, 227 | A value, 228 | B value2, 229 | C value3, 230 | Widget? child, 231 | ) builder; 232 | 233 | @override 234 | Widget buildWithChild(BuildContext context, Widget? child) { 235 | return builder( 236 | context, 237 | Provider.of(context), 238 | Provider.of(context), 239 | Provider.of(context), 240 | child, 241 | ); 242 | } 243 | } 244 | 245 | /// {@macro provider.consumer} 246 | class Consumer4 extends SingleChildStatelessWidget { 247 | /// {@macro provider.consumer.constructor} 248 | Consumer4({ 249 | Key? key, 250 | required this.builder, 251 | Widget? child, 252 | }) : super(key: key, child: child); 253 | 254 | /// {@macro provider.consumer.builder} 255 | final Widget Function( 256 | BuildContext context, 257 | A value, 258 | B value2, 259 | C value3, 260 | D value4, 261 | Widget? child, 262 | ) builder; 263 | 264 | @override 265 | Widget buildWithChild(BuildContext context, Widget? child) { 266 | return builder( 267 | context, 268 | Provider.of(context), 269 | Provider.of(context), 270 | Provider.of(context), 271 | Provider.of(context), 272 | child, 273 | ); 274 | } 275 | } 276 | 277 | /// {@macro provider.consumer} 278 | class Consumer5 extends SingleChildStatelessWidget { 279 | /// {@macro provider.consumer.constructor} 280 | Consumer5({ 281 | Key? key, 282 | required this.builder, 283 | Widget? child, 284 | }) : super(key: key, child: child); 285 | 286 | /// {@macro provider.consumer.builder} 287 | final Widget Function( 288 | BuildContext context, 289 | A value, 290 | B value2, 291 | C value3, 292 | D value4, 293 | E value5, 294 | Widget? child, 295 | ) builder; 296 | 297 | @override 298 | Widget buildWithChild(BuildContext context, Widget? child) { 299 | return builder( 300 | context, 301 | Provider.of(context), 302 | Provider.of(context), 303 | Provider.of(context), 304 | Provider.of(context), 305 | Provider.of(context), 306 | child, 307 | ); 308 | } 309 | } 310 | 311 | /// {@macro provider.consumer} 312 | class Consumer6 extends SingleChildStatelessWidget { 313 | /// {@macro provider.consumer.constructor} 314 | Consumer6({ 315 | Key? key, 316 | required this.builder, 317 | Widget? child, 318 | }) : super(key: key, child: child); 319 | 320 | /// {@macro provider.consumer.builder} 321 | final Widget Function( 322 | BuildContext context, 323 | A value, 324 | B value2, 325 | C value3, 326 | D value4, 327 | E value5, 328 | F value6, 329 | Widget? child, 330 | ) builder; 331 | 332 | @override 333 | Widget buildWithChild(BuildContext context, Widget? child) { 334 | return builder( 335 | context, 336 | Provider.of(context), 337 | Provider.of(context), 338 | Provider.of(context), 339 | Provider.of(context), 340 | Provider.of(context), 341 | Provider.of(context), 342 | child, 343 | ); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /packages/provider/lib/src/deferred_inherited_provider.dart: -------------------------------------------------------------------------------- 1 | part of 'provider.dart'; 2 | 3 | /// A callback used to handle the subscription of `controller`. 4 | /// 5 | /// It is expected to start the listening process and return a callback 6 | /// that will later be used to stop that listening. 7 | /// 8 | /// See also: 9 | /// 10 | /// - [DeferredInheritedProvider] 11 | /// - [StartListening], a simpler version of this typedef. 12 | typedef DeferredStartListening = VoidCallback Function( 13 | InheritedContext context, 14 | void Function(R value) setState, 15 | T controller, 16 | R? value, 17 | ); 18 | 19 | /// An [InheritedProvider] where the object listened is _not_ the object 20 | /// emitted. 21 | /// 22 | /// For example, for a stream provider, we'll want to listen to `Stream`, 23 | /// but expose `T` not the [Stream]. 24 | /// 25 | /// See also: 26 | /// 27 | /// - [InheritedProvider], a variant of this object where the provider object and 28 | /// the created object are the same. 29 | class DeferredInheritedProvider extends InheritedProvider { 30 | /// Lazily create an object automatically disposed when 31 | /// [DeferredInheritedProvider] is removed from the tree. 32 | /// 33 | /// The object create will be listened using `startListening`, and its content 34 | /// will be exposed to `child` and its descendants. 35 | DeferredInheritedProvider({ 36 | Key? key, 37 | required Create create, 38 | Dispose? dispose, 39 | required DeferredStartListening startListening, 40 | UpdateShouldNotify? updateShouldNotify, 41 | bool? lazy, 42 | TransitionBuilder? builder, 43 | Widget? child, 44 | }) : super._constructor( 45 | key: key, 46 | child: child, 47 | lazy: lazy, 48 | builder: builder, 49 | delegate: _CreateDeferredInheritedProvider( 50 | create: create, 51 | dispose: dispose, 52 | updateShouldNotify: updateShouldNotify, 53 | startListening: startListening, 54 | ), 55 | ); 56 | 57 | /// Listens to `value` and expose its content to `child` and its descendants. 58 | DeferredInheritedProvider.value({ 59 | Key? key, 60 | required T value, 61 | required DeferredStartListening startListening, 62 | UpdateShouldNotify? updateShouldNotify, 63 | bool? lazy, 64 | TransitionBuilder? builder, 65 | Widget? child, 66 | }) : super._constructor( 67 | key: key, 68 | lazy: lazy, 69 | builder: builder, 70 | delegate: _ValueDeferredInheritedProvider( 71 | value, 72 | updateShouldNotify, 73 | startListening, 74 | ), 75 | child: child, 76 | ); 77 | } 78 | 79 | abstract class _DeferredDelegate extends _Delegate { 80 | _DeferredDelegate(this.updateShouldNotify, this.startListening); 81 | 82 | final UpdateShouldNotify? updateShouldNotify; 83 | final DeferredStartListening startListening; 84 | 85 | @override 86 | _DeferredDelegateState> createState(); 87 | } 88 | 89 | abstract class _DeferredDelegateState> 90 | extends _DelegateState { 91 | VoidCallback? _removeListener; 92 | 93 | T get controller; 94 | 95 | R? _value; 96 | 97 | @override 98 | R get value { 99 | // setState should be no-op inside startListening, as it's lazy-loaded 100 | // otherwise Flutter will throw an exception for no reason. 101 | element!._isNotifyDependentsEnabled = false; 102 | _removeListener ??= delegate.startListening( 103 | element!, 104 | setState, 105 | controller, 106 | _value, 107 | ); 108 | element!._isNotifyDependentsEnabled = true; 109 | assert(element!.hasValue, ''' 110 | The callback "startListening" was called, but it left DeferredInhertitedProviderElement<$T, $R> 111 | in an uninitialized state. 112 | 113 | It is necessary for "startListening" to call "setState" at least once the very 114 | first time "value" is requested. 115 | 116 | To fix, consider: 117 | 118 | DeferredInheritedProvider( 119 | ..., 120 | startListening: (element, setState, controller, value) { 121 | if (!element.hasValue) { 122 | setState(myInitialValue); // TODO replace myInitialValue with your own 123 | } 124 | ... 125 | } 126 | ) 127 | '''); 128 | assert(_removeListener != null); 129 | return _value as R; 130 | } 131 | 132 | @override 133 | void dispose() { 134 | super.dispose(); 135 | _removeListener?.call(); 136 | } 137 | 138 | bool get isLoaded => _removeListener != null; 139 | 140 | bool _hasValue = false; 141 | 142 | @override 143 | bool get hasValue => _hasValue; 144 | 145 | void setState(R value) { 146 | if (_hasValue) { 147 | final shouldNotify = delegate.updateShouldNotify != null 148 | ? delegate.updateShouldNotify!(_value as R, value) 149 | : _value != value; 150 | if (shouldNotify) { 151 | element!.markNeedsNotifyDependents(); 152 | } 153 | } 154 | _hasValue = true; 155 | _value = value; 156 | } 157 | } 158 | 159 | class _CreateDeferredInheritedProvider extends _DeferredDelegate { 160 | _CreateDeferredInheritedProvider({ 161 | required this.create, 162 | this.dispose, 163 | UpdateShouldNotify? updateShouldNotify, 164 | required DeferredStartListening startListening, 165 | }) : super(updateShouldNotify, startListening); 166 | 167 | final Create create; 168 | final Dispose? dispose; 169 | 170 | @override 171 | _CreateDeferredInheritedProviderElement createState() { 172 | return _CreateDeferredInheritedProviderElement(); 173 | } 174 | } 175 | 176 | class _CreateDeferredInheritedProviderElement 177 | extends _DeferredDelegateState> { 179 | bool _didBuild = false; 180 | 181 | T? _controller; 182 | 183 | @override 184 | T get controller { 185 | if (!_didBuild) { 186 | assert(debugSetInheritedLock(true)); 187 | bool? _debugPreviousIsInInheritedProviderCreate; 188 | bool? _debugPreviousIsInInheritedProviderUpdate; 189 | 190 | assert(() { 191 | _debugPreviousIsInInheritedProviderCreate = 192 | debugIsInInheritedProviderCreate; 193 | _debugPreviousIsInInheritedProviderUpdate = 194 | debugIsInInheritedProviderUpdate; 195 | return true; 196 | }()); 197 | 198 | try { 199 | assert(() { 200 | debugIsInInheritedProviderCreate = true; 201 | debugIsInInheritedProviderUpdate = false; 202 | return true; 203 | }()); 204 | _controller = delegate.create(element!); 205 | } finally { 206 | assert(() { 207 | debugIsInInheritedProviderCreate = 208 | _debugPreviousIsInInheritedProviderCreate!; 209 | debugIsInInheritedProviderUpdate = 210 | _debugPreviousIsInInheritedProviderUpdate!; 211 | return true; 212 | }()); 213 | } 214 | _didBuild = true; 215 | } 216 | return _controller as T; 217 | } 218 | 219 | @override 220 | void dispose() { 221 | super.dispose(); 222 | if (_didBuild) { 223 | delegate.dispose?.call(element!, _controller as T); 224 | } 225 | } 226 | 227 | @override 228 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 229 | super.debugFillProperties(properties); 230 | if (isLoaded) { 231 | properties 232 | ..add(DiagnosticsProperty('controller', controller)) 233 | ..add(DiagnosticsProperty('value', value)); 234 | } else { 235 | properties 236 | ..add( 237 | FlagProperty( 238 | 'controller', 239 | value: true, 240 | showName: true, 241 | ifTrue: '', 242 | ), 243 | ) 244 | ..add( 245 | FlagProperty( 246 | 'value', 247 | value: true, 248 | showName: true, 249 | ifTrue: '', 250 | ), 251 | ); 252 | } 253 | } 254 | } 255 | 256 | class _ValueDeferredInheritedProvider extends _DeferredDelegate { 257 | _ValueDeferredInheritedProvider( 258 | this.value, 259 | UpdateShouldNotify? updateShouldNotify, 260 | DeferredStartListening startListening, 261 | ) : super(updateShouldNotify, startListening); 262 | 263 | final T value; 264 | 265 | @override 266 | _ValueDeferredInheritedProviderState createState() { 267 | return _ValueDeferredInheritedProviderState(); 268 | } 269 | 270 | @override 271 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 272 | super.debugFillProperties(properties); 273 | properties.add(DiagnosticsProperty('controller', value)); 274 | } 275 | } 276 | 277 | class _ValueDeferredInheritedProviderState extends _DeferredDelegateState< 278 | T, R, _ValueDeferredInheritedProvider> { 279 | @override 280 | bool willUpdateDelegate(_ValueDeferredInheritedProvider oldDelegate) { 281 | if (delegate.value != oldDelegate.value) { 282 | if (_removeListener != null) { 283 | _removeListener!(); 284 | _removeListener = null; 285 | } 286 | return true; 287 | } 288 | return false; 289 | } 290 | 291 | @override 292 | T get controller => delegate.value; 293 | 294 | @override 295 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 296 | super.debugFillProperties(properties); 297 | if (_removeListener != null) { 298 | properties.add(DiagnosticsProperty('value', value)); 299 | } else { 300 | properties.add( 301 | FlagProperty( 302 | 'value', 303 | value: true, 304 | showName: true, 305 | ifTrue: '', 306 | ), 307 | ); 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /packages/provider/lib/src/devtool.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs 2 | 3 | part of 'provider.dart'; 4 | 5 | void Function( 6 | String eventKind, 7 | Map event, 8 | )? _debugPostEventOverride; 9 | 10 | void debugPostEvent( 11 | String eventKind, [ 12 | Map event = const {}, 13 | ]) { 14 | if (_debugPostEventOverride != null) { 15 | _debugPostEventOverride!(eventKind, event); 16 | } else { 17 | developer.postEvent(eventKind, event); 18 | } 19 | } 20 | 21 | PostEventSpy spyPostEvent() { 22 | assert(_debugPostEventOverride == null, 'postEvent is already spied'); 23 | 24 | final spy = PostEventSpy._(); 25 | _debugPostEventOverride = spy._postEvent; 26 | return spy; 27 | } 28 | 29 | @protected 30 | class PostEventCall { 31 | PostEventCall._(this.eventKind, this.event); 32 | final String eventKind; 33 | final Map event; 34 | } 35 | 36 | @protected 37 | class PostEventSpy { 38 | PostEventSpy._(); 39 | final logs = []; 40 | 41 | void dispose() { 42 | assert( 43 | _debugPostEventOverride == _postEvent, 44 | 'disposed a spy different from the current spy', 45 | ); 46 | _debugPostEventOverride = null; 47 | } 48 | 49 | void _postEvent( 50 | String eventKind, 51 | Map event, 52 | ) { 53 | logs.add(PostEventCall._(eventKind, event)); 54 | } 55 | } 56 | 57 | @immutable 58 | class ProviderNode { 59 | const ProviderNode({ 60 | required this.id, 61 | required this.childrenNodeIds, 62 | required this.type, 63 | required _InheritedProviderScopeElement element, 64 | }) : _element = element; 65 | 66 | final String id; 67 | final String type; 68 | final List childrenNodeIds; 69 | final _InheritedProviderScopeElement _element; 70 | 71 | Object? get value => _element._delegateState.value; 72 | } 73 | 74 | @protected 75 | class ProviderBinding { 76 | ProviderBinding._(); 77 | 78 | static final debugInstance = kDebugMode 79 | ? ProviderBinding._() 80 | : throw UnsupportedError('Cannot use ProviderBinding in release mode'); 81 | 82 | Map _providerDetails = {}; 83 | Map get providerDetails => _providerDetails; 84 | set providerDetails(Map value) { 85 | debugPostEvent('provider:provider_list_changed', {}); 86 | _providerDetails = value; 87 | } 88 | 89 | void providerDidChange(String providerId) { 90 | debugPostEvent( 91 | 'provider:provider_changed', 92 | {'id': providerId}, 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/provider/lib/src/listenable_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'change_notifier_provider.dart' 4 | show ChangeNotifierProvider, ChangeNotifierProxyProvider; 5 | import 'provider.dart'; 6 | import 'proxy_provider.dart'; 7 | 8 | /// Listens to a [Listenable], expose it to its descendants and rebuilds 9 | /// dependents whenever the listener emits an event. 10 | /// 11 | /// For usage information, see [ChangeNotifierProvider], a subclass of 12 | /// [ListenableProvider] made for [ChangeNotifier]. 13 | /// 14 | /// You will generally want to use [ChangeNotifierProvider] instead. 15 | /// But [ListenableProvider] is available in case you want to implement 16 | /// [Listenable] yourself, or use [Animation]. 17 | class ListenableProvider extends InheritedProvider { 18 | /// Creates a [Listenable] using [create] and subscribes to it. 19 | /// 20 | /// [dispose] can optionally passed to free resources 21 | /// when [ListenableProvider] is removed from the tree. 22 | /// 23 | /// [create] must not be `null`. 24 | ListenableProvider({ 25 | Key? key, 26 | required Create create, 27 | Dispose? dispose, 28 | bool? lazy, 29 | TransitionBuilder? builder, 30 | Widget? child, 31 | }) : super( 32 | key: key, 33 | startListening: _startListening, 34 | create: create, 35 | dispose: dispose, 36 | lazy: lazy, 37 | builder: builder, 38 | child: child, 39 | ); 40 | 41 | /// Provides an existing [Listenable]. 42 | ListenableProvider.value({ 43 | Key? key, 44 | required T value, 45 | UpdateShouldNotify? updateShouldNotify, 46 | TransitionBuilder? builder, 47 | Widget? child, 48 | }) : super.value( 49 | key: key, 50 | builder: builder, 51 | value: value, 52 | updateShouldNotify: updateShouldNotify, 53 | startListening: _startListening, 54 | child: child, 55 | ); 56 | 57 | static VoidCallback _startListening( 58 | InheritedContext e, 59 | Listenable? value, 60 | ) { 61 | value?.addListener(e.markNeedsNotifyDependents); 62 | return () => value?.removeListener(e.markNeedsNotifyDependents); 63 | } 64 | } 65 | 66 | /// {@macro provider.listenableproxyprovider} 67 | class ListenableProxyProvider0 68 | extends InheritedProvider { 69 | /// Initializes [key] for subclasses. 70 | ListenableProxyProvider0({ 71 | Key? key, 72 | Create? create, 73 | required R Function(BuildContext, R? previous) update, 74 | Dispose? dispose, 75 | UpdateShouldNotify? updateShouldNotify, 76 | bool? lazy, 77 | TransitionBuilder? builder, 78 | Widget? child, 79 | }) : super( 80 | key: key, 81 | create: create, 82 | update: update, 83 | lazy: lazy, 84 | builder: builder, 85 | dispose: dispose, 86 | updateShouldNotify: updateShouldNotify, 87 | startListening: ListenableProvider._startListening, 88 | child: child, 89 | ); 90 | } 91 | 92 | /// {@template provider.listenableproxyprovider} 93 | /// A variation of [ListenableProvider] that builds its value from 94 | /// values obtained from other providers. 95 | /// 96 | /// See the discussion on [ChangeNotifierProxyProvider] for a complete 97 | /// explanation on how to use it. 98 | /// 99 | /// [ChangeNotifierProxyProvider] extends [ListenableProxyProvider] to make it 100 | /// work with [ChangeNotifier], but the behavior stays the same. 101 | /// Most of the time you'll want to use [ChangeNotifierProxyProvider] instead. 102 | /// But [ListenableProxyProvider] is exposed in case someone wants to use a 103 | /// [Listenable] implementation other than [ChangeNotifier], such as 104 | /// [Animation]. 105 | /// {@endtemplate} 106 | class ListenableProxyProvider 107 | extends ListenableProxyProvider0 { 108 | /// Initializes [key] for subclasses. 109 | ListenableProxyProvider({ 110 | Key? key, 111 | Create? create, 112 | required ProxyProviderBuilder update, 113 | Dispose? dispose, 114 | bool? lazy, 115 | TransitionBuilder? builder, 116 | Widget? child, 117 | }) : super( 118 | key: key, 119 | create: create, 120 | lazy: lazy, 121 | builder: builder, 122 | update: (context, previous) => update( 123 | context, 124 | Provider.of(context), 125 | previous, 126 | ), 127 | dispose: dispose, 128 | child: child, 129 | ); 130 | } 131 | 132 | /// {@macro provider.listenableproxyprovider} 133 | class ListenableProxyProvider2 134 | extends ListenableProxyProvider0 { 135 | /// Initializes [key] for subclasses. 136 | ListenableProxyProvider2({ 137 | Key? key, 138 | Create? create, 139 | required ProxyProviderBuilder2 update, 140 | Dispose? dispose, 141 | bool? lazy, 142 | TransitionBuilder? builder, 143 | Widget? child, 144 | }) : super( 145 | key: key, 146 | create: create, 147 | lazy: lazy, 148 | builder: builder, 149 | update: (context, previous) => update( 150 | context, 151 | Provider.of(context), 152 | Provider.of(context), 153 | previous, 154 | ), 155 | dispose: dispose, 156 | child: child, 157 | ); 158 | } 159 | 160 | /// {@macro provider.listenableproxyprovider} 161 | class ListenableProxyProvider3 162 | extends ListenableProxyProvider0 { 163 | /// Initializes [key] for subclasses. 164 | ListenableProxyProvider3({ 165 | Key? key, 166 | Create? create, 167 | required ProxyProviderBuilder3 update, 168 | Dispose? dispose, 169 | bool? lazy, 170 | TransitionBuilder? builder, 171 | Widget? child, 172 | }) : super( 173 | key: key, 174 | create: create, 175 | lazy: lazy, 176 | builder: builder, 177 | update: (context, previous) => update( 178 | context, 179 | Provider.of(context), 180 | Provider.of(context), 181 | Provider.of(context), 182 | previous, 183 | ), 184 | dispose: dispose, 185 | child: child, 186 | ); 187 | } 188 | 189 | /// {@macro provider.listenableproxyprovider} 190 | class ListenableProxyProvider4 191 | extends ListenableProxyProvider0 { 192 | /// Initializes [key] for subclasses. 193 | ListenableProxyProvider4({ 194 | Key? key, 195 | Create? create, 196 | required ProxyProviderBuilder4 update, 197 | Dispose? dispose, 198 | bool? lazy, 199 | TransitionBuilder? builder, 200 | Widget? child, 201 | }) : super( 202 | key: key, 203 | create: create, 204 | lazy: lazy, 205 | builder: builder, 206 | update: (context, previous) => update( 207 | context, 208 | Provider.of(context), 209 | Provider.of(context), 210 | Provider.of(context), 211 | Provider.of(context), 212 | previous, 213 | ), 214 | dispose: dispose, 215 | child: child, 216 | ); 217 | } 218 | 219 | /// {@macro provider.listenableproxyprovider} 220 | class ListenableProxyProvider5 221 | extends ListenableProxyProvider0 { 222 | /// Initializes [key] for subclasses. 223 | ListenableProxyProvider5({ 224 | Key? key, 225 | Create? create, 226 | required ProxyProviderBuilder5 update, 227 | Dispose? dispose, 228 | bool? lazy, 229 | TransitionBuilder? builder, 230 | Widget? child, 231 | }) : super( 232 | key: key, 233 | create: create, 234 | lazy: lazy, 235 | builder: builder, 236 | update: (context, previous) => update( 237 | context, 238 | Provider.of(context), 239 | Provider.of(context), 240 | Provider.of(context), 241 | Provider.of(context), 242 | Provider.of(context), 243 | previous, 244 | ), 245 | dispose: dispose, 246 | child: child, 247 | ); 248 | } 249 | 250 | /// {@macro provider.listenableproxyprovider} 251 | class ListenableProxyProvider6 252 | extends ListenableProxyProvider0 { 253 | /// Initializes [key] for subclasses. 254 | ListenableProxyProvider6({ 255 | Key? key, 256 | Create? create, 257 | required ProxyProviderBuilder6 update, 258 | Dispose? dispose, 259 | bool? lazy, 260 | TransitionBuilder? builder, 261 | Widget? child, 262 | }) : super( 263 | key: key, 264 | create: create, 265 | lazy: lazy, 266 | builder: builder, 267 | update: (context, previous) => update( 268 | context, 269 | Provider.of(context), 270 | Provider.of(context), 271 | Provider.of(context), 272 | Provider.of(context), 273 | Provider.of(context), 274 | Provider.of(context), 275 | previous, 276 | ), 277 | dispose: dispose, 278 | child: child, 279 | ); 280 | } 281 | -------------------------------------------------------------------------------- /packages/provider/lib/src/proxy_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'provider.dart'; 5 | 6 | // ignore: public_member_api_docs 7 | typedef ProviderBuilder = Widget Function( 8 | BuildContext context, 9 | R value, 10 | Widget child, 11 | ); 12 | 13 | // ignore: public_member_api_docs 14 | typedef ProxyProviderBuilder = R Function( 15 | BuildContext context, 16 | T value, 17 | R? previous, 18 | ); 19 | 20 | // ignore: public_member_api_docs 21 | typedef ProxyProviderBuilder2 = R Function( 22 | BuildContext context, 23 | T value, 24 | T2 value2, 25 | R? previous, 26 | ); 27 | 28 | // ignore: public_member_api_docs 29 | typedef ProxyProviderBuilder3 = R Function( 30 | BuildContext context, 31 | T value, 32 | T2 value2, 33 | T3 value3, 34 | R? previous, 35 | ); 36 | 37 | // ignore: public_member_api_docs 38 | typedef ProxyProviderBuilder4 = R Function( 39 | BuildContext context, 40 | T value, 41 | T2 value2, 42 | T3 value3, 43 | T4 value4, 44 | R? previous, 45 | ); 46 | 47 | // ignore: public_member_api_docs 48 | typedef ProxyProviderBuilder5 = R Function( 49 | BuildContext context, 50 | T value, 51 | T2 value2, 52 | T3 value3, 53 | T4 value4, 54 | T5 value5, 55 | R? previous, 56 | ); 57 | 58 | // ignore: public_member_api_docs 59 | typedef ProxyProviderBuilder6 = R Function( 60 | BuildContext context, 61 | T value, 62 | T2 value2, 63 | T3 value3, 64 | T4 value4, 65 | T5 value5, 66 | T6 value6, 67 | R? previous, 68 | ); 69 | 70 | /// {@macro provider.proxyprovider} 71 | class ProxyProvider0 extends InheritedProvider { 72 | /// Initializes [key] for subclasses. 73 | ProxyProvider0({ 74 | Key? key, 75 | Create? create, 76 | required R Function(BuildContext context, R? value) update, 77 | UpdateShouldNotify? updateShouldNotify, 78 | Dispose? dispose, 79 | bool? lazy, 80 | TransitionBuilder? builder, 81 | Widget? child, 82 | }) : super( 83 | key: key, 84 | lazy: lazy, 85 | builder: builder, 86 | create: create, 87 | update: update, 88 | dispose: dispose, 89 | updateShouldNotify: updateShouldNotify, 90 | debugCheckInvalidValueType: kReleaseMode 91 | ? null 92 | : (R value) => 93 | Provider.debugCheckInvalidValueType?.call(value), 94 | child: child, 95 | ); 96 | } 97 | 98 | /// {@template provider.proxyprovider} 99 | /// A provider that builds a value based on other providers. 100 | /// 101 | /// The exposed value is built through either `create` or `update`, then passed 102 | /// to [InheritedProvider]. 103 | /// 104 | /// As opposed to `create`, `update` may be called more than once. 105 | /// It will be called once the first time the value is obtained, then once 106 | /// whenever [ProxyProvider] rebuilds or when one of the providers it depends on 107 | /// updates. 108 | /// 109 | /// [ProxyProvider] comes in different variants such as [ProxyProvider2]. This 110 | /// is syntax sugar on the top of [ProxyProvider0]. 111 | /// 112 | /// As such, `ProxyProvider` is equal to: 113 | /// ```dart 114 | /// ProxyProvider0( 115 | /// update: (context, result) { 116 | /// final a = Provider.of(context); 117 | /// return update(context, a, result); 118 | /// } 119 | /// ); 120 | /// ``` 121 | /// 122 | /// Whereas `ProxyProvider2` is equal to: 123 | /// ```dart 124 | /// ProxyProvider0( 125 | /// update: (context, result) { 126 | /// final a = Provider.of(context); 127 | /// final b = Provider.of(context); 128 | /// return update(context, a, b, result); 129 | /// } 130 | /// ); 131 | /// ``` 132 | /// 133 | /// This last parameter of `update` is the last value returned by either 134 | /// `create` or `update`. 135 | /// It is `null` by default. 136 | /// 137 | /// `update` must not be `null`. 138 | /// 139 | /// See also: 140 | /// 141 | /// * [Provider], which matches the behavior of [ProxyProvider] but has only 142 | /// a `create` callback. 143 | /// {@endtemplate} 144 | class ProxyProvider extends ProxyProvider0 { 145 | /// Initializes [key] for subclasses. 146 | ProxyProvider({ 147 | Key? key, 148 | Create? create, 149 | required ProxyProviderBuilder update, 150 | UpdateShouldNotify? updateShouldNotify, 151 | Dispose? dispose, 152 | bool? lazy, 153 | TransitionBuilder? builder, 154 | Widget? child, 155 | }) : super( 156 | key: key, 157 | lazy: lazy, 158 | builder: builder, 159 | create: create, 160 | update: (context, value) => update( 161 | context, 162 | Provider.of(context), 163 | value, 164 | ), 165 | updateShouldNotify: updateShouldNotify, 166 | dispose: dispose, 167 | child: child, 168 | ); 169 | } 170 | 171 | /// {@macro provider.proxyprovider} 172 | class ProxyProvider2 extends ProxyProvider0 { 173 | /// Initializes [key] for subclasses. 174 | ProxyProvider2({ 175 | Key? key, 176 | Create? create, 177 | required ProxyProviderBuilder2 update, 178 | UpdateShouldNotify? updateShouldNotify, 179 | Dispose? dispose, 180 | bool? lazy, 181 | TransitionBuilder? builder, 182 | Widget? child, 183 | }) : super( 184 | key: key, 185 | lazy: lazy, 186 | builder: builder, 187 | create: create, 188 | update: (context, value) => update( 189 | context, 190 | Provider.of(context), 191 | Provider.of(context), 192 | value, 193 | ), 194 | updateShouldNotify: updateShouldNotify, 195 | dispose: dispose, 196 | child: child, 197 | ); 198 | } 199 | 200 | /// {@macro provider.proxyprovider} 201 | class ProxyProvider3 extends ProxyProvider0 { 202 | /// Initializes [key] for subclasses. 203 | ProxyProvider3({ 204 | Key? key, 205 | Create? create, 206 | required ProxyProviderBuilder3 update, 207 | UpdateShouldNotify? updateShouldNotify, 208 | Dispose? dispose, 209 | bool? lazy, 210 | TransitionBuilder? builder, 211 | Widget? child, 212 | }) : super( 213 | key: key, 214 | lazy: lazy, 215 | builder: builder, 216 | create: create, 217 | update: (context, value) => update( 218 | context, 219 | Provider.of(context), 220 | Provider.of(context), 221 | Provider.of(context), 222 | value, 223 | ), 224 | updateShouldNotify: updateShouldNotify, 225 | dispose: dispose, 226 | child: child, 227 | ); 228 | } 229 | 230 | /// {@macro provider.proxyprovider} 231 | class ProxyProvider4 extends ProxyProvider0 { 232 | /// Initializes [key] for subclasses. 233 | ProxyProvider4({ 234 | Key? key, 235 | Create? create, 236 | required ProxyProviderBuilder4 update, 237 | UpdateShouldNotify? updateShouldNotify, 238 | Dispose? dispose, 239 | bool? lazy, 240 | TransitionBuilder? builder, 241 | Widget? child, 242 | }) : super( 243 | key: key, 244 | lazy: lazy, 245 | builder: builder, 246 | create: create, 247 | update: (context, value) => update( 248 | context, 249 | Provider.of(context), 250 | Provider.of(context), 251 | Provider.of(context), 252 | Provider.of(context), 253 | value, 254 | ), 255 | updateShouldNotify: updateShouldNotify, 256 | dispose: dispose, 257 | child: child, 258 | ); 259 | } 260 | 261 | /// {@macro provider.proxyprovider} 262 | class ProxyProvider5 extends ProxyProvider0 { 263 | /// Initializes [key] for subclasses. 264 | ProxyProvider5({ 265 | Key? key, 266 | Create? create, 267 | required ProxyProviderBuilder5 update, 268 | UpdateShouldNotify? updateShouldNotify, 269 | Dispose? dispose, 270 | bool? lazy, 271 | TransitionBuilder? builder, 272 | Widget? child, 273 | }) : super( 274 | key: key, 275 | lazy: lazy, 276 | builder: builder, 277 | create: create, 278 | update: (context, value) => update( 279 | context, 280 | Provider.of(context), 281 | Provider.of(context), 282 | Provider.of(context), 283 | Provider.of(context), 284 | Provider.of(context), 285 | value, 286 | ), 287 | updateShouldNotify: updateShouldNotify, 288 | dispose: dispose, 289 | child: child, 290 | ); 291 | } 292 | 293 | /// {@macro provider.proxyprovider} 294 | class ProxyProvider6 extends ProxyProvider0 { 295 | /// Initializes [key] for subclasses. 296 | ProxyProvider6({ 297 | Key? key, 298 | Create? create, 299 | required ProxyProviderBuilder6 update, 300 | UpdateShouldNotify? updateShouldNotify, 301 | Dispose? dispose, 302 | bool? lazy, 303 | TransitionBuilder? builder, 304 | Widget? child, 305 | }) : super( 306 | key: key, 307 | lazy: lazy, 308 | builder: builder, 309 | create: create, 310 | update: (context, value) => update( 311 | context, 312 | Provider.of(context), 313 | Provider.of(context), 314 | Provider.of(context), 315 | Provider.of(context), 316 | Provider.of(context), 317 | Provider.of(context), 318 | value, 319 | ), 320 | updateShouldNotify: updateShouldNotify, 321 | dispose: dispose, 322 | child: child, 323 | ); 324 | } 325 | -------------------------------------------------------------------------------- /packages/provider/lib/src/reassemble_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart' show BindingBase; 2 | import 'package:flutter/widgets.dart' show Element; 3 | 4 | /// If you need your provider to be notified when 'Hot Reload' occurs, 5 | /// use this class 6 | /// 7 | /// ```dart 8 | /// class MyChangeNotifier extends ChangeNotifier with ReassembleHandler {} 9 | /// ``` 10 | // ignore: one_member_abstracts 11 | abstract class ReassembleHandler { 12 | /// Called when 'Hot Reload' occurs 13 | /// 14 | /// See also: 15 | /// 16 | /// * [Element.reassemble] 17 | /// * [BindingBase.reassembleApplication] 18 | void reassemble(); 19 | } 20 | -------------------------------------------------------------------------------- /packages/provider/lib/src/selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:nested/nested.dart'; 5 | 6 | import 'consumer.dart'; 7 | import 'provider.dart'; 8 | 9 | /// Used by providers to determine whether dependents needs to be updated 10 | /// when the value exposed changes 11 | typedef ShouldRebuild = bool Function(T previous, T next); 12 | 13 | /// A base class for custom [Selector]. 14 | /// 15 | /// It works with any [InheritedWidget]. Variants like [Selector] and 16 | /// [Selector6] are just syntax sugar to use [Selector0] with [Provider.of]. 17 | /// 18 | /// But it will **not** work with values 19 | /// coming from anything but [InheritedWidget]. 20 | /// 21 | /// As such, the following: 22 | /// 23 | /// ```dart 24 | /// T value; 25 | /// 26 | /// return Selector0( 27 | /// selector: (_) => value, 28 | /// builder: ..., 29 | /// ) 30 | /// ``` 31 | /// 32 | /// will still call `builder` again, even if `value` didn't change. 33 | class Selector0 extends SingleChildStatefulWidget { 34 | /// Both `builder` and `selector` must not be `null`. 35 | Selector0({ 36 | Key? key, 37 | required this.builder, 38 | required this.selector, 39 | ShouldRebuild? shouldRebuild, 40 | Widget? child, 41 | }) : _shouldRebuild = shouldRebuild, 42 | super(key: key, child: child); 43 | 44 | /// A function that builds a widget tree from `child` and the last result of 45 | /// [selector]. 46 | /// 47 | /// [builder] will be called again whenever the its parent widget asks for an 48 | /// update, or if [selector] return a value that is different from the 49 | /// previous one using [operator==]. 50 | /// 51 | /// Must not be `null`. 52 | final ValueWidgetBuilder builder; 53 | 54 | /// A function that obtains some [InheritedWidget] and map their content into 55 | /// a new object with only a limited number of properties. 56 | /// 57 | /// The returned object must implement [operator==]. 58 | /// 59 | /// Must not be `null` 60 | final T Function(BuildContext) selector; 61 | 62 | final ShouldRebuild? _shouldRebuild; 63 | 64 | @override 65 | _Selector0State createState() => _Selector0State(); 66 | } 67 | 68 | class _Selector0State extends SingleChildState> { 69 | T? value; 70 | Widget? cache; 71 | Widget? oldWidget; 72 | 73 | @override 74 | Widget buildWithChild(BuildContext context, Widget? child) { 75 | final selected = widget.selector(context); 76 | 77 | final shouldInvalidateCache = oldWidget != widget || 78 | (widget._shouldRebuild != null && 79 | widget._shouldRebuild!(value as T, selected)) || 80 | (widget._shouldRebuild == null && 81 | !const DeepCollectionEquality().equals(value, selected)); 82 | if (shouldInvalidateCache) { 83 | value = selected; 84 | oldWidget = widget; 85 | cache = Builder( 86 | builder: (context) => widget.builder( 87 | context, 88 | selected, 89 | child, 90 | ), 91 | ); 92 | } 93 | return cache!; 94 | } 95 | 96 | @override 97 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 98 | super.debugFillProperties(properties); 99 | properties.add(DiagnosticsProperty('value', value)); 100 | } 101 | } 102 | 103 | /// {@template provider.selector} 104 | /// An equivalent to [Consumer] that can filter updates by selecting a limited 105 | /// amount of values and prevent rebuild if they don't change. 106 | /// 107 | /// [Selector] will obtain a value using [Provider.of], then pass that value 108 | /// to `selector`. That `selector` callback is then tasked to return an object 109 | /// that contains only the information needed for `builder` to complete. 110 | /// 111 | /// By default, [Selector] determines if `builder` needs to be called again 112 | /// by comparing the previous and new result of `selector` using 113 | /// [DeepCollectionEquality] from the package `collection`. 114 | /// 115 | /// This behavior can be overridden by passing a custom `shouldRebuild` callback. 116 | /// 117 | /// **NOTE**: 118 | /// The selected value must be immutable, or otherwise [Selector] may think 119 | /// nothing changed and not call `builder` again. 120 | /// 121 | /// As such, it `selector` should return either a collection ([List]/[Map]/[Set]/[Iterable]) 122 | /// or a class that override `==`. 123 | /// 124 | /// Here's an example: 125 | /// 126 | /// Example 1: 127 | /// 128 | ///```dart 129 | /// Selector( 130 | /// selector: (_, foo) => foo.bar, // will rebuild only when `bar` changes 131 | /// builder: (_, data, __) { 132 | /// return Text('${data.item}'); 133 | /// } 134 | /// ) 135 | ///``` 136 | ///In this example `builder` will be called only when `foo.bar` changes. 137 | /// 138 | /// Example 2: 139 | /// 140 | /// To select multiple values without having to write a class that implements `==`, 141 | /// the easiest solution is to use "Records," available from Dart version 3.0. 142 | /// For more information on Records, refer to the [records](https://dart.dev/language/records). 143 | /// 144 | /// ```dart 145 | /// Selector( 146 | /// selector: (_, foo) => (item1: foo.item1, item2: foo.item2), 147 | /// builder: (_, data, __) { 148 | /// return Text('${data.item1} ${data.item2}'); 149 | /// }, 150 | /// ); 151 | /// ``` 152 | /// 153 | /// In this example, `builder` will be called again only if `foo.item1` or 154 | /// `foo.item2` changes. 155 | /// 156 | /// For generic usage information, see [Consumer]. 157 | /// {@endtemplate} 158 | class Selector extends Selector0 { 159 | /// {@macro provider.selector} 160 | Selector({ 161 | Key? key, 162 | required ValueWidgetBuilder builder, 163 | required S Function(BuildContext, A) selector, 164 | ShouldRebuild? shouldRebuild, 165 | Widget? child, 166 | }) : super( 167 | key: key, 168 | shouldRebuild: shouldRebuild, 169 | builder: builder, 170 | selector: (context) => selector(context, Provider.of(context)), 171 | child: child, 172 | ); 173 | } 174 | 175 | /// {@macro provider.selector} 176 | class Selector2 extends Selector0 { 177 | /// {@macro provider.selector} 178 | Selector2({ 179 | Key? key, 180 | required ValueWidgetBuilder builder, 181 | required S Function(BuildContext, A, B) selector, 182 | ShouldRebuild? shouldRebuild, 183 | Widget? child, 184 | }) : super( 185 | key: key, 186 | shouldRebuild: shouldRebuild, 187 | builder: builder, 188 | selector: (context) => selector( 189 | context, 190 | Provider.of(context), 191 | Provider.of(context), 192 | ), 193 | child: child, 194 | ); 195 | } 196 | 197 | /// {@macro provider.selector} 198 | class Selector3 extends Selector0 { 199 | /// {@macro provider.selector} 200 | Selector3({ 201 | Key? key, 202 | required ValueWidgetBuilder builder, 203 | required S Function(BuildContext, A, B, C) selector, 204 | ShouldRebuild? shouldRebuild, 205 | Widget? child, 206 | }) : super( 207 | key: key, 208 | shouldRebuild: shouldRebuild, 209 | builder: builder, 210 | selector: (context) => selector( 211 | context, 212 | Provider.of(context), 213 | Provider.of(context), 214 | Provider.of(context), 215 | ), 216 | child: child, 217 | ); 218 | } 219 | 220 | /// {@macro provider.selector} 221 | class Selector4 extends Selector0 { 222 | /// {@macro provider.selector} 223 | Selector4({ 224 | Key? key, 225 | required ValueWidgetBuilder builder, 226 | required S Function(BuildContext, A, B, C, D) selector, 227 | ShouldRebuild? shouldRebuild, 228 | Widget? child, 229 | }) : super( 230 | key: key, 231 | shouldRebuild: shouldRebuild, 232 | builder: builder, 233 | selector: (context) => selector( 234 | context, 235 | Provider.of(context), 236 | Provider.of(context), 237 | Provider.of(context), 238 | Provider.of(context), 239 | ), 240 | child: child, 241 | ); 242 | } 243 | 244 | /// {@macro provider.selector} 245 | class Selector5 extends Selector0 { 246 | /// {@macro provider.selector} 247 | Selector5({ 248 | Key? key, 249 | required ValueWidgetBuilder builder, 250 | required S Function(BuildContext, A, B, C, D, E) selector, 251 | ShouldRebuild? shouldRebuild, 252 | Widget? child, 253 | }) : super( 254 | key: key, 255 | shouldRebuild: shouldRebuild, 256 | builder: builder, 257 | selector: (context) => selector( 258 | context, 259 | Provider.of(context), 260 | Provider.of(context), 261 | Provider.of(context), 262 | Provider.of(context), 263 | Provider.of(context), 264 | ), 265 | child: child, 266 | ); 267 | } 268 | 269 | /// {@macro provider.selector} 270 | class Selector6 extends Selector0 { 271 | /// {@macro provider.selector} 272 | Selector6({ 273 | Key? key, 274 | required ValueWidgetBuilder builder, 275 | required S Function(BuildContext, A, B, C, D, E, F) selector, 276 | ShouldRebuild? shouldRebuild, 277 | Widget? child, 278 | }) : super( 279 | key: key, 280 | shouldRebuild: shouldRebuild, 281 | builder: builder, 282 | selector: (context) => selector( 283 | context, 284 | Provider.of(context), 285 | Provider.of(context), 286 | Provider.of(context), 287 | Provider.of(context), 288 | Provider.of(context), 289 | Provider.of(context), 290 | ), 291 | child: child, 292 | ); 293 | } 294 | -------------------------------------------------------------------------------- /packages/provider/lib/src/value_listenable_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:nested/nested.dart'; 4 | 5 | import 'provider.dart'; 6 | 7 | /// {@macro provider.valuelistenableprovider} 8 | class ValueListenableProvider extends SingleChildStatelessWidget { 9 | /// {@template provider.valuelistenableprovider} 10 | /// Listens to a [ValueListenable] and exposes its current value. 11 | /// 12 | /// This is useful for testing purposes, to easily simular a provider update: 13 | /// 14 | /// ```dart 15 | /// testWidgets('example', (tester) async { 16 | /// // Create a ValueNotifier that tests will use to drive the application 17 | /// final counter = ValueNotifier(0); 18 | /// 19 | /// // Mount the application using ValueListenableProvider 20 | /// await tester.pumpWidget( 21 | /// ValueListenableProvider.value( 22 | /// value: counter, 23 | /// child: MyApp(), 24 | /// ), 25 | /// ); 26 | /// 27 | /// // Tests can now simulate a provider update by updating the notifier 28 | /// // then calling tester.pump() 29 | /// counter.value++; 30 | /// await tester.pump(); 31 | /// }); 32 | /// ``` 33 | /// {@endtemplate} 34 | ValueListenableProvider.value({ 35 | Key? key, 36 | required ValueListenable value, 37 | UpdateShouldNotify? updateShouldNotify, 38 | Widget? child, 39 | }) : _valueListenable = value, 40 | _updateShouldNotify = updateShouldNotify, 41 | super(key: key, child: child); 42 | 43 | final ValueListenable _valueListenable; 44 | final UpdateShouldNotify? _updateShouldNotify; 45 | 46 | @override 47 | Widget buildWithChild(BuildContext context, Widget? child) { 48 | return ValueListenableBuilder( 49 | valueListenable: _valueListenable, 50 | builder: (context, value, _) { 51 | return Provider.value( 52 | value: value, 53 | updateShouldNotify: _updateShouldNotify, 54 | child: child, 55 | ); 56 | }, 57 | ); 58 | } 59 | 60 | @override 61 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 62 | super.debugFillProperties(properties); 63 | properties.add(DiagnosticsProperty('value', _valueListenable.value)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/provider/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: provider 2 | description: A wrapper around InheritedWidget to make them easier to use and more reusable. 3 | version: 6.1.5 4 | repository: https://github.com/rrousselGit/provider 5 | issue_tracker: https://github.com/rrousselGit/provider/issues 6 | 7 | environment: 8 | sdk: ">=2.12.0 <4.0.0" 9 | flutter: ">=1.16.0" 10 | 11 | dependencies: 12 | collection: ^1.15.0 13 | flutter: 14 | sdk: flutter 15 | nested: ^1.0.0 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | # The version is `any` because it is defined by Flutter SDK. 21 | leak_tracker: any 22 | mockito: ^5.0.0 23 | test: ^1.15.5 24 | -------------------------------------------------------------------------------- /packages/provider/test/flutter_test_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; 4 | 5 | FutureOr testExecutable(FutureOr Function() testMain) { 6 | LeakTesting.enable(); 7 | LeakTesting.settings = LeakTesting.settings.withIgnored( 8 | allNotGCed: true, 9 | createdByTestHelpers: true, 10 | classes: [ 11 | 'RenderObject', 12 | 'RenderParagraph', 13 | 'StatefulElement', 14 | '_StatefulTestState', 15 | 'StatelessElement', 16 | 'SingleChildRenderObjectElement', 17 | '_InheritedProviderScopeElement', 18 | '_InheritedProviderScopeElement', 19 | '_InheritedProviderScopeElement?>', 20 | '_InheritedProviderScopeElement', 21 | 'MultiChildRenderObjectElement', 22 | 'TextPainter', 23 | ], 24 | ); 25 | 26 | return testMain(); 27 | } 28 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/change_notifier_provider_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | void main() { 8 | group('ChangeNotifierProvider', () { 9 | testWidgets('value', (tester) async { 10 | final myNotifier = ValueNotifier(0); 11 | addTearDown(myNotifier.dispose); 12 | 13 | await tester.pumpWidget( 14 | MultiProvider( 15 | providers: [ 16 | ChangeNotifierProvider.value(value: myNotifier), 17 | ], 18 | child: Consumer>( 19 | builder: (_, value, __) { 20 | return Text( 21 | value.value.toString(), 22 | textDirection: TextDirection.ltr, 23 | ); 24 | }, 25 | ), 26 | ), 27 | ); 28 | 29 | expect(find.text('0'), findsOneWidget); 30 | 31 | myNotifier.value++; 32 | await tester.pump(); 33 | 34 | expect(find.text('1'), findsOneWidget); 35 | 36 | await tester.pumpWidget(Container()); 37 | 38 | // would throw if myNotifier is disposed 39 | myNotifier.notifyListeners(); 40 | }); 41 | 42 | testWidgets('builder', (tester) async { 43 | final myNotifier = ValueNotifier(0); 44 | 45 | await tester.pumpWidget( 46 | MultiProvider( 47 | providers: [ 48 | ChangeNotifierProvider(create: (_) => myNotifier), 49 | ], 50 | child: Consumer>( 51 | builder: (_, value, __) { 52 | return Text( 53 | value.value.toString(), 54 | textDirection: TextDirection.ltr, 55 | ); 56 | }, 57 | ), 58 | ), 59 | ); 60 | 61 | expect(find.text('0'), findsOneWidget); 62 | 63 | myNotifier.value++; 64 | await tester.pump(); 65 | 66 | expect(find.text('1'), findsOneWidget); 67 | 68 | await tester.pumpWidget(Container()); 69 | 70 | expect(myNotifier.notifyListeners, throwsAssertionError); 71 | }); 72 | 73 | testWidgets('builder1', (tester) async { 74 | final myNotifier = ValueNotifier(0); 75 | 76 | await tester.pumpWidget( 77 | MultiProvider( 78 | providers: [ 79 | Provider(create: (_) => A()), 80 | ChangeNotifierProxyProvider?>( 81 | create: (_) => null, 82 | update: (_, __, ___) => myNotifier, 83 | ), 84 | ], 85 | child: Consumer?>( 86 | builder: (_, value, __) { 87 | return Text( 88 | value!.value.toString(), 89 | textDirection: TextDirection.ltr, 90 | ); 91 | }, 92 | ), 93 | ), 94 | ); 95 | 96 | expect(find.text('0'), findsOneWidget); 97 | 98 | myNotifier.value++; 99 | await tester.pump(); 100 | 101 | expect(find.text('1'), findsOneWidget); 102 | 103 | await tester.pumpWidget(Container()); 104 | 105 | expect(myNotifier.notifyListeners, throwsAssertionError); 106 | }); 107 | 108 | testWidgets('builder2', (tester) async { 109 | final myNotifier = ValueNotifier(0); 110 | 111 | await tester.pumpWidget( 112 | MultiProvider( 113 | providers: [ 114 | Provider(create: (_) => A()), 115 | Provider(create: (_) => B()), 116 | ChangeNotifierProxyProvider2?>( 117 | create: (_) => null, 118 | update: (_, _a, _b, ___) => myNotifier, 119 | ), 120 | ], 121 | child: Consumer?>( 122 | builder: (_, value, __) { 123 | return Text( 124 | value!.value.toString(), 125 | textDirection: TextDirection.ltr, 126 | ); 127 | }, 128 | ), 129 | ), 130 | ); 131 | 132 | expect(find.text('0'), findsOneWidget); 133 | 134 | myNotifier.value++; 135 | await tester.pump(); 136 | 137 | expect(find.text('1'), findsOneWidget); 138 | 139 | await tester.pumpWidget(Container()); 140 | 141 | expect(myNotifier.notifyListeners, throwsAssertionError); 142 | }); 143 | 144 | testWidgets('builder3', (tester) async { 145 | final myNotifier = ValueNotifier(0); 146 | 147 | await tester.pumpWidget( 148 | MultiProvider( 149 | providers: [ 150 | Provider(create: (_) => A()), 151 | Provider(create: (_) => B()), 152 | Provider(create: (_) => C()), 153 | ChangeNotifierProxyProvider3?>( 154 | create: (_) => null, 155 | update: (_, _a, _b, _c, ___) => myNotifier, 156 | ), 157 | ], 158 | child: Consumer?>( 159 | builder: (_, value, __) { 160 | return Text( 161 | value!.value.toString(), 162 | textDirection: TextDirection.ltr, 163 | ); 164 | }, 165 | ), 166 | ), 167 | ); 168 | 169 | expect(find.text('0'), findsOneWidget); 170 | 171 | myNotifier.value++; 172 | await tester.pump(); 173 | 174 | expect(find.text('1'), findsOneWidget); 175 | 176 | await tester.pumpWidget(Container()); 177 | 178 | expect(myNotifier.notifyListeners, throwsAssertionError); 179 | }); 180 | 181 | testWidgets('builder4', (tester) async { 182 | final myNotifier = ValueNotifier(0); 183 | 184 | await tester.pumpWidget( 185 | MultiProvider( 186 | providers: [ 187 | Provider(create: (_) => A()), 188 | Provider(create: (_) => B()), 189 | Provider(create: (_) => C()), 190 | Provider(create: (_) => D()), 191 | ChangeNotifierProxyProvider4?>( 192 | create: (_) => null, 193 | update: (_, _a, _b, _c, _d, ___) => myNotifier, 194 | ), 195 | ], 196 | child: Consumer?>( 197 | builder: (_, value, __) { 198 | return Text( 199 | value!.value.toString(), 200 | textDirection: TextDirection.ltr, 201 | ); 202 | }, 203 | ), 204 | ), 205 | ); 206 | 207 | expect(find.text('0'), findsOneWidget); 208 | 209 | myNotifier.value++; 210 | await tester.pump(); 211 | 212 | expect(find.text('1'), findsOneWidget); 213 | 214 | await tester.pumpWidget(Container()); 215 | 216 | expect(myNotifier.notifyListeners, throwsAssertionError); 217 | }); 218 | 219 | testWidgets('builder5', (tester) async { 220 | final myNotifier = ValueNotifier(0); 221 | 222 | await tester.pumpWidget( 223 | MultiProvider( 224 | providers: [ 225 | Provider(create: (_) => A()), 226 | Provider(create: (_) => B()), 227 | Provider(create: (_) => C()), 228 | Provider(create: (_) => D()), 229 | Provider(create: (_) => E()), 230 | ChangeNotifierProxyProvider5?>( 231 | create: (_) => null, 232 | update: (_, _a, _b, _c, _d, _e, ___) => myNotifier, 233 | ), 234 | ], 235 | child: Consumer?>( 236 | builder: (_, value, __) { 237 | return Text( 238 | value!.value.toString(), 239 | textDirection: TextDirection.ltr, 240 | ); 241 | }, 242 | ), 243 | ), 244 | ); 245 | 246 | expect(find.text('0'), findsOneWidget); 247 | 248 | myNotifier.value++; 249 | await tester.pump(); 250 | 251 | expect(find.text('1'), findsOneWidget); 252 | 253 | await tester.pumpWidget(Container()); 254 | 255 | expect(myNotifier.notifyListeners, throwsAssertionError); 256 | }); 257 | 258 | testWidgets('builder6', (tester) async { 259 | final myNotifier = ValueNotifier(0); 260 | 261 | await tester.pumpWidget( 262 | MultiProvider( 263 | providers: [ 264 | Provider(create: (_) => A()), 265 | Provider(create: (_) => B()), 266 | Provider(create: (_) => C()), 267 | Provider(create: (_) => D()), 268 | Provider(create: (_) => E()), 269 | Provider(create: (_) => F()), 270 | ChangeNotifierProxyProvider6?>( 271 | create: (_) => null, 272 | update: (_, _a, _b, _c, _d, _e, _f, ___) => myNotifier, 273 | ), 274 | ], 275 | child: Consumer?>( 276 | builder: (_, value, __) { 277 | return Text( 278 | value!.value.toString(), 279 | textDirection: TextDirection.ltr, 280 | ); 281 | }, 282 | ), 283 | ), 284 | ); 285 | 286 | expect(find.text('0'), findsOneWidget); 287 | 288 | myNotifier.value++; 289 | await tester.pump(); 290 | 291 | expect(find.text('1'), findsOneWidget); 292 | 293 | await tester.pumpWidget(Container()); 294 | 295 | expect(myNotifier.notifyListeners, throwsAssertionError); 296 | }); 297 | 298 | testWidgets('builder0', (tester) async { 299 | final myNotifier = ValueNotifier(0); 300 | 301 | await tester.pumpWidget( 302 | MultiProvider( 303 | providers: [ 304 | ChangeNotifierProxyProvider0?>( 305 | create: (_) => null, 306 | update: (_, ___) => myNotifier, 307 | ), 308 | ], 309 | child: Consumer?>( 310 | builder: (_, value, __) { 311 | return Text( 312 | value!.value.toString(), 313 | textDirection: TextDirection.ltr, 314 | ); 315 | }, 316 | ), 317 | ), 318 | ); 319 | 320 | expect(find.text('0'), findsOneWidget); 321 | 322 | myNotifier.value++; 323 | await tester.pump(); 324 | 325 | expect(find.text('1'), findsOneWidget); 326 | 327 | await tester.pumpWidget(Container()); 328 | 329 | expect(myNotifier.notifyListeners, throwsAssertionError); 330 | }); 331 | }); 332 | 333 | testWidgets('Use builder property, not child', (tester) async { 334 | final myNotifier = ValueNotifier(0); 335 | 336 | await tester.pumpWidget( 337 | ChangeNotifierProvider>( 338 | create: (context) => myNotifier, 339 | builder: (context, _) { 340 | final notifier = context.watch>(); 341 | return Text( 342 | '${notifier.value}', 343 | textDirection: TextDirection.ltr, 344 | ); 345 | }, 346 | ), 347 | ); 348 | 349 | expect(find.text('0'), findsOneWidget); 350 | 351 | myNotifier.value++; 352 | await tester.pump(); 353 | 354 | expect(find.text('1'), findsOneWidget); 355 | 356 | await tester.pumpWidget(Container()); 357 | 358 | expect(myNotifier.notifyListeners, throwsAssertionError); 359 | }); 360 | } 361 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/common.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | Element findElementOfWidget() { 10 | return find.byType(T).first.evaluate().first; 11 | } 12 | 13 | final bool isSoundMode = [] is! List; 14 | 15 | InheritedContext findInheritedContext() { 16 | return find 17 | .byElementPredicate((e) => e is InheritedContext) 18 | .first 19 | .evaluate() 20 | .first as InheritedContext; 21 | } 22 | 23 | Type typeOf() => T; 24 | 25 | /// Given `T`, returns a `Provider`. 26 | /// 27 | /// For use in legacy tests: they can't instantiate a `Provider` directly 28 | /// because they can't write ``. But, they can pass around a `Provider. 29 | Provider nullableProviderOfValue(T value, Provider? child) => 30 | Provider.value( 31 | value: value, 32 | child: child, 33 | ); 34 | 35 | /// Given `T`, returns a `Provider`. 36 | /// 37 | /// For legacy tests to get a `Provider`. 38 | Provider nullSafeProviderOfValue(T value, Provider? child) => 39 | Provider.value( 40 | value: value, 41 | child: child, 42 | ); 43 | 44 | class InitialValueBuilderMock extends Mock { 45 | InitialValueBuilderMock(this._value) { 46 | when(this(any)).thenAnswer((_) => _value); 47 | } 48 | 49 | final T _value; 50 | 51 | T call(BuildContext? context) { 52 | return super.noSuchMethod( 53 | Invocation.method(#call, [context]), 54 | returnValue: _value, 55 | returnValueForMissingStub: _value, 56 | ) as T; 57 | } 58 | } 59 | 60 | class ValueBuilderMock extends Mock { 61 | ValueBuilderMock(this._value) { 62 | when(this(any, any)).thenReturn(_value); 63 | } 64 | 65 | final T _value; 66 | 67 | T call(BuildContext? context, T? previous) { 68 | return super.noSuchMethod( 69 | Invocation.method(#call, [context, previous]), 70 | returnValue: _value, 71 | returnValueForMissingStub: _value, 72 | ) as T; 73 | } 74 | } 75 | 76 | class TransitionBuilderMock extends Mock { 77 | TransitionBuilderMock([Widget Function(BuildContext c, Widget child)? cb]) { 78 | if (cb != null) { 79 | when(this(any, any)).thenAnswer((i) { 80 | final context = i.positionalArguments.first as BuildContext; 81 | final child = i.positionalArguments[1] as Widget; 82 | return cb(context, child); 83 | }); 84 | } 85 | } 86 | 87 | Widget call(BuildContext? context, Widget? child) { 88 | return super.noSuchMethod( 89 | Invocation.method(#call, [context, child]), 90 | returnValue: Container(), 91 | ) as Widget; 92 | } 93 | } 94 | 95 | class StartListeningMock extends Mock { 96 | StartListeningMock(VoidCallback value) { 97 | when(this(any, any)).thenReturn(value); 98 | } 99 | 100 | VoidCallback call(InheritedContext? context, T? value) { 101 | return super.noSuchMethod( 102 | Invocation.method(#call, [context, value]), 103 | returnValue: () {}, 104 | ) as VoidCallback; 105 | } 106 | } 107 | 108 | class StopListeningMock extends Mock { 109 | void call(); 110 | } 111 | 112 | class DisposeMock extends Mock { 113 | void call(BuildContext? context, T? value) { 114 | super.noSuchMethod( 115 | Invocation.method(#call, [context, value]), 116 | ); 117 | } 118 | } 119 | 120 | class MockNotifier extends Mock implements ChangeNotifier { 121 | @override 122 | void addListener(VoidCallback? listener); 123 | 124 | @override 125 | void removeListener(VoidCallback? listener); 126 | 127 | @override 128 | bool get hasListeners => super.noSuchMethod( 129 | Invocation.getter(#hasListeners), 130 | returnValue: false, 131 | returnValueForMissingStub: false, 132 | ) as bool; 133 | } 134 | 135 | class ValueWidgetBuilderMock extends Mock { 136 | ValueWidgetBuilderMock([ 137 | Widget Function(BuildContext c, T value, Widget child)? cb, 138 | ]) { 139 | if (cb != null) { 140 | when(this(any, any, any)).thenAnswer((i) { 141 | final context = i.positionalArguments.first as BuildContext; 142 | final value = i.positionalArguments[1] as T; 143 | final child = i.positionalArguments[2] as Widget; 144 | return cb(context, value, child); 145 | }); 146 | } 147 | } 148 | 149 | Widget call(BuildContext? context, T? value, Widget? child) { 150 | return super.noSuchMethod( 151 | Invocation.method(#call, [context, value, child]), 152 | returnValue: Container(), 153 | returnValueForMissingStub: Container(), 154 | ) as Widget; 155 | } 156 | } 157 | 158 | class BuilderMock extends Mock { 159 | BuilderMock([Widget Function(BuildContext c)? cb]) { 160 | if (cb != null) { 161 | when(this(any)).thenAnswer((i) { 162 | final context = i.positionalArguments.first as BuildContext; 163 | return cb(context); 164 | }); 165 | } 166 | } 167 | 168 | Widget call(BuildContext? context) { 169 | return super.noSuchMethod( 170 | Invocation.method(#call, [context]), 171 | returnValue: Container(), 172 | returnValueForMissingStub: Container(), 173 | ) as Widget; 174 | } 175 | } 176 | 177 | class StreamMock extends Mock implements Stream { 178 | @override 179 | StreamSubscription listen( 180 | void Function(T event)? onData, { 181 | Function? onError, 182 | void Function()? onDone, 183 | bool? cancelOnError, 184 | }) { 185 | return super.noSuchMethod( 186 | Invocation.method(#listen, [ 187 | onData 188 | ], { 189 | #onError: onError, 190 | #onDone: onDone, 191 | #cancelOnError: cancelOnError, 192 | }), 193 | returnValue: StreamSubscriptionMock(), 194 | returnValueForMissingStub: StreamSubscriptionMock(), 195 | ) as StreamSubscription; 196 | } 197 | } 198 | 199 | class FutureMock extends Mock implements Future {} 200 | 201 | class StreamSubscriptionMock extends Mock implements StreamSubscription { 202 | @override 203 | Future cancel() { 204 | return super.noSuchMethod( 205 | Invocation.method(#cancel, []), 206 | returnValue: Future.value(), 207 | returnValueForMissingStub: Future.value(), 208 | ) as Future; 209 | } 210 | } 211 | 212 | class MockConsumerBuilder extends Mock { 213 | Widget call(BuildContext? context, T? value, Widget? child) { 214 | return super.noSuchMethod( 215 | Invocation.method(#call, [context, value, child]), 216 | returnValue: Container(), 217 | returnValueForMissingStub: Container(), 218 | ) as Widget; 219 | } 220 | } 221 | 222 | class UpdateShouldNotifyMock extends Mock { 223 | bool call(T? old, T? newValue) { 224 | return super.noSuchMethod( 225 | Invocation.method(#call, [old, newValue]), 226 | returnValue: false, 227 | returnValueForMissingStub: false, 228 | ) as bool; 229 | } 230 | } 231 | 232 | class TextOf extends StatelessWidget { 233 | TextOf({Key? key}) : super(key: key); 234 | 235 | @override 236 | Widget build(BuildContext context) { 237 | return Text( 238 | Provider.of(context).toString(), 239 | textDirection: TextDirection.ltr, 240 | ); 241 | } 242 | } 243 | 244 | class DeferredStartListeningMock extends Mock { 245 | DeferredStartListeningMock([ 246 | VoidCallback Function( 247 | InheritedContext context, 248 | void Function(R value) setState, 249 | T controller, 250 | R? value, 251 | )? call, 252 | ]) { 253 | if (call != null) { 254 | when(this(any, any, any, any)).thenAnswer((invoc) { 255 | return Function.apply( 256 | call, 257 | invoc.positionalArguments, 258 | invoc.namedArguments, 259 | ) as VoidCallback; 260 | }); 261 | } 262 | } 263 | 264 | VoidCallback call( 265 | InheritedContext? context, 266 | void Function(R value)? setState, 267 | T? controller, 268 | R? value, 269 | ) => 270 | super.noSuchMethod( 271 | Invocation.method( 272 | #call, 273 | [context, setState, controller, value], 274 | ), 275 | returnValue: () {}, 276 | returnValueForMissingStub: () {}, 277 | ) as VoidCallback; 278 | } 279 | 280 | class DebugCheckValueTypeMock extends Mock { 281 | void call(T value); 282 | } 283 | 284 | class A with DiagnosticableTreeMixin {} 285 | 286 | class B with DiagnosticableTreeMixin {} 287 | 288 | class C with DiagnosticableTreeMixin {} 289 | 290 | class D with DiagnosticableTreeMixin {} 291 | 292 | class E with DiagnosticableTreeMixin {} 293 | 294 | class F with DiagnosticableTreeMixin {} 295 | 296 | class MockCombinedBuilder extends Mock { 297 | Widget call(Combined? foo) { 298 | return super.noSuchMethod( 299 | Invocation.method(#call, [foo]), 300 | returnValue: Container(), 301 | returnValueForMissingStub: Container(), 302 | ) as Widget; 303 | } 304 | } 305 | 306 | class CombinerMock extends Mock { 307 | Combined call(BuildContext? context, A? a, Combined? foo) { 308 | return super.noSuchMethod( 309 | Invocation.method(#call, [context, a, foo]), 310 | returnValue: const Combined(), 311 | returnValueForMissingStub: const Combined(), 312 | ) as Combined; 313 | } 314 | } 315 | 316 | class ProviderBuilderMock extends Mock { 317 | Widget call(BuildContext context, Combined value, Widget child); 318 | } 319 | 320 | class MyStream extends Fake implements Stream {} 321 | 322 | @immutable 323 | class Combined extends DiagnosticableTree { 324 | const Combined([ 325 | this.context, 326 | this.previous, 327 | this.a, 328 | this.b, 329 | this.c, 330 | this.d, 331 | this.e, 332 | this.f, 333 | ]); 334 | 335 | final A? a; 336 | final B? b; 337 | final C? c; 338 | final D? d; 339 | final E? e; 340 | final F? f; 341 | final Combined? previous; 342 | final BuildContext? context; 343 | 344 | @override 345 | // ignore: hash_and_equals 346 | bool operator ==(Object other) => 347 | other is Combined && 348 | other.context == context && 349 | other.previous == previous && 350 | other.a == a && 351 | other.b == b && 352 | other.c == c && 353 | other.e == e && 354 | other.f == f; 355 | 356 | // fancy toString for debug purposes. 357 | @override 358 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 359 | super.debugFillProperties(properties); 360 | properties.properties.addAll([ 361 | DiagnosticsProperty('a', a, defaultValue: null), 362 | DiagnosticsProperty('b', b, defaultValue: null), 363 | DiagnosticsProperty('c', c, defaultValue: null), 364 | DiagnosticsProperty('d', d, defaultValue: null), 365 | DiagnosticsProperty('e', e, defaultValue: null), 366 | DiagnosticsProperty('f', f, defaultValue: null), 367 | DiagnosticsProperty('previous', previous, defaultValue: null), 368 | DiagnosticsProperty('context', context, defaultValue: null), 369 | ]); 370 | } 371 | } 372 | 373 | class MyListenable extends ChangeNotifier {} 374 | 375 | int buildCountOf(BuildCount widget) { 376 | return ((find.byWidget(widget).evaluate().single as StatefulElement).state 377 | as _BuildCountState) 378 | .buildCount; 379 | } 380 | 381 | class BuildCount extends StatefulWidget { 382 | const BuildCount(this.builder, {Key? key}) : super(key: key); 383 | 384 | final WidgetBuilder builder; 385 | 386 | @override 387 | _BuildCountState createState() => _BuildCountState(); 388 | } 389 | 390 | class _BuildCountState extends State { 391 | int buildCount = 0; 392 | 393 | @override 394 | Widget build(BuildContext context) { 395 | buildCount++; 396 | return widget.builder(context); 397 | } 398 | 399 | @override 400 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 401 | super.debugFillProperties(properties); 402 | properties.add(IntProperty('buildCount', buildCount)); 403 | } 404 | } 405 | 406 | Matcher throwsProviderNotFound() { 407 | return throwsA(isA() 408 | .having((err) => err.valueType, 'valueType', T)); 409 | } 410 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/consumer_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | import 'common.dart'; 7 | 8 | class ConsumerBuilderMock extends Mock { 9 | void call(Combined? foo); 10 | } 11 | 12 | @immutable 13 | class Combined { 14 | const Combined( 15 | this.context, 16 | this.child, 17 | this.a, [ 18 | this.b, 19 | this.c, 20 | this.d, 21 | this.e, 22 | this.f, 23 | ]); 24 | 25 | final A? a; 26 | final B? b; 27 | final C? c; 28 | final D? d; 29 | final E? e; 30 | final F? f; 31 | final Widget? child; 32 | final BuildContext? context; 33 | 34 | @override 35 | // ignore: hash_and_equals 36 | bool operator ==(Object other) => 37 | other is Combined && 38 | other.context == context && 39 | other.child == child && 40 | other.a == a && 41 | other.b == b && 42 | other.c == c && 43 | other.e == e && 44 | other.f == f; 45 | } 46 | 47 | void main() { 48 | final a = A(); 49 | final b = B(); 50 | final c = C(); 51 | final d = D(); 52 | final e = E(); 53 | final f = F(); 54 | 55 | final multiProviderNodes = [ 56 | Provider.value(value: a), 57 | Provider.value(value: b), 58 | Provider.value(value: c), 59 | Provider.value(value: d), 60 | Provider.value(value: e), 61 | Provider.value(value: f), 62 | ]; 63 | 64 | final mock = ConsumerBuilderMock(); 65 | tearDown(() { 66 | clearInteractions(mock); 67 | }); 68 | 69 | group('consumer', () { 70 | testWidgets('obtains value from Provider', (tester) async { 71 | final key = GlobalKey(); 72 | final child = Container(); 73 | 74 | await tester.pumpWidget( 75 | MultiProvider( 76 | providers: multiProviderNodes, 77 | child: Consumer( 78 | key: key, 79 | builder: (context, value, child) { 80 | mock(Combined(context, child, value)); 81 | return Container(); 82 | }, 83 | child: child, 84 | ), 85 | ), 86 | ); 87 | 88 | verify(mock(Combined(key.currentContext, child, a))); 89 | }); 90 | 91 | testWidgets('can be used inside MultiProvider', (tester) async { 92 | final key = GlobalKey(); 93 | 94 | await tester.pumpWidget( 95 | MultiProvider( 96 | providers: [ 97 | ...multiProviderNodes, 98 | Consumer( 99 | key: key, 100 | builder: (_, a, child) => Container(child: child), 101 | ) 102 | ], 103 | child: const Text('foo', textDirection: TextDirection.ltr), 104 | ), 105 | ); 106 | 107 | expect(find.text('foo'), findsOneWidget); 108 | expect(find.byType(Container), findsOneWidget); 109 | expect(key.currentContext, isNotNull); 110 | }); 111 | }); 112 | 113 | group('consumer2', () { 114 | testWidgets('obtains value from Provider', (tester) async { 115 | final key = GlobalKey(); 116 | final child = Container(); 117 | 118 | await tester.pumpWidget( 119 | MultiProvider( 120 | providers: multiProviderNodes, 121 | child: Consumer2( 122 | key: key, 123 | builder: (context, value, v2, child) { 124 | mock(Combined(context, child, value, v2)); 125 | return Container(); 126 | }, 127 | child: child, 128 | ), 129 | ), 130 | ); 131 | 132 | verify(mock(Combined(key.currentContext, child, a, b))); 133 | }); 134 | 135 | testWidgets('can be used inside MultiProvider', (tester) async { 136 | final key = GlobalKey(); 137 | 138 | await tester.pumpWidget( 139 | MultiProvider( 140 | providers: [ 141 | ...multiProviderNodes, 142 | Consumer2( 143 | key: key, 144 | builder: (_, a, b, child) => Container(child: child), 145 | ) 146 | ], 147 | child: const Text('foo', textDirection: TextDirection.ltr), 148 | ), 149 | ); 150 | 151 | expect(find.text('foo'), findsOneWidget); 152 | expect(find.byType(Container), findsOneWidget); 153 | expect(key.currentContext, isNotNull); 154 | }); 155 | }); 156 | 157 | group('consumer3', () { 158 | testWidgets('obtains value from Provider', (tester) async { 159 | final key = GlobalKey(); 160 | final child = Container(); 161 | 162 | await tester.pumpWidget( 163 | MultiProvider( 164 | providers: multiProviderNodes, 165 | child: Consumer3( 166 | key: key, 167 | builder: (context, value, v2, v3, child) { 168 | mock(Combined(context, child, value, v2, v3)); 169 | return Container(); 170 | }, 171 | child: child, 172 | ), 173 | ), 174 | ); 175 | 176 | verify(mock(Combined(key.currentContext, child, a, b, c))); 177 | }); 178 | 179 | testWidgets('can be used inside MultiProvider', (tester) async { 180 | final key = GlobalKey(); 181 | 182 | await tester.pumpWidget( 183 | MultiProvider( 184 | providers: [ 185 | ...multiProviderNodes, 186 | Consumer3( 187 | key: key, 188 | builder: (_, a, b, c, child) => Container(child: child), 189 | ) 190 | ], 191 | child: const Text('foo', textDirection: TextDirection.ltr), 192 | ), 193 | ); 194 | 195 | expect(find.text('foo'), findsOneWidget); 196 | expect(find.byType(Container), findsOneWidget); 197 | expect(key.currentContext, isNotNull); 198 | }); 199 | }); 200 | 201 | group('consumer4', () { 202 | testWidgets('obtains value from Provider', (tester) async { 203 | final key = GlobalKey(); 204 | final child = Container(); 205 | 206 | await tester.pumpWidget( 207 | MultiProvider( 208 | providers: multiProviderNodes, 209 | child: Consumer4( 210 | key: key, 211 | builder: (context, value, v2, v3, v4, child) { 212 | mock(Combined(context, child, value, v2, v3, v4)); 213 | return Container(); 214 | }, 215 | child: child, 216 | ), 217 | ), 218 | ); 219 | 220 | verify(mock(Combined(key.currentContext, child, a, b, c, d))); 221 | }); 222 | 223 | testWidgets('can be used inside MultiProvider', (tester) async { 224 | final key = GlobalKey(); 225 | 226 | await tester.pumpWidget( 227 | MultiProvider( 228 | providers: [ 229 | ...multiProviderNodes, 230 | Consumer4( 231 | key: key, 232 | builder: (_, a, b, c, d, child) => Container(child: child), 233 | ) 234 | ], 235 | child: const Text('foo', textDirection: TextDirection.ltr), 236 | ), 237 | ); 238 | 239 | expect(find.text('foo'), findsOneWidget); 240 | expect(find.byType(Container), findsOneWidget); 241 | expect(key.currentContext, isNotNull); 242 | }); 243 | }); 244 | 245 | group('consumer5', () { 246 | testWidgets('obtains value from Provider', (tester) async { 247 | final key = GlobalKey(); 248 | final child = Container(); 249 | 250 | await tester.pumpWidget( 251 | MultiProvider( 252 | providers: multiProviderNodes, 253 | child: Consumer5( 254 | key: key, 255 | builder: (context, value, v2, v3, v4, v5, child) { 256 | mock(Combined(context, child, value, v2, v3, v4, v5)); 257 | return Container(); 258 | }, 259 | child: child, 260 | ), 261 | ), 262 | ); 263 | 264 | verify(mock(Combined(key.currentContext, child, a, b, c, d, e))); 265 | }); 266 | 267 | testWidgets('can be used inside MultiProvider', (tester) async { 268 | final key = GlobalKey(); 269 | 270 | await tester.pumpWidget( 271 | MultiProvider( 272 | providers: [ 273 | ...multiProviderNodes, 274 | Consumer5( 275 | key: key, 276 | builder: (_, a, b, c, d, e, child) => Container(child: child), 277 | ) 278 | ], 279 | child: const Text('foo', textDirection: TextDirection.ltr), 280 | ), 281 | ); 282 | 283 | expect(find.text('foo'), findsOneWidget); 284 | expect(find.byType(Container), findsOneWidget); 285 | expect(key.currentContext, isNotNull); 286 | }); 287 | }); 288 | 289 | group('consumer6', () { 290 | testWidgets('obtains value from Provider', (tester) async { 291 | final key = GlobalKey(); 292 | final child = Container(); 293 | 294 | await tester.pumpWidget( 295 | MultiProvider( 296 | providers: multiProviderNodes, 297 | child: Consumer6( 298 | key: key, 299 | builder: (context, value, v2, v3, v4, v5, v6, child) { 300 | mock(Combined(context, child, value, v2, v3, v4, v5, v6)); 301 | return Container(); 302 | }, 303 | child: child, 304 | ), 305 | ), 306 | ); 307 | 308 | verify(mock(Combined(key.currentContext, child, a, b, c, d, e, f))); 309 | }); 310 | 311 | testWidgets('can be used inside MultiProvider', (tester) async { 312 | final key = GlobalKey(); 313 | 314 | await tester.pumpWidget( 315 | MultiProvider( 316 | providers: [ 317 | ...multiProviderNodes, 318 | Consumer6( 319 | key: key, 320 | builder: (_, a, b, c, d, e, f, child) => Container(child: child), 321 | ) 322 | ], 323 | child: const Text('foo', textDirection: TextDirection.ltr), 324 | ), 325 | ); 326 | 327 | expect(find.text('foo'), findsOneWidget); 328 | expect(find.byType(Container), findsOneWidget); 329 | expect(key.currentContext, isNotNull); 330 | }); 331 | }); 332 | } 333 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/devtool_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:provider/src/provider.dart'; 5 | 6 | import 'matchers.dart'; 7 | 8 | void main() { 9 | late PostEventSpy spy; 10 | 11 | setUp(() { 12 | spy = spyPostEvent(); 13 | }); 14 | 15 | tearDown(() => spy.dispose()); 16 | 17 | testWidgets('calls postEvent whenever a provider is updated', (tester) async { 18 | final notifier = ValueNotifier(42); 19 | addTearDown(notifier.dispose); 20 | 21 | await tester.pumpWidget( 22 | MultiProvider( 23 | providers: [ 24 | ChangeNotifierProvider.value(value: notifier), 25 | ], 26 | child: Consumer>( 27 | builder: (context, value, child) { 28 | return Container(); 29 | }, 30 | ), 31 | ), 32 | ); 33 | 34 | final notifierId = 35 | ProviderBinding.debugInstance.providerDetails.keys.single; 36 | 37 | spy.logs.clear(); 38 | 39 | notifier.notifyListeners(); 40 | 41 | expect(spy.logs, isEmpty); 42 | 43 | await tester.pump(); 44 | 45 | expect( 46 | spy.logs, 47 | [ 48 | isPostEventCall( 49 | 'provider:provider_changed', 50 | {'id': notifierId}, 51 | ), 52 | ], 53 | ); 54 | spy.logs.clear(); 55 | }); 56 | 57 | testWidgets('calls postEvent whenever a provider is mounted/unmounted', 58 | (tester) async { 59 | Provider.value(value: 42); 60 | 61 | expect(spy.logs, isEmpty); 62 | expect(ProviderBinding.debugInstance.providerDetails, isEmpty); 63 | 64 | await tester.pumpWidget( 65 | MultiProvider( 66 | providers: [ 67 | Provider.value(value: 42), 68 | ], 69 | child: Container(), 70 | ), 71 | ); 72 | 73 | final intProviderId = 74 | ProviderBinding.debugInstance.providerDetails.keys.first; 75 | 76 | expect(ProviderBinding.debugInstance.providerDetails, { 77 | intProviderId: isA() 78 | .having((e) => e.id, 'id', intProviderId) 79 | .having((e) => e.type, 'type', 'Provider') 80 | .having((e) => e.value, 'value', 42), 81 | }); 82 | expect( 83 | spy.logs, 84 | [isPostEventCall('provider:provider_list_changed', isEmpty)], 85 | ); 86 | spy.logs.clear(); 87 | 88 | await tester.pumpWidget( 89 | MultiProvider( 90 | providers: [ 91 | Provider.value(value: 42), 92 | Provider.value(value: '42'), 93 | ], 94 | child: Container(), 95 | ), 96 | ); 97 | 98 | final stringProviderId = 99 | ProviderBinding.debugInstance.providerDetails.keys.last; 100 | 101 | expect(intProviderId, isNot(stringProviderId)); 102 | expect(ProviderBinding.debugInstance.providerDetails, { 103 | intProviderId: isA() 104 | .having((e) => e.id, 'id', intProviderId) 105 | .having((e) => e.type, 'type', 'Provider') 106 | .having((e) => e.value, 'value', 42), 107 | stringProviderId: isA() 108 | .having((e) => e.id, 'id', stringProviderId) 109 | .having((e) => e.type, 'type', 'Provider') 110 | .having((e) => e.value, 'value', '42'), 111 | }); 112 | expect( 113 | spy.logs, 114 | [isPostEventCall('provider:provider_list_changed', isEmpty)], 115 | ); 116 | spy.logs.clear(); 117 | 118 | await tester.pumpWidget( 119 | MultiProvider( 120 | providers: [ 121 | Provider.value(value: 42), 122 | ], 123 | child: Container(), 124 | ), 125 | ); 126 | 127 | expect(ProviderBinding.debugInstance.providerDetails, { 128 | intProviderId: isA() 129 | .having((e) => e.id, 'id', intProviderId) 130 | .having((e) => e.type, 'type', 'Provider') 131 | .having((e) => e.value, 'value', 42), 132 | }); 133 | expect( 134 | spy.logs, 135 | [isPostEventCall('provider:provider_list_changed', isEmpty)], 136 | ); 137 | spy.logs.clear(); 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/future_provider_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:mockito/mockito.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import 'common.dart'; 9 | 10 | class ErrorBuilderMock extends Mock { 11 | ErrorBuilderMock(this.fallback); 12 | 13 | final T fallback; 14 | 15 | T call(BuildContext? context, Object? error) { 16 | return super.noSuchMethod( 17 | Invocation.method(#call, [context, error]), 18 | returnValue: fallback, 19 | returnValueForMissingStub: fallback, 20 | ) as T; 21 | } 22 | } 23 | 24 | void main() { 25 | testWidgets('works with MultiProvider', (tester) async { 26 | await tester.pumpWidget( 27 | MultiProvider( 28 | providers: [ 29 | FutureProvider.value( 30 | initialData: 0, 31 | value: Future.value(42), 32 | ), 33 | ], 34 | child: TextOf(), 35 | ), 36 | ); 37 | 38 | expect(find.text('0'), findsOneWidget); 39 | 40 | await Future.microtask(tester.pump); 41 | 42 | expect(find.text('42'), findsOneWidget); 43 | }); 44 | 45 | testWidgets( 46 | '(catchError) previous future completes after transition is no-op', 47 | (tester) async { 48 | final controller = Completer(); 49 | final controller2 = Completer(); 50 | 51 | await tester.pumpWidget( 52 | FutureProvider.value( 53 | initialData: 0, 54 | value: controller.future, 55 | child: TextOf(), 56 | ), 57 | ); 58 | 59 | expect(find.text('0'), findsOneWidget); 60 | 61 | await tester.pumpWidget( 62 | FutureProvider.value( 63 | initialData: 1, 64 | value: controller2.future, 65 | child: TextOf(), 66 | ), 67 | ); 68 | 69 | expect(find.text('0'), findsOneWidget); 70 | 71 | controller.complete(1); 72 | await Future.microtask(tester.pump); 73 | 74 | expect(find.text('0'), findsOneWidget); 75 | 76 | controller2.complete(2); 77 | 78 | await Future.microtask(tester.pump); 79 | 80 | expect(find.text('0'), findsNothing); 81 | expect(find.text('2'), findsOneWidget); 82 | }, 83 | ); 84 | testWidgets( 85 | 'previous future completes after transition is no-op', 86 | (tester) async { 87 | final controller = Completer(); 88 | final controller2 = Completer(); 89 | 90 | await tester.pumpWidget( 91 | FutureProvider.value( 92 | initialData: 0, 93 | value: controller.future, 94 | child: TextOf(), 95 | ), 96 | ); 97 | 98 | expect(find.text('0'), findsOneWidget); 99 | 100 | await tester.pumpWidget( 101 | FutureProvider.value( 102 | initialData: 1, 103 | value: controller2.future, 104 | child: TextOf(), 105 | ), 106 | ); 107 | 108 | expect(find.text('0'), findsOneWidget); 109 | 110 | controller.complete(1); 111 | await Future.microtask(tester.pump); 112 | 113 | expect(find.text('0'), findsOneWidget); 114 | 115 | controller2.complete(2); 116 | await Future.microtask(tester.pump); 117 | 118 | expect(find.text('2'), findsOneWidget); 119 | }, 120 | ); 121 | testWidgets( 122 | 'transition from future to future preserve state', 123 | (tester) async { 124 | final controller = Completer(); 125 | final controller2 = Completer(); 126 | 127 | await tester.pumpWidget( 128 | FutureProvider.value( 129 | initialData: 0, 130 | value: controller.future, 131 | child: TextOf(), 132 | ), 133 | ); 134 | 135 | expect(find.text('0'), findsOneWidget); 136 | 137 | controller.complete(1); 138 | 139 | await Future.microtask(tester.pump); 140 | 141 | expect(find.text('1'), findsOneWidget); 142 | 143 | await tester.pumpWidget( 144 | FutureProvider.value( 145 | initialData: 0, 146 | value: controller2.future, 147 | child: TextOf(), 148 | ), 149 | ); 150 | 151 | expect(find.text('1'), findsOneWidget); 152 | 153 | controller2.complete(2); 154 | await Future.microtask(tester.pump); 155 | 156 | expect(find.text('2'), findsOneWidget); 157 | }, 158 | ); 159 | testWidgets('throws if future has error and catchError is missing', 160 | (tester) async { 161 | final controller = Completer(); 162 | 163 | await tester.pumpWidget( 164 | FutureProvider.value( 165 | initialData: 0, 166 | value: controller.future, 167 | child: TextOf(), 168 | ), 169 | ); 170 | 171 | controller.completeError(42); 172 | await Future.microtask(tester.pump); 173 | 174 | final dynamic exception = tester.takeException(); 175 | expect(exception, isFlutterError); 176 | expect(exception.toString(), equals(''' 177 | An exception was throw by Future listened by 178 | FutureProvider, but no `catchError` was provided. 179 | 180 | Exception: 181 | 42 182 | ''')); 183 | }); 184 | 185 | testWidgets('calls catchError if present and future has error', 186 | (tester) async { 187 | final controller = Completer(); 188 | final catchError = ErrorBuilderMock(0); 189 | when(catchError(any, 42)).thenReturn(42); 190 | 191 | await tester.pumpWidget( 192 | FutureProvider.value( 193 | initialData: null, 194 | value: controller.future, 195 | catchError: catchError, 196 | child: TextOf(), 197 | ), 198 | ); 199 | 200 | expect(find.text('null'), findsOneWidget); 201 | 202 | controller.completeError(42); 203 | 204 | await Future.microtask(tester.pump); 205 | 206 | expect(find.text('42'), findsOneWidget); 207 | verify(catchError(argThat(isNotNull), 42)).called(1); 208 | verifyNoMoreInteractions(catchError); 209 | }); 210 | 211 | testWidgets('works with null', (tester) async { 212 | await tester.pumpWidget( 213 | FutureProvider.value( 214 | initialData: 42, 215 | value: null, 216 | child: TextOf(), 217 | ), 218 | ); 219 | 220 | expect(find.text('42'), findsOneWidget); 221 | 222 | await tester.pumpWidget(Container()); 223 | }); 224 | 225 | testWidgets('create and dispose future with builder', (tester) async { 226 | final completer = Completer(); 227 | 228 | await tester.pumpWidget( 229 | FutureProvider( 230 | initialData: 42, 231 | create: (_) => completer.future, 232 | child: TextOf(), 233 | ), 234 | ); 235 | 236 | expect(find.text('42'), findsOneWidget); 237 | 238 | completer.complete(24); 239 | 240 | await Future.microtask(tester.pump); 241 | 242 | expect(find.text('24'), findsOneWidget); 243 | }); 244 | } 245 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/listenable_proxy_provider_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: invalid_use_of_protected_member 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'common.dart'; 8 | 9 | // ignore: prefer_mixin, must_be_immutable 10 | class _ListenableCombined extends Combined implements Listenable { 11 | const _ListenableCombined([ 12 | BuildContext? context, 13 | Combined? previous, 14 | A? a, 15 | B? b, 16 | C? c, 17 | D? d, 18 | E? e, 19 | F? f, 20 | ]) : super(context, previous, a, b, c, d, e, f); 21 | 22 | @override 23 | void addListener(VoidCallback listener) {} 24 | 25 | @override 26 | void removeListener(VoidCallback listener) {} 27 | } 28 | 29 | void main() { 30 | final a = A(); 31 | final b = B(); 32 | final c = C(); 33 | final d = D(); 34 | final e = E(); 35 | final f = F(); 36 | 37 | final combinedConsumerMock = MockCombinedBuilder(); 38 | setUp(() => when(combinedConsumerMock(any)).thenReturn(Container())); 39 | tearDown(() { 40 | clearInteractions(combinedConsumerMock); 41 | }); 42 | 43 | final mockConsumer = Consumer<_ListenableCombined>( 44 | builder: (context, combined, child) => combinedConsumerMock(combined), 45 | ); 46 | 47 | group('ListenableProxyProvider', () { 48 | testWidgets('rebuilds dependendents when listeners are called', 49 | (tester) async { 50 | final notifier = ValueNotifier(0); 51 | addTearDown(notifier.dispose); 52 | 53 | await tester.pumpWidget( 54 | MultiProvider( 55 | providers: [ 56 | Provider.value(value: 0), 57 | ListenableProxyProvider>( 58 | create: (_) => notifier, 59 | update: (_, count, value) => value!, 60 | ) 61 | ], 62 | child: Consumer>(builder: (_, value, __) { 63 | return Text( 64 | value.value.toString(), 65 | textDirection: TextDirection.ltr, 66 | ); 67 | }), 68 | ), 69 | ); 70 | 71 | expect(find.text('0'), findsOneWidget); 72 | expect(find.text('1'), findsNothing); 73 | 74 | notifier.value++; 75 | await tester.pump(); 76 | 77 | expect(find.text('1'), findsOneWidget); 78 | expect(find.text('0'), findsNothing); 79 | }); 80 | 81 | testWidgets( 82 | 'update returning a new Listenable disposes the previously created value' 83 | ' and update dependents', 84 | (tester) async { 85 | final builder = MockConsumerBuilder(); 86 | when(builder(any, any, any)).thenReturn(Container()); 87 | final child = Consumer(builder: builder); 88 | 89 | final dispose = DisposeMock(); 90 | final notifier = MockNotifier(); 91 | when(notifier.hasListeners).thenReturn(false); 92 | await tester.pumpWidget( 93 | MultiProvider( 94 | providers: [ 95 | Provider.value(value: 0), 96 | ListenableProxyProvider( 97 | update: (_, count, value) => notifier, 98 | dispose: dispose, 99 | ) 100 | ], 101 | child: child, 102 | ), 103 | ); 104 | 105 | clearInteractions(builder); 106 | 107 | final dispose2 = DisposeMock(); 108 | final notifier2 = MockNotifier(); 109 | when(notifier2.hasListeners).thenReturn(false); 110 | await tester.pumpWidget( 111 | MultiProvider( 112 | providers: [ 113 | Provider.value(value: 1), 114 | ListenableProxyProvider( 115 | update: (_, count, value) => notifier2, 116 | dispose: dispose2, 117 | ) 118 | ], 119 | child: child, 120 | ), 121 | ); 122 | 123 | verify(builder(argThat(isNotNull), notifier2, null)).called(1); 124 | verifyNoMoreInteractions(builder); 125 | verify(dispose(argThat(isNotNull), notifier)).called(1); 126 | verifyNoMoreInteractions(dispose); 127 | verifyNoMoreInteractions(dispose2); 128 | 129 | await tester.pumpWidget(Container()); 130 | 131 | verify(dispose2(argThat(isNotNull), notifier2)).called(1); 132 | verifyNoMoreInteractions(dispose); 133 | verifyNoMoreInteractions(dispose2); 134 | }, 135 | ); 136 | testWidgets('disposes of created value', (tester) async { 137 | final dispose = DisposeMock>(); 138 | final notifier = ValueNotifier(0); 139 | addTearDown(notifier.dispose); 140 | final key = GlobalKey(); 141 | 142 | await tester.pumpWidget( 143 | MultiProvider( 144 | providers: [ 145 | Provider.value(value: 0), 146 | ListenableProxyProvider>( 147 | key: key, 148 | create: (_) => notifier, 149 | update: (_, count, value) => value!..value = count, 150 | dispose: dispose, 151 | ) 152 | ], 153 | child: TextOf>(), 154 | ), 155 | ); 156 | 157 | verifyZeroInteractions(dispose); 158 | 159 | await tester.pumpWidget(Container()); 160 | 161 | verify(dispose(argThat(isNotNull), notifier)).called(1); 162 | verifyNoMoreInteractions(dispose); 163 | }); 164 | }); 165 | 166 | group('ListenableProxyProvider variants', () { 167 | InheritedContext findInheritedProvider() => 168 | findInheritedContext(); 169 | testWidgets('ListenableProxyProvider', (tester) async { 170 | await tester.pumpWidget( 171 | MultiProvider( 172 | providers: [ 173 | Provider.value(value: a), 174 | Provider.value(value: b), 175 | Provider.value(value: c), 176 | Provider.value(value: d), 177 | Provider.value(value: e), 178 | Provider.value(value: f), 179 | ListenableProxyProvider0<_ListenableCombined>( 180 | create: (_) => const _ListenableCombined(), 181 | update: (context, previous) => _ListenableCombined( 182 | context, 183 | previous, 184 | Provider.of(context), 185 | Provider.of(context), 186 | Provider.of(context), 187 | Provider.of(context), 188 | Provider.of(context), 189 | Provider.of(context), 190 | ), 191 | ) 192 | ], 193 | child: mockConsumer, 194 | ), 195 | ); 196 | 197 | final context = findInheritedProvider(); 198 | 199 | verify( 200 | combinedConsumerMock( 201 | _ListenableCombined( 202 | context, 203 | const _ListenableCombined(), 204 | a, 205 | b, 206 | c, 207 | d, 208 | e, 209 | f, 210 | ), 211 | ), 212 | ).called(1); 213 | }); 214 | 215 | testWidgets('ListenableProxyProvider2', (tester) async { 216 | await tester.pumpWidget( 217 | MultiProvider( 218 | providers: [ 219 | Provider.value(value: a), 220 | Provider.value(value: b), 221 | Provider.value(value: c), 222 | Provider.value(value: d), 223 | Provider.value(value: e), 224 | Provider.value(value: f), 225 | ListenableProxyProvider2( 226 | create: (_) => const _ListenableCombined(), 227 | update: (context, a, b, previous) => 228 | _ListenableCombined(context, previous, a, b), 229 | ) 230 | ], 231 | child: mockConsumer, 232 | ), 233 | ); 234 | 235 | final context = findInheritedProvider(); 236 | 237 | verify( 238 | combinedConsumerMock( 239 | _ListenableCombined(context, const _ListenableCombined(), a, b), 240 | ), 241 | ).called(1); 242 | }); 243 | 244 | testWidgets('ListenableProxyProvider3', (tester) async { 245 | await tester.pumpWidget( 246 | MultiProvider( 247 | providers: [ 248 | Provider.value(value: a), 249 | Provider.value(value: b), 250 | Provider.value(value: c), 251 | Provider.value(value: d), 252 | Provider.value(value: e), 253 | Provider.value(value: f), 254 | ListenableProxyProvider3( 255 | create: (_) => const _ListenableCombined(), 256 | update: (context, a, b, c, previous) => 257 | _ListenableCombined(context, previous, a, b, c), 258 | ) 259 | ], 260 | child: mockConsumer, 261 | ), 262 | ); 263 | 264 | final context = findInheritedProvider(); 265 | 266 | verify( 267 | combinedConsumerMock( 268 | _ListenableCombined(context, const _ListenableCombined(), a, b, c), 269 | ), 270 | ).called(1); 271 | }); 272 | 273 | testWidgets('ListenableProxyProvider4', (tester) async { 274 | await tester.pumpWidget( 275 | MultiProvider( 276 | providers: [ 277 | Provider.value(value: a), 278 | Provider.value(value: b), 279 | Provider.value(value: c), 280 | Provider.value(value: d), 281 | Provider.value(value: e), 282 | Provider.value(value: f), 283 | ListenableProxyProvider4( 284 | create: (_) => const _ListenableCombined(), 285 | update: (context, a, b, c, d, previous) => 286 | _ListenableCombined(context, previous, a, b, c, d), 287 | ) 288 | ], 289 | child: mockConsumer, 290 | ), 291 | ); 292 | 293 | final context = findInheritedProvider(); 294 | 295 | verify( 296 | combinedConsumerMock( 297 | _ListenableCombined(context, const _ListenableCombined(), a, b, c, d), 298 | ), 299 | ).called(1); 300 | }); 301 | 302 | testWidgets('ListenableProxyProvider5', (tester) async { 303 | await tester.pumpWidget( 304 | MultiProvider( 305 | providers: [ 306 | Provider.value(value: a), 307 | Provider.value(value: b), 308 | Provider.value(value: c), 309 | Provider.value(value: d), 310 | Provider.value(value: e), 311 | Provider.value(value: f), 312 | ListenableProxyProvider5( 313 | create: (_) => const _ListenableCombined(), 314 | update: (context, a, b, c, d, e, previous) => 315 | _ListenableCombined(context, previous, a, b, c, d, e), 316 | ) 317 | ], 318 | child: mockConsumer, 319 | ), 320 | ); 321 | 322 | final context = findInheritedProvider(); 323 | 324 | verify( 325 | combinedConsumerMock( 326 | _ListenableCombined( 327 | context, const _ListenableCombined(), a, b, c, d, e), 328 | ), 329 | ).called(1); 330 | }); 331 | 332 | testWidgets('ListenableProxyProvider6', (tester) async { 333 | await tester.pumpWidget( 334 | MultiProvider( 335 | providers: [ 336 | Provider.value(value: a), 337 | Provider.value(value: b), 338 | Provider.value(value: c), 339 | Provider.value(value: d), 340 | Provider.value(value: e), 341 | Provider.value(value: f), 342 | ListenableProxyProvider6( 343 | create: (_) => const _ListenableCombined(), 344 | update: (context, a, b, c, d, e, f, previous) => 345 | _ListenableCombined(context, previous, a, b, c, d, e, f), 346 | ) 347 | ], 348 | child: mockConsumer, 349 | ), 350 | ); 351 | 352 | final context = findInheritedProvider(); 353 | verify( 354 | combinedConsumerMock( 355 | _ListenableCombined( 356 | context, const _ListenableCombined(), a, b, c, d, e, f), 357 | ), 358 | ).called(1); 359 | }); 360 | }); 361 | } 362 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/matchers.dart: -------------------------------------------------------------------------------- 1 | import 'package:provider/src/provider.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | Matcher isPostEventCall(Object kind, Object? event) { 5 | var matcher = 6 | isA().having((e) => e.eventKind, 'eventKind', kind); 7 | 8 | if (event != null) { 9 | matcher = matcher.having((e) => e.event, 'event', event); 10 | } 11 | 12 | return matcher; 13 | } 14 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/multi_provider_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:nested/nested.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | import 'common.dart'; 7 | 8 | void main() { 9 | group('MultiProvider', () { 10 | testWidgets('Supports a large number of providers', (tester) async { 11 | await tester.pumpWidget( 12 | MultiProvider( 13 | providers: [ 14 | for (var i = 0; i < 1500; i++) Provider.value(value: i), 15 | ], 16 | child: Container(), 17 | ), 18 | ); 19 | }); 20 | 21 | testWidgets('Simple', (tester) async { 22 | await tester.pumpWidget( 23 | MultiProvider( 24 | providers: [ 25 | Provider.value(value: 42), 26 | Provider( 27 | create: (context) => context.read().toString(), 28 | ), 29 | Provider( 30 | create: (context) => double.parse(context.read()), 31 | ), 32 | ], 33 | child: Container(), 34 | ), 35 | ); 36 | }); 37 | 38 | testWidgets('Direct dependency', (tester) async { 39 | final key = GlobalKey(); 40 | await tester.pumpWidget( 41 | MultiProvider( 42 | providers: [ 43 | Provider.value(value: 'foo'), 44 | Provider.value(key: key, value: 42), 45 | ], 46 | child: Container(), 47 | ), 48 | ); 49 | 50 | expect(key.currentContext!.read(), 'foo'); 51 | expect(key.currentContext!.read(), isNull); 52 | }); 53 | 54 | testWidgets('MultiProvider children can only access parent providers', 55 | (tester) async { 56 | final k1 = GlobalKey(); 57 | final k2 = GlobalKey(); 58 | final k3 = GlobalKey(); 59 | final p1 = Provider.value(key: k1, value: 42); 60 | final p2 = Provider.value(key: k2, value: 'foo'); 61 | final p3 = Provider.value(key: k3, value: 44); 62 | 63 | final keyChild = GlobalKey(); 64 | await tester.pumpWidget(MultiProvider( 65 | providers: [p1, p2, p3], 66 | child: Text('Foo', key: keyChild, textDirection: TextDirection.ltr), 67 | )); 68 | 69 | expect(find.text('Foo'), findsOneWidget); 70 | 71 | // p1 cannot access to p1/p2/p3 72 | expect( 73 | () => Provider.of(k1.currentContext!, listen: false), 74 | throwsProviderNotFound(), 75 | ); 76 | expect( 77 | () => Provider.of(k1.currentContext!, listen: false), 78 | throwsProviderNotFound(), 79 | ); 80 | expect( 81 | () => Provider.of(k1.currentContext!, listen: false), 82 | throwsProviderNotFound(), 83 | ); 84 | 85 | // p2 can access only p1 86 | expect(Provider.of(k2.currentContext!, listen: false), 42); 87 | expect( 88 | () => Provider.of(k2.currentContext!, listen: false), 89 | throwsProviderNotFound(), 90 | ); 91 | expect( 92 | () => Provider.of(k2.currentContext!, listen: false), 93 | throwsProviderNotFound(), 94 | ); 95 | 96 | // p3 can access both p1 and p2 97 | expect(Provider.of(k3.currentContext!, listen: false), 42); 98 | expect(Provider.of(k3.currentContext!, listen: false), 'foo'); 99 | expect( 100 | () => Provider.of(k3.currentContext!, listen: false), 101 | throwsProviderNotFound(), 102 | ); 103 | 104 | // the child can access them all 105 | expect(Provider.of(keyChild.currentContext!, listen: false), 42); 106 | expect( 107 | Provider.of(keyChild.currentContext!, listen: false), 108 | 'foo', 109 | ); 110 | expect(Provider.of(keyChild.currentContext!, listen: false), 44); 111 | }); 112 | 113 | testWidgets('MultiProvider.providers with ignored child', (tester) async { 114 | final p1 = Provider.value( 115 | value: 42, 116 | child: const Text('Bar'), 117 | ); 118 | 119 | await tester.pumpWidget(MultiProvider( 120 | providers: [p1], 121 | child: const Text('Foo', textDirection: TextDirection.ltr), 122 | )); 123 | 124 | expect(find.text('Bar'), findsNothing); 125 | expect(find.text('Foo'), findsOneWidget); 126 | }); 127 | }); 128 | } 129 | 130 | class SubMulti extends MultiProvider { 131 | SubMulti({ 132 | Key? key, 133 | required List providers, 134 | required Widget child, 135 | }) : super(key: key, providers: providers, child: child); 136 | } 137 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/reassemble_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | class _ReassembleHandler extends ReassembleHandler { 6 | bool hasReassemble = false; 7 | 8 | @override 9 | void reassemble() { 10 | hasReassemble = true; 11 | } 12 | } 13 | 14 | void main() { 15 | testWidgets('ReassembleHandler', (tester) async { 16 | final provider = _ReassembleHandler(); 17 | 18 | await tester.pumpWidget( 19 | Provider.value( 20 | value: provider, 21 | child: const SizedBox(), 22 | ), 23 | ); 24 | 25 | // ignore: unawaited_futures 26 | tester.binding.reassembleApplication(); 27 | await tester.pump(); 28 | 29 | expect(provider.hasReassemble, equals(true)); 30 | }); 31 | 32 | testWidgets('unevaluated create', (tester) async { 33 | final provider = _ReassembleHandler(); 34 | 35 | await tester.pumpWidget( 36 | Provider( 37 | create: (_) => provider, 38 | child: const SizedBox(), 39 | ), 40 | ); 41 | 42 | // ignore: unawaited_futures 43 | tester.binding.reassembleApplication(); 44 | await tester.pump(); 45 | 46 | expect(provider.hasReassemble, equals(false)); 47 | }); 48 | 49 | testWidgets('unevaluated create', (tester) async { 50 | final provider = _ReassembleHandler(); 51 | 52 | await tester.pumpWidget( 53 | Provider( 54 | create: (_) => provider, 55 | builder: (context, _) { 56 | context.watch<_ReassembleHandler>(); 57 | return Container(); 58 | }, 59 | ), 60 | ); 61 | 62 | // ignore: unawaited_futures 63 | tester.binding.reassembleApplication(); 64 | await tester.pump(); 65 | 66 | expect(provider.hasReassemble, equals(true)); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/stateful_provider_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:provider/src/provider.dart'; 6 | 7 | import 'common.dart'; 8 | 9 | class ValueBuilder extends Mock { 10 | int? call(BuildContext? context); 11 | } 12 | 13 | class Dispose extends Mock { 14 | void call(BuildContext context, int value); 15 | } 16 | 17 | void main() { 18 | testWidgets('works with MultiProvider', (tester) async { 19 | await tester.pumpWidget( 20 | MultiProvider( 21 | providers: [ 22 | Provider( 23 | create: (_) => 42, 24 | ), 25 | ], 26 | child: TextOf(), 27 | ), 28 | ); 29 | 30 | expect(find.text('42'), findsOneWidget); 31 | }); 32 | 33 | testWidgets('calls create only once', (tester) async { 34 | final create = ValueBuilder(); 35 | 36 | await tester.pumpWidget(Provider( 37 | create: create, 38 | child: TextOf(), 39 | )); 40 | 41 | await tester.pumpWidget(Provider( 42 | create: create, 43 | child: TextOf(), 44 | )); 45 | 46 | await tester.pumpWidget(Container()); 47 | 48 | verify(create(any)).called(1); 49 | }); 50 | 51 | testWidgets('dispose', (tester) async { 52 | final dispose = Dispose(); 53 | 54 | await tester.pumpWidget( 55 | Provider( 56 | create: (_) => 42, 57 | dispose: dispose, 58 | child: TextOf(), 59 | ), 60 | ); 61 | 62 | final context = findInheritedContext(); 63 | 64 | verifyZeroInteractions(dispose); 65 | await tester.pumpWidget(Container()); 66 | verify(dispose(context, 42)).called(1); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/stream_provider_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:mockito/mockito.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import 'common.dart'; 9 | 10 | class ErrorBuilderMock extends Mock { 11 | ErrorBuilderMock(this.fallback); 12 | 13 | final T fallback; 14 | 15 | T call(BuildContext? context, Object? error) { 16 | return super.noSuchMethod( 17 | Invocation.method(#call, [context, error]), 18 | returnValue: fallback, 19 | returnValueForMissingStub: fallback, 20 | ) as T; 21 | } 22 | } 23 | 24 | void main() { 25 | testWidgets('works with MultiProvider', (tester) async { 26 | await tester.pumpWidget( 27 | MultiProvider( 28 | providers: [ 29 | StreamProvider( 30 | initialData: 0, 31 | create: (_) => Stream.value(42), 32 | ), 33 | ], 34 | child: TextOf(), 35 | ), 36 | ); 37 | 38 | expect(find.text('0'), findsOneWidget); 39 | 40 | await Future.microtask(tester.pump); 41 | 42 | expect(find.text('42'), findsOneWidget); 43 | }); 44 | 45 | testWidgets( 46 | 'transition from stream to stream preserve state', 47 | (tester) async { 48 | final controller = StreamController(sync: true); 49 | final controller2 = StreamController(sync: true); 50 | 51 | await tester.pumpWidget( 52 | StreamProvider.value( 53 | initialData: 0, 54 | value: controller.stream, 55 | child: TextOf(), 56 | ), 57 | ); 58 | 59 | expect(find.text('0'), findsOneWidget); 60 | 61 | controller.add(1); 62 | 63 | await tester.pump(); 64 | 65 | expect(find.text('1'), findsOneWidget); 66 | 67 | await tester.pumpWidget( 68 | StreamProvider.value( 69 | initialData: 0, 70 | value: controller2.stream, 71 | child: TextOf(), 72 | ), 73 | ); 74 | 75 | expect(find.text('1'), findsOneWidget); 76 | 77 | controller.add(0); 78 | await tester.pump(); 79 | 80 | expect(find.text('1'), findsOneWidget); 81 | 82 | controller2.add(2); 83 | await tester.pump(); 84 | 85 | expect(find.text('2'), findsOneWidget); 86 | 87 | await tester.pump(); 88 | // ignore: unawaited_futures 89 | controller.close(); 90 | // ignore: unawaited_futures 91 | controller2.close(); 92 | }, 93 | ); 94 | testWidgets('throws if stream has error and catchError is missing', 95 | (tester) async { 96 | final controller = StreamController(); 97 | 98 | await tester.pumpWidget( 99 | StreamProvider.value( 100 | initialData: -1, 101 | value: controller.stream, 102 | child: TextOf(), 103 | ), 104 | ); 105 | 106 | controller.addError(42); 107 | await Future.microtask(tester.pump); 108 | 109 | final dynamic exception = tester.takeException(); 110 | expect(exception, isFlutterError); 111 | expect(exception.toString(), equals(''' 112 | An exception was throw by _ControllerStream listened by 113 | StreamProvider, but no `catchError` was provided. 114 | 115 | Exception: 116 | 42 117 | ''')); 118 | 119 | // ignore: unawaited_futures 120 | controller.close(); 121 | }); 122 | 123 | testWidgets('calls catchError if present and stream has error', 124 | (tester) async { 125 | final controller = StreamController(sync: true); 126 | final catchError = ErrorBuilderMock(0); 127 | when(catchError(any, 42)).thenReturn(42); 128 | 129 | await tester.pumpWidget( 130 | StreamProvider.value( 131 | initialData: -1, 132 | value: controller.stream, 133 | catchError: catchError, 134 | child: TextOf(), 135 | ), 136 | ); 137 | 138 | expect(find.text('-1'), findsOneWidget); 139 | 140 | controller.addError(42); 141 | 142 | await Future.microtask(tester.pump); 143 | 144 | expect(find.text('42'), findsOneWidget); 145 | verify(catchError(argThat(isNotNull), 42)).called(1); 146 | verifyNoMoreInteractions(catchError); 147 | 148 | // ignore: unawaited_futures 149 | controller.close(); 150 | }); 151 | 152 | testWidgets('works with null', (tester) async { 153 | await tester.pumpWidget( 154 | StreamProvider.value( 155 | initialData: 42, 156 | value: null, 157 | child: TextOf(), 158 | ), 159 | ); 160 | 161 | expect(find.text('42'), findsOneWidget); 162 | 163 | await tester.pumpWidget(Container()); 164 | }); 165 | 166 | group('StreamProvider()', () { 167 | testWidgets('create and dispose stream with builder', (tester) async { 168 | final stream = StreamMock(); 169 | final sub = StreamSubscriptionMock(); 170 | when(stream.listen(any, onError: anyNamed('onError'))).thenReturn(sub); 171 | 172 | final builder = InitialValueBuilderMock(stream); 173 | 174 | await tester.pumpWidget( 175 | StreamProvider( 176 | initialData: -1, 177 | create: builder, 178 | child: TextOf(), 179 | ), 180 | ); 181 | 182 | verify(builder(argThat(isNotNull))).called(1); 183 | 184 | verify(stream.listen(any, onError: anyNamed('onError'))).called(1); 185 | verifyNoMoreInteractions(stream); 186 | 187 | await tester.pumpWidget(Container()); 188 | 189 | verifyNoMoreInteractions(builder); 190 | verify(sub.cancel()).called(1); 191 | verifyNoMoreInteractions(sub); 192 | verifyNoMoreInteractions(stream); 193 | }); 194 | }); 195 | } 196 | -------------------------------------------------------------------------------- /packages/provider/test/null_safe/value_listenable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'common.dart'; 8 | 9 | class ValueNotifierMock extends Mock implements ValueNotifier { 10 | ValueNotifierMock(this.fallbackValue); 11 | 12 | final T fallbackValue; 13 | 14 | @override 15 | T get value => super.noSuchMethod( 16 | Invocation.getter(#value), 17 | returnValue: fallbackValue, 18 | returnValueForMissingStub: fallbackValue, 19 | ) as T; 20 | 21 | @override 22 | void addListener(VoidCallback? listener) { 23 | super.noSuchMethod( 24 | Invocation.method(#addListener, [listener]), 25 | ); 26 | } 27 | 28 | @override 29 | void removeListener(VoidCallback? listener) { 30 | super.noSuchMethod( 31 | Invocation.method(#removeListener, [listener]), 32 | ); 33 | } 34 | } 35 | 36 | void main() { 37 | group('valueListenableProvider', () { 38 | testWidgets('rebuilds when value change', (tester) async { 39 | final listenable = ValueNotifier(0); 40 | addTearDown(listenable.dispose); 41 | 42 | final child = Builder( 43 | builder: (context) { 44 | return Text( 45 | Provider.of(context).toString(), 46 | textDirection: TextDirection.ltr, 47 | ); 48 | }, 49 | ); 50 | 51 | await tester.pumpWidget( 52 | ValueListenableProvider.value( 53 | value: listenable, 54 | child: child, 55 | ), 56 | ); 57 | 58 | expect(find.text('0'), findsOneWidget); 59 | listenable.value++; 60 | await tester.pump(); 61 | expect(find.text('1'), findsOneWidget); 62 | }); 63 | 64 | testWidgets("don't rebuild dependents by default", (tester) async { 65 | var buildCount = 0; 66 | final listenable = ValueNotifier(0); 67 | addTearDown(listenable.dispose); 68 | 69 | final child = Builder(builder: (context) { 70 | buildCount++; 71 | return Container(); 72 | }); 73 | 74 | await tester.pumpWidget( 75 | ValueListenableProvider.value( 76 | value: listenable, 77 | child: child, 78 | ), 79 | ); 80 | 81 | expect(buildCount, 1); 82 | 83 | await tester.pumpWidget( 84 | ValueListenableProvider.value( 85 | value: listenable, 86 | child: child, 87 | ), 88 | ); 89 | 90 | expect(buildCount, 1); 91 | }); 92 | 93 | testWidgets('pass keys', (tester) async { 94 | final key = GlobalKey(); 95 | final valueNotifier = ValueNotifier(42); 96 | addTearDown(valueNotifier.dispose); 97 | 98 | await tester.pumpWidget( 99 | ValueListenableProvider.value( 100 | key: key, 101 | value: valueNotifier, 102 | child: Container(), 103 | ), 104 | ); 105 | 106 | expect(key.currentWidget, isInstanceOf>()); 107 | }); 108 | 109 | testWidgets("don't listen again if Value instance doesn't change", 110 | (tester) async { 111 | final valueNotifier = ValueNotifierMock(0); 112 | await tester.pumpWidget( 113 | ValueListenableProvider.value( 114 | value: valueNotifier, 115 | child: TextOf(), 116 | ), 117 | ); 118 | await tester.pumpWidget( 119 | ValueListenableProvider.value( 120 | value: valueNotifier, 121 | child: TextOf(), 122 | ), 123 | ); 124 | 125 | verify(valueNotifier.addListener(any)).called(1); 126 | verify(valueNotifier.value); 127 | verifyNoMoreInteractions(valueNotifier); 128 | }); 129 | 130 | testWidgets('pass updateShouldNotify', (tester) async { 131 | final notifier = ValueNotifier(0); 132 | addTearDown(notifier.dispose); 133 | 134 | final shouldNotify = UpdateShouldNotifyMock(); 135 | when(shouldNotify(0, 1)).thenReturn(true); 136 | 137 | await tester.pumpWidget( 138 | ValueListenableProvider.value( 139 | value: notifier, 140 | updateShouldNotify: shouldNotify, 141 | child: TextOf(), 142 | ), 143 | ); 144 | 145 | verifyZeroInteractions(shouldNotify); 146 | 147 | notifier.value++; 148 | await tester.pump(); 149 | 150 | verify(shouldNotify(0, 1)).called(1); 151 | verifyNoMoreInteractions(shouldNotify); 152 | }); 153 | 154 | test('has correct debugFillProperties', () { 155 | final builder = DiagnosticPropertiesBuilder(); 156 | final notifier = ValueNotifier(0); 157 | ValueListenableProvider.value(value: notifier, child: const SizedBox()) 158 | .debugFillProperties(builder); 159 | final description = builder.properties 160 | .where( 161 | (DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info), 162 | ) 163 | .map((DiagnosticsNode node) => node.toString()) 164 | .toList(); 165 | expect(description, ['value: 0']); 166 | }); 167 | }); 168 | } 169 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "972d36c4efeee7e3bb16c050fd233389625a6470" 8 | channel: "[user-branch]" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 972d36c4efeee7e3bb16c050fd233389625a6470 17 | base_revision: 972d36c4efeee7e3bb16c050fd233389625a6470 18 | - platform: web 19 | create_revision: 972d36c4efeee7e3bb16c050fd233389625a6470 20 | base_revision: 972d36c4efeee7e3bb16c050fd233389625a6470 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | Initial release -------------------------------------------------------------------------------- /packages/provider_devtools_extension/README.md: -------------------------------------------------------------------------------- 1 | This is the source of the provider's devtool. 2 | 3 | You can locally run it with: 4 | 5 | ``` 6 | flutter run -d chrome --dart-define=use_simulated_environment=true 7 | ``` -------------------------------------------------------------------------------- /packages/provider_devtools_extension/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | 30 | analyzer: 31 | exclude: 32 | - "**/*.freezed.dart" -------------------------------------------------------------------------------- /packages/provider_devtools_extension/integration_test/provider_integration_test.dart: -------------------------------------------------------------------------------- 1 | // TODO: write integration test as proper integration test. 2 | // See legacy test here: 3 | // https://github.com/flutter/devtools/blob/master/packages/devtools_app/test/provider/provider_screen_integration_test.dart 4 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:devtools_extensions/devtools_extensions.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import 'src/provider_screen.dart'; 6 | import 'src/utils/riverpod_error_logger_observer.dart'; 7 | 8 | void main() { 9 | runApp(const ProviderScope( 10 | observers: [ErrorLoggerObserver()], 11 | child: ProviderDevToolsExtension(), 12 | )); 13 | } 14 | 15 | class ProviderDevToolsExtension extends StatelessWidget { 16 | const ProviderDevToolsExtension({super.key}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return const DevToolsExtension( 21 | child: ProviderScreenBody(), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/instance_viewer/eval.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | /// A few utilities related to evaluating dart code 6 | 7 | library eval; 8 | 9 | import 'dart:async'; 10 | 11 | import 'package:devtools_app_shared/service.dart'; 12 | import 'package:devtools_extensions/devtools_extensions.dart'; 13 | import 'package:flutter/foundation.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | import 'package:vm_service/vm_service.dart'; 16 | 17 | Stream get _serviceConnectionStream => 18 | _serviceConnectionStreamController.stream; 19 | final _serviceConnectionStreamController = 20 | StreamController.broadcast(); 21 | void setServiceConnectionForProviderScreen(VmService service) { 22 | _serviceConnectionStreamController.add(service); 23 | } 24 | 25 | /// Exposes the current VmServiceWrapper. 26 | /// By listening to this provider instead of directly accessing `serviceManager.service`, 27 | /// this ensures that providers reload properly when the devtool is connected 28 | /// to a different application. 29 | final serviceProvider = StreamProvider((ref) async* { 30 | yield serviceManager.service!; 31 | yield* _serviceConnectionStream; 32 | }); 33 | 34 | /// An [EvalOnDartLibrary] that has access to no specific library in particular 35 | /// 36 | /// Not suitable to be used when evaluating third-party objects, as it would 37 | /// otherwise not be possible to read private properties. 38 | final evalProvider = libraryEvalProvider('dart:io'); 39 | 40 | /// An [EvalOnDartLibrary] that has access to `provider` 41 | final providerEvalProvider = 42 | libraryEvalProvider('package:provider/src/provider.dart'); 43 | 44 | /// An [EvalOnDartLibrary] for custom objects. 45 | final libraryEvalProvider = 46 | FutureProviderFamily((ref, libraryPath) async { 47 | final service = await ref.watch(serviceProvider.future); 48 | 49 | final eval = EvalOnDartLibrary( 50 | libraryPath, 51 | service, 52 | serviceManager: serviceManager, 53 | ); 54 | ref.onDispose(eval.dispose); 55 | return eval; 56 | }); 57 | 58 | final hotRestartEventProvider = 59 | ChangeNotifierProvider>((ref) { 60 | final selectedIsolateListenable = 61 | serviceManager.isolateManager.selectedIsolate; 62 | 63 | // Since ChangeNotifierProvider calls `dispose` on the returned ChangeNotifier 64 | // when the provider is destroyed, we can't simply return `selectedIsolateListenable`. 65 | // So we're making a copy of it instead. 66 | final notifier = ValueNotifier(selectedIsolateListenable.value); 67 | 68 | void listener() => notifier.value = selectedIsolateListenable.value; 69 | selectedIsolateListenable.addListener(listener); 70 | ref.onDispose(() => selectedIsolateListenable.removeListener(listener)); 71 | 72 | return notifier; 73 | }); 74 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/instance_viewer/fake_freezed_annotation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // This file contains annotations that behaves like package:freezed_annotation. 6 | // This allows using Freezed without having the devtool depend on the package. 7 | // We could instead remove the annotations, but that would make the process of 8 | // updating the generated files tedious. 9 | 10 | const nullable = Object(); 11 | const freezed = Object(); 12 | 13 | class Default { 14 | const Default(Object value); 15 | } 16 | 17 | class Assert { 18 | const Assert(String exp); 19 | } 20 | 21 | class JsonKey { 22 | const JsonKey({ 23 | bool? ignore, 24 | Object? defaultValue, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/instance_viewer/instance_details.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:devtools_app_shared/service.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:vm_service/vm_service.dart'; 9 | 10 | import 'fake_freezed_annotation.dart'; 11 | import 'result.dart'; 12 | 13 | // This part is generated using `package:freezed`, but without the devtool depending 14 | // on the package. 15 | // To update the generated files, temporarily add freezed/freezed_annotation/build_runner 16 | // as dependencies; replace the `fake_freezed_annotation.dart` import with the 17 | // real annotation package, then execute `pub run build_runner build`. 18 | part 'instance_details.freezed.dart'; 19 | 20 | typedef Setter = Future Function(String newValue); 21 | 22 | @freezed 23 | class PathToProperty with _$PathToProperty { 24 | const factory PathToProperty.listIndex(int index) = ListIndexPath; 25 | 26 | // TODO test that mutating a Map does not collapse previously expanded keys 27 | const factory PathToProperty.mapKey({ 28 | required String? ref, 29 | }) = MapKeyPath; 30 | 31 | /// Must not depend on [InstanceRef] and its ID, as they may change across 32 | /// re-evaluations of the object. 33 | /// Depending on those would lead to the UI collapsing previously expanded objects 34 | /// because the new path to a property would be different. 35 | /// 36 | /// We can't just rely on the property name either, because in some cases 37 | /// an object can have multiple properties with the same name (private properties 38 | /// defined in different libraries) 39 | const factory PathToProperty.objectProperty({ 40 | required String name, 41 | 42 | /// Path to the class/mixin that defined this property 43 | required String ownerUri, 44 | 45 | /// Name of the class/mixin that defined this property 46 | required String ownerName, 47 | }) = PropertyPath; 48 | 49 | factory PathToProperty.fromObjectField(ObjectField field) { 50 | return PathToProperty.objectProperty( 51 | name: field.name, 52 | ownerUri: field.ownerUri, 53 | ownerName: field.ownerName, 54 | ); 55 | } 56 | } 57 | 58 | @freezed 59 | class ObjectField with _$ObjectField { 60 | factory ObjectField({ 61 | required String name, 62 | required bool isFinal, 63 | required String ownerName, 64 | required String ownerUri, 65 | required Result ref, 66 | 67 | /// An [EvalOnDartLibrary] that can access this field from the owner object 68 | required EvalOnDartLibrary eval, 69 | 70 | /// Whether this field was defined by the inspected app or by one of its dependencies 71 | /// 72 | /// This is used by the UI to hide variables that are not useful for the user. 73 | required bool isDefinedByDependency, 74 | }) = _ObjectField; 75 | 76 | ObjectField._(); 77 | 78 | bool get isPrivate => name.startsWith('_'); 79 | } 80 | 81 | @freezed 82 | class InstanceDetails with _$InstanceDetails { 83 | InstanceDetails._(); 84 | 85 | factory InstanceDetails.nill({ 86 | required Setter? setter, 87 | }) = NullInstance; 88 | 89 | factory InstanceDetails.boolean( 90 | String displayString, { 91 | required String instanceRefId, 92 | required Setter? setter, 93 | }) = BoolInstance; 94 | 95 | factory InstanceDetails.number( 96 | String displayString, { 97 | required String instanceRefId, 98 | required Setter? setter, 99 | }) = NumInstance; 100 | 101 | factory InstanceDetails.string( 102 | String displayString, { 103 | required String instanceRefId, 104 | required Setter? setter, 105 | }) = StringInstance; 106 | 107 | factory InstanceDetails.map( 108 | List keys, { 109 | required int hash, 110 | required String instanceRefId, 111 | required Setter? setter, 112 | }) = MapInstance; 113 | 114 | factory InstanceDetails.list({ 115 | required int length, 116 | required int hash, 117 | required String instanceRefId, 118 | required Setter? setter, 119 | }) = ListInstance; 120 | 121 | factory InstanceDetails.object( 122 | List fields, { 123 | required String type, 124 | required int hash, 125 | required String instanceRefId, 126 | required Setter? setter, 127 | 128 | /// An [EvalOnDartLibrary] associated with the library of this object 129 | /// 130 | /// This allows to edit private properties. 131 | required EvalOnDartLibrary evalForInstance, 132 | }) = ObjectInstance; 133 | 134 | factory InstanceDetails.enumeration({ 135 | required String type, 136 | required String value, 137 | required Setter? setter, 138 | required String instanceRefId, 139 | }) = EnumInstance; 140 | 141 | bool get isExpandable { 142 | bool falsy(Object _) => false; 143 | 144 | return map( 145 | nill: falsy, 146 | boolean: falsy, 147 | number: falsy, 148 | string: falsy, 149 | enumeration: falsy, 150 | map: (instance) => instance.keys.isNotEmpty, 151 | list: (instance) => instance.length > 0, 152 | object: (instance) => instance.fields.isNotEmpty, 153 | ); 154 | } 155 | 156 | // Since `nil` doesn't have those properties, we are manually exposing them 157 | String? get instanceRefId { 158 | return map( 159 | nill: (_) => null, 160 | boolean: (a) => a.instanceRefId, 161 | number: (a) => a.instanceRefId, 162 | string: (a) => a.instanceRefId, 163 | map: (a) => a.instanceRefId, 164 | list: (a) => a.instanceRefId, 165 | object: (a) => a.instanceRefId, 166 | enumeration: (a) => a.instanceRefId, 167 | ); 168 | } 169 | } 170 | 171 | /// The path to visit child elements of an [Instance] or providers from `provider`/`riverpod`. 172 | @freezed 173 | class InstancePath with _$InstancePath { 174 | const InstancePath._(); 175 | 176 | const factory InstancePath.fromInstanceId( 177 | String instanceId, { 178 | @Default([]) List pathToProperty, 179 | }) = _InstancePathFromInstanceId; 180 | 181 | const factory InstancePath.fromProviderId( 182 | String providerId, { 183 | @Default([]) List pathToProperty, 184 | }) = _InstancePathFromProviderId; 185 | 186 | InstancePath get root => copyWith(pathToProperty: []); 187 | 188 | InstancePath? get parent { 189 | if (pathToProperty.isEmpty) return null; 190 | 191 | return copyWith( 192 | pathToProperty: [ 193 | for (var i = 0; i + 1 < pathToProperty.length; i++) pathToProperty[i], 194 | ], 195 | ); 196 | } 197 | 198 | InstancePath pathForChild(PathToProperty property) { 199 | return copyWith( 200 | pathToProperty: [...pathToProperty, property], 201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/instance_viewer/result.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:collection/collection.dart'; 6 | import 'package:devtools_app_shared/service.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:vm_service/vm_service.dart' hide SentinelException, Error; 9 | 10 | import 'fake_freezed_annotation.dart'; 11 | 12 | // This part is generated using `package:freezed`, but without the devtool depending 13 | // on the package. 14 | // To update the generated files, temporarily add freezed/freezed_annotation/build_runner 15 | // as dependencies; replace the `fake_freezed_annotation.dart` import with the 16 | // real annotation package, then execute `pub run build_runner build`. 17 | part 'result.freezed.dart'; 18 | 19 | @freezed 20 | class Result with _$Result { 21 | Result._(); 22 | factory Result.data(T value) = _ResultData; 23 | factory Result.error(Object error, StackTrace stackTrace) = _ResultError; 24 | 25 | factory Result.guard(T Function() cb) { 26 | try { 27 | return Result.data(cb()); 28 | } catch (err, stack) { 29 | return Result.error(err, stack); 30 | } 31 | } 32 | 33 | static Future> guardFuture(Future Function() cb) async { 34 | try { 35 | return Result.data(await cb()); 36 | } catch (err, stack) { 37 | return Result.error(err, stack); 38 | } 39 | } 40 | 41 | Result chain(Res Function(T value) cb) { 42 | return when( 43 | data: (value) { 44 | try { 45 | return Result.data(cb(value)); 46 | } catch (err, stack) { 47 | return Result.error(err, stack); 48 | } 49 | }, 50 | error: (err, stack) => Result.error(err, stack), 51 | ); 52 | } 53 | 54 | T get dataOrThrow { 55 | return when( 56 | data: (value) => value, 57 | error: Error.throwWithStackTrace, 58 | ); 59 | } 60 | } 61 | 62 | Result parseSentinel(Object? value) { 63 | if (value is T) return Result.data(value); 64 | 65 | if (value == null) { 66 | return Result.error( 67 | ArgumentError( 68 | 'Expected $value to be an instance of $T but received `null`', 69 | ), 70 | StackTrace.current, 71 | ); 72 | } 73 | 74 | if (value is Sentinel) { 75 | return Result.error( 76 | SentinelException(value), 77 | StackTrace.current, 78 | ); 79 | } 80 | 81 | return Result.error(value, StackTrace.current); 82 | } 83 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/provider_list.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:devtools_app_shared/ui.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | import 'provider_nodes.dart'; 10 | 11 | const _tilePadding = EdgeInsets.only( 12 | left: defaultSpacing, 13 | right: densePadding, 14 | top: densePadding, 15 | bottom: densePadding, 16 | ); 17 | 18 | final AutoDisposeStateNotifierProvider, String?> 19 | selectedProviderIdProvider = 20 | AutoDisposeStateNotifierProvider, String?>( 21 | (ref) { 22 | final controller = StateController(null); 23 | 24 | ref.listen>>( 25 | sortedProviderNodesProvider, 26 | (prev, value) { 27 | final nodes = value.asData?.value; 28 | if (nodes == null) return; 29 | 30 | if (controller.state == null) { 31 | if (nodes.isNotEmpty) controller.state = nodes.first.id; 32 | return; 33 | } 34 | 35 | if (nodes.isEmpty) { 36 | controller.state = null; 37 | } 38 | 39 | /// The previously selected provider was unmounted 40 | else if (!nodes.any((node) => node.id == controller.state)) { 41 | controller.state = nodes.first.id; 42 | } 43 | }, 44 | fireImmediately: true, 45 | ); 46 | 47 | return controller; 48 | }, 49 | name: 'selectedProviderIdProvider', 50 | ); 51 | 52 | class ProviderList extends ConsumerStatefulWidget { 53 | const ProviderList({Key? key}) : super(key: key); 54 | 55 | @override 56 | ConsumerState createState() => _ProviderListState(); 57 | } 58 | 59 | class _ProviderListState extends ConsumerState { 60 | final scrollController = ScrollController(); 61 | 62 | @override 63 | void dispose() { 64 | scrollController.dispose(); 65 | super.dispose(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | final nodes = ref.watch(sortedProviderNodesProvider); 71 | 72 | return nodes.when( 73 | loading: () => const Center(child: CircularProgressIndicator()), 74 | error: (err, stack) => Padding( 75 | padding: _tilePadding, 76 | child: Text('\n$stack'), 77 | ), 78 | data: (nodes) { 79 | return Scrollbar( 80 | controller: scrollController, 81 | thumbVisibility: true, 82 | child: ListView.builder( 83 | primary: false, 84 | controller: scrollController, 85 | itemCount: nodes.length, 86 | itemBuilder: (context, index) { 87 | final node = nodes[index]; 88 | return ProviderNodeItem( 89 | key: Key('provider-${node.id}'), 90 | node: node, 91 | ); 92 | }, 93 | ), 94 | ); 95 | }, 96 | ); 97 | } 98 | } 99 | 100 | class ProviderNodeItem extends ConsumerWidget { 101 | const ProviderNodeItem({ 102 | Key? key, 103 | required this.node, 104 | }) : super(key: key); 105 | 106 | final ProviderNode node; 107 | 108 | @override 109 | Widget build(BuildContext context, WidgetRef ref) { 110 | final isSelected = ref.watch(selectedProviderIdProvider) == node.id; 111 | 112 | final colorScheme = Theme.of(context).colorScheme; 113 | final backgroundColor = 114 | isSelected ? colorScheme.selectedRowBackgroundColor : null; 115 | 116 | return GestureDetector( 117 | behavior: HitTestBehavior.opaque, 118 | onTap: () { 119 | ref.read(selectedProviderIdProvider.notifier).state = node.id; 120 | }, 121 | child: Container( 122 | color: backgroundColor, 123 | padding: _tilePadding, 124 | child: Text('${node.type}()'), 125 | ), 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/provider_nodes.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:devtools_app_shared/service.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 10 | import 'package:vm_service/vm_service.dart'; 11 | 12 | import 'instance_viewer/eval.dart'; 13 | 14 | @immutable 15 | class ProviderNode { 16 | const ProviderNode({ 17 | required this.id, 18 | required this.type, 19 | }); 20 | 21 | final String id; 22 | final String type; 23 | } 24 | 25 | final _providerListChanged = AutoDisposeStreamProvider((ref) async* { 26 | final service = await ref.watch(serviceProvider.future); 27 | 28 | yield* service.onExtensionEvent.where((event) { 29 | return event.extensionKind == 'provider:provider_list_changed'; 30 | }); 31 | }); 32 | 33 | final _rawProviderIdsProvider = AutoDisposeFutureProvider>( 34 | (ref) async { 35 | // recompute the list of providers on hot-restart 36 | ref.watch(hotRestartEventProvider); 37 | // cause the list of providers to be re-evaluated when notified of a change 38 | ref.watch(_providerListChanged); 39 | 40 | final isAlive = Disposable(); 41 | ref.onDispose(isAlive.dispose); 42 | 43 | final eval = await ref.watch(providerEvalProvider.future); 44 | 45 | final providerIdRefs = await eval.evalInstance( 46 | 'ProviderBinding.debugInstance.providerDetails.keys.toList()', 47 | isAlive: isAlive, 48 | ); 49 | 50 | final providerIdInstances = await Future.wait([ 51 | for (final idRef in providerIdRefs.elements!.cast()) 52 | eval.safeGetInstance(idRef, isAlive), 53 | ]); 54 | 55 | return [ 56 | for (final idInstance in providerIdInstances) idInstance.valueAsString!, 57 | ]; 58 | }, 59 | name: '_rawProviderIdsProvider', 60 | ); 61 | 62 | final _rawProviderNodeProvider = 63 | AutoDisposeFutureProviderFamily( 64 | (ref, id) async { 65 | // recompute the providers informations on hot-restart 66 | ref.watch(hotRestartEventProvider); 67 | 68 | final isAlive = Disposable(); 69 | ref.onDispose(isAlive.dispose); 70 | 71 | final eval = await ref.watch(providerEvalProvider.future); 72 | 73 | final providerNodeInstance = await eval.evalInstance( 74 | "ProviderBinding.debugInstance.providerDetails['$id']", 75 | isAlive: isAlive, 76 | ); 77 | 78 | Future getFieldWithName(String name) { 79 | return eval.safeGetInstance( 80 | providerNodeInstance.fields! 81 | .firstWhere((e) => e.decl?.name == name) 82 | .value as InstanceRef, 83 | isAlive, 84 | ); 85 | } 86 | 87 | final type = await getFieldWithName('type'); 88 | 89 | return ProviderNode( 90 | id: id, 91 | type: type.valueAsString!, 92 | ); 93 | }, 94 | name: '_rawProviderNodeProvider', 95 | ); 96 | 97 | /// Combines [providerIdsProvider] with [providerNodeProvider] to obtain all 98 | /// the [ProviderNode]s at once, sorted alphabetically. 99 | final sortedProviderNodesProvider = 100 | AutoDisposeFutureProvider>((ref) async { 101 | final ids = await ref.watch(_rawProviderIdsProvider.future); 102 | 103 | final nodes = await Future.wait( 104 | ids.map((id) => ref.watch(_rawProviderNodeProvider(id).future)), 105 | ); 106 | 107 | return nodes.toList()..sort((a, b) => a.type.compareTo(b.type)); 108 | }); 109 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/provider_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:collection/collection.dart'; 4 | import 'package:devtools_app_shared/ui.dart'; 5 | import 'package:devtools_extensions/devtools_extensions.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | 10 | import 'instance_viewer/instance_details.dart'; 11 | import 'instance_viewer/instance_providers.dart'; 12 | import 'instance_viewer/instance_viewer.dart'; 13 | import 'provider_list.dart'; 14 | import 'provider_nodes.dart'; 15 | 16 | final _hasErrorProvider = Provider.autoDispose((ref) { 17 | if (ref.watch(sortedProviderNodesProvider) is AsyncError) return true; 18 | 19 | final selectedProviderId = ref.watch(selectedProviderIdProvider); 20 | 21 | if (selectedProviderId == null) return false; 22 | 23 | final instance = ref.watch( 24 | instanceProvider(InstancePath.fromProviderId(selectedProviderId)), 25 | ); 26 | 27 | return instance is AsyncError; 28 | }); 29 | 30 | final _selectedProviderNode = AutoDisposeProvider((ref) { 31 | final selectedId = ref.watch(selectedProviderIdProvider); 32 | 33 | return ref.watch(sortedProviderNodesProvider).asData?.value.firstWhereOrNull( 34 | (node) => node.id == selectedId, 35 | ); 36 | }); 37 | 38 | final _showInternals = StateProvider((ref) => false); 39 | 40 | class ProviderScreenBody extends ConsumerWidget { 41 | const ProviderScreenBody({Key? key}) : super(key: key); 42 | 43 | @override 44 | Widget build(BuildContext context, WidgetRef ref) { 45 | final splitAxis = Split.axisFor(context, 0.85); 46 | 47 | // A provider will automatically be selected as soon as one is detected 48 | final selectedProviderId = ref.watch(selectedProviderIdProvider); 49 | final detailsTitleText = selectedProviderId != null 50 | ? ref.watch(_selectedProviderNode)?.type ?? '' 51 | : '[No provider selected]'; 52 | 53 | ref.listen(_hasErrorProvider, (_, hasError) { 54 | if (hasError) showProviderErrorBanner(); 55 | }); 56 | 57 | return Split( 58 | axis: splitAxis, 59 | initialFractions: const [0.33, 0.67], 60 | children: [ 61 | const RoundedOutlinedBorder( 62 | clip: true, 63 | child: Column( 64 | children: [ 65 | AreaPaneHeader( 66 | roundedTopBorder: false, 67 | includeTopBorder: false, 68 | title: Text('Providers'), 69 | ), 70 | Expanded( 71 | child: ProviderList(), 72 | ), 73 | ], 74 | ), 75 | ), 76 | RoundedOutlinedBorder( 77 | clip: true, 78 | child: Column( 79 | children: [ 80 | AreaPaneHeader( 81 | roundedTopBorder: false, 82 | includeTopBorder: false, 83 | title: Text(detailsTitleText), 84 | actions: [ 85 | IconButton( 86 | icon: const Icon(Icons.settings), 87 | onPressed: () { 88 | unawaited( 89 | showDialog( 90 | context: context, 91 | builder: (_) => _StateInspectorSettingsDialog(), 92 | ), 93 | ); 94 | }, 95 | ), 96 | ], 97 | ), 98 | if (selectedProviderId != null) 99 | Expanded( 100 | child: InstanceViewer( 101 | rootPath: InstancePath.fromProviderId(selectedProviderId), 102 | showInternalProperties: ref.watch(_showInternals), 103 | ), 104 | ), 105 | ], 106 | ), 107 | ), 108 | ], 109 | ); 110 | } 111 | } 112 | 113 | void showProviderErrorBanner() { 114 | extensionManager.showBannerMessage( 115 | key: 'provider_unknown_error', 116 | type: 'error', 117 | message: ''' 118 | DevTools failed to connect with package:provider. 119 | 120 | This could be caused by an older version of package:provider; please make sure that you are using version >=5.0.0.''', 121 | extensionName: 'provider', 122 | ); 123 | } 124 | 125 | class _StateInspectorSettingsDialog extends ConsumerWidget { 126 | static const title = 'State inspector configurations'; 127 | 128 | @override 129 | Widget build(BuildContext context, WidgetRef ref) { 130 | return DevToolsDialog( 131 | title: const DialogTitleText(title), 132 | content: Column( 133 | mainAxisSize: MainAxisSize.min, 134 | crossAxisAlignment: CrossAxisAlignment.start, 135 | children: [ 136 | InkWell( 137 | onTap: () => 138 | ref.read(_showInternals.notifier).update((state) => !state), 139 | child: Row( 140 | children: [ 141 | Checkbox( 142 | value: ref.watch(_showInternals), 143 | onChanged: (_) => ref 144 | .read(_showInternals.notifier) 145 | .update((state) => !state), 146 | ), 147 | const Text( 148 | 'Show private properties inherited from SDKs/packages', 149 | ), 150 | ], 151 | ), 152 | ), 153 | ], 154 | ), 155 | actions: const [ 156 | DialogCloseButton(), 157 | ], 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/utils/riverpod_error_logger_observer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:devtools_app_shared/service.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:logging/logging.dart'; 8 | 9 | final _log = Logger('riverpod_error_logger_observer'); 10 | 11 | class ErrorLoggerObserver extends ProviderObserver { 12 | const ErrorLoggerObserver(); 13 | 14 | @override 15 | void didAddProvider( 16 | ProviderBase provider, 17 | Object? value, 18 | ProviderContainer container, 19 | ) { 20 | _maybeLogError(provider, value); 21 | } 22 | 23 | @override 24 | void didUpdateProvider( 25 | ProviderBase provider, 26 | Object? previousValue, 27 | Object? newValue, 28 | ProviderContainer container, 29 | ) { 30 | _maybeLogError(provider, newValue); 31 | } 32 | 33 | void _maybeLogError(ProviderBase provider, Object? value) { 34 | if (value is AsyncError) { 35 | if (value.error is SentinelException) return; 36 | _log.shout('Provider $provider failed with "${value.error}"'); 37 | 38 | final stackTrace = value.stackTrace; 39 | if (stackTrace != null) { 40 | _log.info(stackTrace); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/lib/src/utils/sliver_iterable_child_delegate.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | 7 | /// A delegate that allows using ListView with an undetermined list length 8 | /// while preserve the "build only what is visible" behaviour. 9 | class SliverIterableChildDelegate extends SliverChildDelegate { 10 | SliverIterableChildDelegate( 11 | this.children, { 12 | this.estimatedChildCount, 13 | }); 14 | 15 | final Iterable children; 16 | int? _lastAccessedIndex; 17 | late Iterator _lastAccessedIterator; 18 | 19 | @override 20 | Widget? build(BuildContext context, int index) { 21 | if (_lastAccessedIndex == null || _lastAccessedIndex! > index) { 22 | _lastAccessedIndex = -1; 23 | _lastAccessedIterator = children.iterator; 24 | } 25 | 26 | while (_lastAccessedIndex! < index) { 27 | _lastAccessedIterator.moveNext(); 28 | _lastAccessedIndex = _lastAccessedIndex! + 1; 29 | } 30 | 31 | return _lastAccessedIterator.current; 32 | } 33 | 34 | @override 35 | final int? estimatedChildCount; 36 | 37 | @override 38 | bool shouldRebuild(SliverIterableChildDelegate oldDelegate) { 39 | return children != oldDelegate.children || 40 | _lastAccessedIndex != oldDelegate._lastAccessedIndex || 41 | _lastAccessedIterator != oldDelegate._lastAccessedIterator; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: provider_devtools_extension 2 | description: "The Flutter web app for the package:provider DevTools extension." 3 | publish_to: 'none' 4 | 5 | version: 0.0.1 6 | 7 | environment: 8 | sdk: '>=3.0.0 <4.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | cupertino_icons: ^1.0.2 14 | collection: ^1.15.0 15 | devtools_extensions: ^0.0.6 16 | devtools_app_shared: ^0.0.4 17 | # TODO: https://github.com/flutter/devtools/issues/4569 - unpin this version 18 | flutter_riverpod: 2.0.0-dev.9 19 | logging: ^1.1.1 20 | vm_service: ">=11.9.0 <14.0.0" 21 | 22 | dev_dependencies: 23 | flutter_driver: 24 | sdk: flutter 25 | flutter_test: 26 | sdk: flutter 27 | flutter_lints: ^2.0.0 28 | integration_test: 29 | sdk: flutter 30 | 31 | flutter: 32 | uses-material-design: true 33 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/test/test_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:devtools_app_shared/ui.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | Future loadFonts() async { 9 | // source: https://medium.com/swlh/test-your-flutter-widgets-using-golden-files-b533ac0de469 10 | 11 | //https://github.com/flutter/flutter/issues/20907 12 | if (Directory.current.path.endsWith('/test')) { 13 | Directory.current = Directory.current.parent; 14 | } 15 | 16 | const fonts = { 17 | 'Roboto': [ 18 | 'fonts/Roboto/Roboto-Thin.ttf', 19 | 'fonts/Roboto/Roboto-Light.ttf', 20 | 'fonts/Roboto/Roboto-Regular.ttf', 21 | 'fonts/Roboto/Roboto-Medium.ttf', 22 | 'fonts/Roboto/Roboto-Bold.ttf', 23 | 'fonts/Roboto/Roboto-Black.ttf', 24 | ], 25 | 'RobotoMono': [ 26 | 'fonts/Roboto_Mono/RobotoMono-Thin.ttf', 27 | 'fonts/Roboto_Mono/RobotoMono-Light.ttf', 28 | 'fonts/Roboto_Mono/RobotoMono-Regular.ttf', 29 | 'fonts/Roboto_Mono/RobotoMono-Medium.ttf', 30 | 'fonts/Roboto_Mono/RobotoMono-Bold.ttf', 31 | ], 32 | 'Octicons': ['fonts/Octicons.ttf'], 33 | // 'Codicon': ['packages/codicon/font/codicon.ttf'] 34 | }; 35 | 36 | final loadFontsFuture = fonts.entries.map((entry) async { 37 | final loader = FontLoader(entry.key); 38 | 39 | for (final path in entry.value) { 40 | final fontData = File(path).readAsBytes().then((bytes) { 41 | return ByteData.view(Uint8List.fromList(bytes).buffer); 42 | }); 43 | 44 | loader.addFont(fontData); 45 | } 46 | 47 | await loader.load(); 48 | }); 49 | 50 | await Future.wait(loadFontsFuture); 51 | } 52 | 53 | // NOTE: the below helpers are duplicated from 54 | // `flutter/devtools/packages/devtools_test`. We copied because `devtools_test` 55 | // is not published on pub.dev for us to import. 56 | 57 | /// Wraps [widget] with the build context it needs to load in a test. 58 | /// 59 | /// This includes a [MaterialApp] to provide context like [Theme.of], a 60 | /// [Material] to support elements like [TextField] that draw ink effects, and a 61 | /// [Directionality] to support [RenderFlex] widgets like [Row] and [Column]. 62 | Widget wrap(Widget widget) { 63 | return MaterialApp( 64 | theme: themeFor( 65 | isDarkTheme: false, 66 | ideTheme: IdeTheme(), 67 | theme: ThemeData( 68 | useMaterial3: true, 69 | colorScheme: lightColorScheme, 70 | ), 71 | ), 72 | home: Directionality( 73 | textDirection: TextDirection.ltr, 74 | child: widget, 75 | ), 76 | ); 77 | } 78 | 79 | /// Runs a test with the size of the app window under test to [windowSize]. 80 | void testWidgetsWithWindowSize( 81 | String name, 82 | Size windowSize, 83 | WidgetTesterCallback test, { 84 | bool skip = false, 85 | }) { 86 | testWidgets( 87 | name, 88 | (WidgetTester tester) async { 89 | await _setWindowSize(tester, windowSize); 90 | await test(tester); 91 | await _resetWindowSize(tester); 92 | }, 93 | skip: skip, 94 | ); 95 | } 96 | 97 | Future _setWindowSize(WidgetTester tester, Size windowSize) async { 98 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 99 | await binding.setSurfaceSize(windowSize); 100 | tester.view.physicalSize = windowSize; 101 | tester.view.devicePixelRatio = 1.0; 102 | } 103 | 104 | Future _resetWindowSize(WidgetTester tester) async { 105 | await _setWindowSize(tester, const Size(800.0, 600.0)); 106 | } 107 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:integration_test/integration_test_driver_extended.dart'; 3 | 4 | Future main() async { 5 | final driver = await FlutterDriver.connect(); 6 | await integrationDriver(driver: driver); 7 | } 8 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/packages/provider_devtools_extension/web/favicon.png -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/packages/provider_devtools_extension/web/icons/Icon-192.png -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/packages/provider_devtools_extension/web/icons/Icon-512.png -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/packages/provider_devtools_extension/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/packages/provider_devtools_extension/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | provider_devtools_extension 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /packages/provider_devtools_extension/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "provider_devtools_extension", 3 | "short_name": "provider_devtools_extension", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": ""A new Flutter project."", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /resources/devtools_providers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/resources/devtools_providers.jpg -------------------------------------------------------------------------------- /resources/expanded_devtools.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/resources/expanded_devtools.jpg -------------------------------------------------------------------------------- /resources/flutter_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/provider/3ee02e49b928fcb37fe6d1e5bb58ed554b26164a/resources/flutter_favorite.png --------------------------------------------------------------------------------