NEW: added provider plugin
This commit is contained in:
parent
9a8c050ad8
commit
5e28ef8ea5
|
@ -0,0 +1,27 @@
|
|||
language: bash
|
||||
os:
|
||||
- osx
|
||||
env:
|
||||
- FLUTTER_CHANNEL="stable"
|
||||
- FLUTTER_CHANNEL="master"
|
||||
sudo: false
|
||||
before_script:
|
||||
- cd ..
|
||||
- git clone https://github.com/flutter/flutter.git -b $FLUTTER_CHANNEL
|
||||
- export PATH=$PATH:$PWD/flutter/bin:$PWD/flutter/bin/cache/dart-sdk/bin
|
||||
- cd -
|
||||
- flutter doctor
|
||||
script:
|
||||
- set -e # abort CI if an error happens
|
||||
- ./scripts/flutter_test.sh packages/provider
|
||||
- ./scripts/flutter_test.sh packages/provider/example
|
||||
|
||||
# export coverage
|
||||
- if [ $FLUTTER_CHANNEL = "stable" ]; then
|
||||
bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
matrix:
|
||||
fast_finish: true
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.pub-cache
|
|
@ -0,0 +1,182 @@
|
|||
[](https://travis-ci.org/rrousselGit/provider)
|
||||
[](https://pub.dartlang.org/packages/provider) [](https://codecov.io/gh/rrousselGit/provider) [](https://gitter.im/flutter_provider/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
A dependency injection system built with widgets for widgets. `provider` is mostly syntax sugar for `InheritedWidget`,
|
||||
to make common use-cases straightforward.
|
||||
|
||||
## Migration from v2.0.0 to v3.0.0
|
||||
|
||||
- Providers can no longer be instantiated with `const`.
|
||||
- `Provider` now throws if used with a `Listenable`/`Stream`.
|
||||
Consider using `ListenableProvider`/`StreamProvider` instead. Alternatively,
|
||||
this exception can be disabled by setting `Provider.debugCheckInvalidValueType`
|
||||
to `null` like so:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
- All `XXProvider.value` constructors now use `value` as parameter name.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
ChangeNotifierProvider.value(notifier: myNotifier),
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
ChangeNotifierProvider.value(value: myNotifier),
|
||||
```
|
||||
|
||||
- `StreamProvider`'s default constructor now builds a `Stream` instead of `StreamController`. The previous behavior has been moved to the named constructor `StreamProvider.controller`.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
StreamProvider(builder: (_) => StreamController<int>()),
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
StreamProvider.controller(builder: (_) => StreamController<int>()),
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Exposing a value
|
||||
|
||||
To expose a variable using `provider`, wrap any widget into one of the provider widgets from this package
|
||||
and pass it your variable. Then, all descendants of the newly added provider widget can access this variable.
|
||||
|
||||
A simple example would be to wrap the entire application into a `Provider` widget and pass it our variable:
|
||||
|
||||
```dart
|
||||
Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: MaterialApp(
|
||||
home: Home(),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Alternatively, for complex objects, most providers expose a constructor that takes a function to create the value.
|
||||
The provider will call that function only once, when inserting the widget in the tree, and expose the result.
|
||||
This is perfect for exposing a complex object that never changes over time without writing a `StatefulWidget`.
|
||||
|
||||
The following creates and exposes a `MyComplexClass`. And in the event where `Provider` is removed from the widget tree,
|
||||
the instantiated `MyComplexClass` will be disposed.
|
||||
|
||||
```dart
|
||||
Provider<MyComplexClass>(
|
||||
builder: (context) => MyComplexClass(),
|
||||
dispose: (context, value) => value.dispose()
|
||||
child: SomeWidget(),
|
||||
)
|
||||
```
|
||||
|
||||
### Reading a value
|
||||
|
||||
The easiest way to read a value is by using the static method `Provider.of<T>(BuildContext context)`. This method will look
|
||||
up in widget tree starting from the widget associated with the `BuildContext` passed and it will return the nearest variable
|
||||
of type `T` found (or throw if nothing if found).
|
||||
|
||||
Combined with the first example of [exposing a value](#exposing-a-value), this widget will read the exposed `String` and render "Hello World."
|
||||
|
||||
```dart
|
||||
class Home extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
/// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
|
||||
Provider.of<String>(context)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively instead of using `Provider.of`, we can use the `Consumer` widget.
|
||||
|
||||
This can be useful for performance optimizations or when it is difficult to obtain a `BuildContext` descendant of the provider.
|
||||
|
||||
```dart
|
||||
Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: Consumer<String>(
|
||||
builder: (context, value, child) => Text(value),
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Note that you can freely use multiple providers with different types together:
|
||||
|
||||
```dart
|
||||
Provider<int>.value(
|
||||
value: 42,
|
||||
child: Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: // ...
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
And obtain their value independently:
|
||||
|
||||
```dart
|
||||
var value = Provider.of<int>(context);
|
||||
var value2 = Provider.of<String>(context);
|
||||
```
|
||||
|
||||
### MultiProvider
|
||||
|
||||
When injecting many values in big applications, `Provider` can rapidly become pretty nested:
|
||||
|
||||
```dart
|
||||
Provider<Foo>.value(
|
||||
value: foo,
|
||||
child: Provider<Bar>.value(
|
||||
value: bar,
|
||||
child: Provider<Baz>.value(
|
||||
value: baz,
|
||||
child: someWidget,
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
In that situation, we can use `MultiProvider` to improve the readability:
|
||||
|
||||
```dart
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider<Foo>.value(value: foo),
|
||||
Provider<Bar>.value(value: bar),
|
||||
Provider<Baz>.value(value: baz),
|
||||
],
|
||||
child: someWidget,
|
||||
)
|
||||
```
|
||||
|
||||
The behavior of both examples is strictly the same. `MultiProvider` only changes the appearance of the code.
|
||||
|
||||
### Existing providers
|
||||
|
||||
`provider` exposes a few different kinds of "provider" for different types of objects.
|
||||
|
||||
| name | description |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Provider](https://pub.dartlang.org/documentation/provider/latest/provider/Provider-class.html) | The most basic form of provider. It takes a value and exposes it, whatever the value is. |
|
||||
| [ListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ListenableProvider-class.html) | A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called. |
|
||||
| [ChangeNotifierProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ChangeNotifierProvider-class.html) | A specification of ListenableProvider for ChangeNotifier. It will automatically call `ChangeNotifier.dispose` when needed. |
|
||||
| [ValueListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ValueListenableProvider-class.html) | Listen to a ValueListenable and only expose `ValueListenable.value`. |
|
||||
| [StreamProvider](https://pub.dartlang.org/documentation/provider/latest/provider/StreamProvider-class.html) | Listen to a Stream and expose the latest value emitted. |
|
||||
| [FutureProvider](https://pub.dartlang.org/documentation/provider/latest/provider/FutureProvider-class.html) | Takes a `Future` and updates dependents when the future completes. |
|
|
@ -0,0 +1,82 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
implicit-dynamic: false
|
||||
errors:
|
||||
todo: error
|
||||
include_file_not_found: ignore
|
||||
linter:
|
||||
rules:
|
||||
- public_member_api_docs
|
||||
- annotate_overrides
|
||||
- avoid_empty_else
|
||||
- avoid_function_literals_in_foreach_calls
|
||||
- avoid_init_to_null
|
||||
- avoid_null_checks_in_equality_operators
|
||||
- avoid_relative_lib_imports
|
||||
- avoid_renaming_method_parameters
|
||||
- avoid_return_types_on_setters
|
||||
- avoid_returning_null
|
||||
- avoid_types_as_parameter_names
|
||||
- avoid_unused_constructor_parameters
|
||||
- await_only_futures
|
||||
- camel_case_types
|
||||
- cancel_subscriptions
|
||||
- cascade_invocations
|
||||
- comment_references
|
||||
- constant_identifier_names
|
||||
- control_flow_in_finally
|
||||
- directives_ordering
|
||||
- empty_catches
|
||||
- empty_constructor_bodies
|
||||
- empty_statements
|
||||
- hash_and_equals
|
||||
- implementation_imports
|
||||
- invariant_booleans
|
||||
- iterable_contains_unrelated_type
|
||||
- library_names
|
||||
- library_prefixes
|
||||
- list_remove_unrelated_type
|
||||
- no_adjacent_strings_in_list
|
||||
- no_duplicate_case_values
|
||||
- non_constant_identifier_names
|
||||
- null_closures
|
||||
- omit_local_variable_types
|
||||
- only_throw_errors
|
||||
- overridden_fields
|
||||
- package_api_docs
|
||||
- package_names
|
||||
- package_prefixed_library_names
|
||||
- prefer_adjacent_string_concatenation
|
||||
- prefer_collection_literals
|
||||
- prefer_conditional_assignment
|
||||
- prefer_const_constructors
|
||||
- prefer_contains
|
||||
- prefer_equal_for_default_values
|
||||
- prefer_final_fields
|
||||
- prefer_initializing_formals
|
||||
- prefer_interpolation_to_compose_strings
|
||||
- prefer_is_empty
|
||||
- prefer_is_not_empty
|
||||
- prefer_single_quotes
|
||||
- prefer_typing_uninitialized_variables
|
||||
- recursive_getters
|
||||
- slash_for_doc_comments
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- type_init_formals
|
||||
- unawaited_futures
|
||||
- unnecessary_brace_in_string_interps
|
||||
- unnecessary_const
|
||||
- unnecessary_getters_setters
|
||||
- unnecessary_lambdas
|
||||
- unnecessary_new
|
||||
- unnecessary_null_aware_assignments
|
||||
- unnecessary_statements
|
||||
- unnecessary_this
|
||||
- unrelated_type_equality_checks
|
||||
- use_rethrow_when_possible
|
||||
- valid_regexps
|
|
@ -0,0 +1,14 @@
|
|||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
android/
|
||||
ios/
|
||||
.packages
|
||||
# Remove the following pattern if you wish to check in your lock file
|
||||
pubspec.lock
|
||||
|
||||
# Conventional directory for build outputs
|
||||
build/
|
||||
coverage/
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
|
@ -0,0 +1,79 @@
|
|||
# 3.0.0
|
||||
|
||||
## breaking (see the readme for migration steps):
|
||||
|
||||
- `Provider` now throws if used with a `Listenable`/`Stream`. This can be disabled by setting
|
||||
`Provider.debugCheckInvalidValueType` to `null`.
|
||||
- The default constructor of `StreamProvider` has now builds a `Stream`
|
||||
instead of `StreamController`. The previous behavior has been moved to `StreamProvider.controller`.
|
||||
- All `XXProvider.value` constructors now use `value` as parameter name.
|
||||
- Added `FutureProvider`, which takes a future and updates dependents when the future completes.
|
||||
- Providers can no longer be instantiated using `const` constructors.
|
||||
|
||||
## non-breaking:
|
||||
|
||||
- Added `ProxyProvider`, `ListenableProxyProvider`, and `ChangeNotifierProxyProvider`.
|
||||
These providers allows building values that depends on other providers,
|
||||
without loosing reactivity or manually handling the state.
|
||||
- Added `DelegateWidget` and a few related classes to help building custom providers.
|
||||
- Exposed the internal generic `InheritedWidget` to help building custom providers.
|
||||
|
||||
# 2.0.1
|
||||
|
||||
- fix a bug where `ListenableProvider.value`/`ChangeNotifierProvider.value`
|
||||
/`StreamProvider.value`/`ValueListenableProvider.value` subscribed/unsubscribed
|
||||
to their respective object too often
|
||||
- fix a bug where `ListenableProvider.value`/`ChangeNotifierProvider.value` may
|
||||
rebuild too often or skip some.
|
||||
|
||||
# 2.0.0
|
||||
|
||||
- `Consumer` now takes an optional `child` argument for optimization purposes.
|
||||
- merged `Provider` and `StatefulProvider`
|
||||
- added a "builder" constructor to `ValueListenableProvider`
|
||||
- normalized providers constructors such that the default constructor is a "builder",
|
||||
and offer a `value` named constructor.
|
||||
|
||||
# 1.6.1
|
||||
|
||||
- `Provider.of<T>` now crashes with a `ProviderNotFoundException` when no `Provider<T>`
|
||||
are found in the ancestors of the context used.
|
||||
|
||||
# 1.6.0
|
||||
|
||||
- new: `ChangeNotifierProvider`, similar to scoped_model that exposes `ChangeNotifer` subclass and
|
||||
rebuilds dependents only when `notifyListeners` is called.
|
||||
- new: `ValueListenableProvider`, a provider that rebuilds whenever the value passed
|
||||
to a `ValueNotifier` change.
|
||||
|
||||
# 1.5.0
|
||||
|
||||
- new: Add `Consumer` with up to 6 parameters.
|
||||
- new: `MultiProvider`, a provider that makes a tree of provider more readable
|
||||
- new: `StreamProvider`, a stream that exposes to its descendants the current value of a `Stream`.
|
||||
|
||||
# 1.4.0
|
||||
|
||||
- Reintroduced `StatefulProvider` with a modified prototype.
|
||||
The second argument of `valueBuilder` and `didChangeDependencies` have been removed.
|
||||
And `valueBuilder` is now called only once for the whole life-cycle of `StatefulProvider`.
|
||||
|
||||
# 1.3.0
|
||||
|
||||
- Added `Consumer`, useful when we need to both expose and consume a value simultaneously.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- Added: `HookProvider`, a `Provider` that creates its value from a `Hook`.
|
||||
- Deprecated `StatefulProvider`. Either make a `StatefulWidget` or use `HookProvider`.
|
||||
- Integrated the widget inspector, so that `Provider` widget shows the current value.
|
||||
|
||||
# 1.1.1
|
||||
|
||||
- add `didChangeDependencies` callback to allow updating the value based on an `InheritedWidget`
|
||||
- add `updateShouldNotify` method to both `Provider` and `StatefulProvider`
|
||||
|
||||
# 1.1.0
|
||||
|
||||
- `onDispose` has been added to `StatefulProvider`
|
||||
- `BuildContext` is now passed to `valueBuilder` callback
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Remi Rousselet
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,182 @@
|
|||
[](https://travis-ci.org/rrousselGit/provider)
|
||||
[](https://pub.dartlang.org/packages/provider) [](https://codecov.io/gh/rrousselGit/provider) [](https://gitter.im/flutter_provider/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
A dependency injection system built with widgets for widgets. `provider` is mostly syntax sugar for `InheritedWidget`,
|
||||
to make common use-cases straightforward.
|
||||
|
||||
## Migration from v2.0.0 to v3.0.0
|
||||
|
||||
- Providers can no longer be instantiated with `const`.
|
||||
- `Provider` now throws if used with a `Listenable`/`Stream`.
|
||||
Consider using `ListenableProvider`/`StreamProvider` instead. Alternatively,
|
||||
this exception can be disabled by setting `Provider.debugCheckInvalidValueType`
|
||||
to `null` like so:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
- All `XXProvider.value` constructors now use `value` as parameter name.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
ChangeNotifierProvider.value(notifier: myNotifier),
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
ChangeNotifierProvider.value(value: myNotifier),
|
||||
```
|
||||
|
||||
- `StreamProvider`'s default constructor now builds a `Stream` instead of `StreamController`. The previous behavior has been moved to the named constructor `StreamProvider.controller`.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
StreamProvider(builder: (_) => StreamController<int>()),
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
StreamProvider.controller(builder: (_) => StreamController<int>()),
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Exposing a value
|
||||
|
||||
To expose a variable using `provider`, wrap any widget into one of the provider widgets from this package
|
||||
and pass it your variable. Then, all descendants of the newly added provider widget can access this variable.
|
||||
|
||||
A simple example would be to wrap the entire application into a `Provider` widget and pass it our variable:
|
||||
|
||||
```dart
|
||||
Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: MaterialApp(
|
||||
home: Home(),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Alternatively, for complex objects, most providers expose a constructor that takes a function to create the value.
|
||||
The provider will call that function only once, when inserting the widget in the tree, and expose the result.
|
||||
This is perfect for exposing a complex object that never changes over time without writing a `StatefulWidget`.
|
||||
|
||||
The following creates and exposes a `MyComplexClass`. And in the event where `Provider` is removed from the widget tree,
|
||||
the instantiated `MyComplexClass` will be disposed.
|
||||
|
||||
```dart
|
||||
Provider<MyComplexClass>(
|
||||
builder: (context) => MyComplexClass(),
|
||||
dispose: (context, value) => value.dispose()
|
||||
child: SomeWidget(),
|
||||
)
|
||||
```
|
||||
|
||||
### Reading a value
|
||||
|
||||
The easiest way to read a value is by using the static method `Provider.of<T>(BuildContext context)`. This method will look
|
||||
up in widget tree starting from the widget associated with the `BuildContext` passed and it will return the nearest variable
|
||||
of type `T` found (or throw if nothing if found).
|
||||
|
||||
Combined with the first example of [exposing a value](#exposing-a-value), this widget will read the exposed `String` and render "Hello World."
|
||||
|
||||
```dart
|
||||
class Home extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
/// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
|
||||
Provider.of<String>(context)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively instead of using `Provider.of`, we can use the `Consumer` widget.
|
||||
|
||||
This can be useful for performance optimizations or when it is difficult to obtain a `BuildContext` descendant of the provider.
|
||||
|
||||
```dart
|
||||
Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: Consumer<String>(
|
||||
builder: (context, value, child) => Text(value),
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Note that you can freely use multiple providers with different types together:
|
||||
|
||||
```dart
|
||||
Provider<int>.value(
|
||||
value: 42,
|
||||
child: Provider<String>.value(
|
||||
value: 'Hello World',
|
||||
child: // ...
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
And obtain their value independently:
|
||||
|
||||
```dart
|
||||
var value = Provider.of<int>(context);
|
||||
var value2 = Provider.of<String>(context);
|
||||
```
|
||||
|
||||
### MultiProvider
|
||||
|
||||
When injecting many values in big applications, `Provider` can rapidly become pretty nested:
|
||||
|
||||
```dart
|
||||
Provider<Foo>.value(
|
||||
value: foo,
|
||||
child: Provider<Bar>.value(
|
||||
value: bar,
|
||||
child: Provider<Baz>.value(
|
||||
value: baz,
|
||||
child: someWidget,
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
In that situation, we can use `MultiProvider` to improve the readability:
|
||||
|
||||
```dart
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider<Foo>.value(value: foo),
|
||||
Provider<Bar>.value(value: bar),
|
||||
Provider<Baz>.value(value: baz),
|
||||
],
|
||||
child: someWidget,
|
||||
)
|
||||
```
|
||||
|
||||
The behavior of both examples is strictly the same. `MultiProvider` only changes the appearance of the code.
|
||||
|
||||
### Existing providers
|
||||
|
||||
`provider` exposes a few different kinds of "provider" for different types of objects.
|
||||
|
||||
| name | description |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Provider](https://pub.dartlang.org/documentation/provider/latest/provider/Provider-class.html) | The most basic form of provider. It takes a value and exposes it, whatever the value is. |
|
||||
| [ListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ListenableProvider-class.html) | A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called. |
|
||||
| [ChangeNotifierProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ChangeNotifierProvider-class.html) | A specification of ListenableProvider for ChangeNotifier. It will automatically call `ChangeNotifier.dispose` when needed. |
|
||||
| [ValueListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ValueListenableProvider-class.html) | Listen to a ValueListenable and only expose `ValueListenable.value`. |
|
||||
| [StreamProvider](https://pub.dartlang.org/documentation/provider/latest/provider/StreamProvider-class.html) | Listen to a Stream and expose the latest value emitted. |
|
||||
| [FutureProvider](https://pub.dartlang.org/documentation/provider/latest/provider/FutureProvider-class.html) | Takes a `Future` and updates dependents when the future completes. |
|
|
@ -0,0 +1,70 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Visual Studio Code related
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
**/android/.gradle
|
||||
**/android/captures/
|
||||
**/android/gradlew
|
||||
**/android/gradlew.bat
|
||||
**/android/local.properties
|
||||
**/android/**/GeneratedPluginRegistrant.java
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
|
@ -0,0 +1,10 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: b593f5167bce84fb3cad5c258477bf3abc1b14eb
|
||||
channel: unknown
|
||||
|
||||
project_type: app
|
|
@ -0,0 +1,125 @@
|
|||
// ignore_for_file: public_member_api_docs
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class Counter with ChangeNotifier {
|
||||
int _count = 0;
|
||||
int get count => _count;
|
||||
|
||||
void increment() {
|
||||
_count++;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(builder: (_) => Counter()),
|
||||
],
|
||||
child: Consumer<Counter>(
|
||||
builder: (context, counter, _) {
|
||||
return MaterialApp(
|
||||
supportedLocales: const [Locale('en')],
|
||||
localizationsDelegates: [
|
||||
DefaultMaterialLocalizations.delegate,
|
||||
DefaultWidgetsLocalizations.delegate,
|
||||
_ExampleLocalizationsDelegate(counter.count),
|
||||
],
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleLocalizations {
|
||||
static ExampleLocalizations of(BuildContext context) =>
|
||||
Localizations.of<ExampleLocalizations>(context, ExampleLocalizations);
|
||||
|
||||
const ExampleLocalizations(this._count);
|
||||
|
||||
final int _count;
|
||||
|
||||
String get title => 'Tapped $_count times';
|
||||
}
|
||||
|
||||
class _ExampleLocalizationsDelegate
|
||||
extends LocalizationsDelegate<ExampleLocalizations> {
|
||||
const _ExampleLocalizationsDelegate(this.count);
|
||||
|
||||
final int count;
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => locale.languageCode == 'en';
|
||||
|
||||
@override
|
||||
Future<ExampleLocalizations> load(Locale locale) =>
|
||||
SynchronousFuture(ExampleLocalizations(count));
|
||||
|
||||
@override
|
||||
bool shouldReload(_ExampleLocalizationsDelegate old) => old.count != count;
|
||||
}
|
||||
|
||||
class MyHomePage extends StatelessWidget {
|
||||
const MyHomePage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Title()),
|
||||
body: const Center(child: CounterLabel()),
|
||||
floatingActionButton: const IncrementCounterButton(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class IncrementCounterButton extends StatelessWidget {
|
||||
const IncrementCounterButton({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton(
|
||||
onPressed: Provider.of<Counter>(context).increment,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CounterLabel extends StatelessWidget {
|
||||
const CounterLabel({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final counter = Provider.of<Counter>(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'${counter.count}',
|
||||
style: Theme.of(context).textTheme.display1,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Title extends StatelessWidget {
|
||||
const Title({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(ExampleLocalizations.of(context).title);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
name: example
|
||||
version: 1.0.0
|
||||
homepage: https://github.com/rrousselGit/provider
|
||||
author: Remi Rousselet <darky12s@gmail.com>
|
||||
|
||||
environment:
|
||||
sdk: ">=2.1.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
provider:
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
dependency_overrides:
|
||||
provider:
|
||||
path: ../
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
|
@ -0,0 +1,35 @@
|
|||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:example/main.dart' as example;
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
|
||||
example.main();
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
expect(find.text('Tapped 0 times'), findsOneWidget);
|
||||
expect(find.text('Tapped 1 times'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('Tapped 0 times'), findsNothing);
|
||||
expect(find.text('Tapped 1 times'), findsOneWidget);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
library provider;
|
||||
|
||||
export 'src/async_provider.dart';
|
||||
export 'src/change_notifier_provider.dart';
|
||||
export 'src/consumer.dart';
|
||||
export 'src/delegate_widget.dart';
|
||||
export 'src/listenable_provider.dart';
|
||||
export 'src/provider.dart';
|
||||
export 'src/proxy_provider.dart'
|
||||
hide NumericProxyProvider, Void, ProxyProviderBase;
|
||||
export 'src/value_listenable_provider.dart';
|
|
@ -0,0 +1,306 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_web/widgets.dart';
|
||||
|
||||
import 'delegate_widget.dart';
|
||||
import 'provider.dart';
|
||||
|
||||
/// A callback used to build a valid value from an error.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [StreamProvider.catchError] which uses [ErrorBuilder] to handle errors
|
||||
/// emitted by a [Stream].
|
||||
/// * [FutureProvider.catchError] which uses [ErrorBuilder] to handle
|
||||
/// [Future.catch].
|
||||
typedef ErrorBuilder<T> = T Function(BuildContext context, Object error);
|
||||
|
||||
/// Listens to a [Stream<T>] and exposes [T] to its descendants.
|
||||
///
|
||||
/// It is considered an error to pass a stream that can emit errors without providing
|
||||
/// a [catchError] method.
|
||||
///
|
||||
/// {@template provider.streamprovider.initialdata}
|
||||
/// [initialData] determines the value exposed until the [Stream] emits a value.
|
||||
/// If omitted, defaults to `null`.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Stream], which is listened by [StreamProvider].
|
||||
/// * [StreamController], to create a [Stream]
|
||||
class StreamProvider<T> extends ValueDelegateWidget<Stream<T>>
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Creates a [Stream] from [builder] and subscribes to it.
|
||||
///
|
||||
/// The parameter [builder] must not be `null`.
|
||||
StreamProvider({
|
||||
Key key,
|
||||
@required ValueBuilder<Stream<T>> builder,
|
||||
T initialData,
|
||||
ErrorBuilder<T> catchError,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate<Stream<T>>(builder),
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Creates a [StreamController] from [builder] and subscribes to its stream.
|
||||
///
|
||||
/// [StreamProvider] will automatically call [StreamController.close]
|
||||
/// when the widget is removed from the tree.
|
||||
///
|
||||
/// The parameter [builder] must not be `null`.
|
||||
StreamProvider.controller({
|
||||
Key key,
|
||||
@required ValueBuilder<StreamController<T>> builder,
|
||||
T initialData,
|
||||
ErrorBuilder<T> catchError,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: _StreamControllerBuilderDelegate(builder),
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Listens to [value] and expose it to all of [StreamProvider] descendants.
|
||||
StreamProvider.value({
|
||||
Key key,
|
||||
@required Stream<T> value,
|
||||
T initialData,
|
||||
ErrorBuilder<T> catchError,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(value),
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
StreamProvider._({
|
||||
Key key,
|
||||
@required ValueStateDelegate<Stream<T>> delegate,
|
||||
this.initialData,
|
||||
this.catchError,
|
||||
this.updateShouldNotify,
|
||||
this.child,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
/// {@macro provider.streamprovider.initialdata}
|
||||
final T initialData;
|
||||
|
||||
/// The widget that is below the current [StreamProvider] widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// An optional function used whenever the [Stream] emits an error.
|
||||
///
|
||||
/// [catchError] will be called with the emitted error and
|
||||
/// is expected to return a fallback value without throwing.
|
||||
///
|
||||
/// The returned value will then be exposed to the descendants of [StreamProvider]
|
||||
/// like any valid value.
|
||||
final ErrorBuilder<T> catchError;
|
||||
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
final UpdateShouldNotify<T> updateShouldNotify;
|
||||
|
||||
@override
|
||||
StreamProvider<T> cloneWithChild(Widget child) {
|
||||
return StreamProvider._(
|
||||
key: key,
|
||||
delegate: delegate,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<T>(
|
||||
stream: delegate.value,
|
||||
initialData: initialData,
|
||||
builder: (_, snapshot) {
|
||||
return InheritedProvider<T>(
|
||||
value: _snapshotToValue(snapshot, context, catchError, this),
|
||||
child: child,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
T _snapshotToValue<T>(AsyncSnapshot<T> snapshot, BuildContext context,
|
||||
ErrorBuilder<T> catchError, ValueDelegateWidget owner) {
|
||||
if (snapshot.hasError) {
|
||||
if (catchError != null) {
|
||||
return catchError(context, snapshot.error);
|
||||
}
|
||||
throw FlutterError('''
|
||||
An exception was throw by ${
|
||||
// ignore: invalid_use_of_protected_member
|
||||
owner.delegate.value?.runtimeType} listened by
|
||||
$owner, but no `catchError` was provided.
|
||||
|
||||
Exception:
|
||||
${snapshot.error}
|
||||
''');
|
||||
}
|
||||
return snapshot.data;
|
||||
}
|
||||
|
||||
class _StreamControllerBuilderDelegate<T>
|
||||
extends ValueStateDelegate<Stream<T>> {
|
||||
_StreamControllerBuilderDelegate(this._builder) : assert(_builder != null);
|
||||
|
||||
StreamController<T> _controller;
|
||||
ValueBuilder<StreamController<T>> _builder;
|
||||
|
||||
@override
|
||||
Stream<T> value;
|
||||
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
_controller = _builder(context);
|
||||
value = _controller?.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(_StreamControllerBuilderDelegate<T> old) {
|
||||
super.didUpdateDelegate(old);
|
||||
value = old.value;
|
||||
_controller = old._controller;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens to a [Future<T>] and exposes [T] to its descendants.
|
||||
///
|
||||
/// It is considered an error to pass a future that can emit errors without providing
|
||||
/// a [catchError] method.
|
||||
///
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Future], which is listened by [FutureProvider].
|
||||
class FutureProvider<T> extends ValueDelegateWidget<Future<T>>
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Creates a [Future] from [builder] and subscribes to it.
|
||||
///
|
||||
/// [builder] must not be `null`.
|
||||
FutureProvider({
|
||||
Key key,
|
||||
@required ValueBuilder<Future<T>> builder,
|
||||
T initialData,
|
||||
ErrorBuilder<T> catchError,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
delegate: BuilderStateDelegate(builder),
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Listens to [value] and expose it to all of [FutureProvider] descendants.
|
||||
FutureProvider.value({
|
||||
Key key,
|
||||
@required Future<T> value,
|
||||
T initialData,
|
||||
ErrorBuilder<T> catchError,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
delegate: SingleValueDelegate(value),
|
||||
child: child,
|
||||
);
|
||||
|
||||
FutureProvider._({
|
||||
Key key,
|
||||
@required ValueStateDelegate<Future<T>> delegate,
|
||||
this.initialData,
|
||||
this.catchError,
|
||||
this.updateShouldNotify,
|
||||
this.child,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
/// [initialData] determines the value exposed until the [Future] completes.
|
||||
///
|
||||
/// If omitted, defaults to `null`.
|
||||
final T initialData;
|
||||
|
||||
/// The widget that is below the current [FutureProvider] widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// Optional function used if the [Future] emits an error.
|
||||
///
|
||||
/// [catchError] will be called with the emitted error and
|
||||
/// is expected to return a fallback value without throwing.
|
||||
///
|
||||
/// The returned value will then be exposed to the descendants of [FutureProvider]
|
||||
/// like any valid value.
|
||||
final ErrorBuilder<T> catchError;
|
||||
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
final UpdateShouldNotify<T> updateShouldNotify;
|
||||
|
||||
@override
|
||||
FutureProvider<T> cloneWithChild(Widget child) {
|
||||
return FutureProvider._(
|
||||
key: key,
|
||||
delegate: delegate,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
initialData: initialData,
|
||||
catchError: catchError,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<T>(
|
||||
future: delegate.value,
|
||||
initialData: initialData,
|
||||
builder: (_, snapshot) {
|
||||
return InheritedProvider<T>(
|
||||
value: _snapshotToValue(snapshot, context, catchError, this),
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
import 'package:flutter_web/widgets.dart';
|
||||
|
||||
import 'delegate_widget.dart';
|
||||
import 'listenable_provider.dart';
|
||||
import 'provider.dart';
|
||||
import 'proxy_provider.dart';
|
||||
|
||||
/// Listens to a [ChangeNotifier], expose it to its descendants
|
||||
/// and rebuilds dependents whenever the [ChangeNotifier.notifyListeners] is called.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChangeNotifier], which is listened by [ChangeNotifierProvider].
|
||||
/// * [ListenableProvider], similar to [ChangeNotifierProvider] but works with any [Listenable].
|
||||
class ChangeNotifierProvider<T extends ChangeNotifier>
|
||||
extends ListenableProvider<T> implements SingleChildCloneableWidget {
|
||||
static void _disposer(BuildContext context, ChangeNotifier notifier) =>
|
||||
notifier?.dispose();
|
||||
|
||||
/// Create a [ChangeNotifier] using the [builder] function and automatically dispose it
|
||||
/// when [ChangeNotifierProvider] is removed from the widget tree.
|
||||
///
|
||||
/// [builder] must not be `null`.
|
||||
ChangeNotifierProvider({
|
||||
Key key,
|
||||
@required ValueBuilder<T> builder,
|
||||
Widget child,
|
||||
}) : super(key: key, builder: builder, dispose: _disposer, child: child);
|
||||
|
||||
/// Listens to [value] and expose it to all of [ChangeNotifierProvider] descendants.
|
||||
ChangeNotifierProvider.value({
|
||||
Key key,
|
||||
@required T value,
|
||||
Widget child,
|
||||
}) : super.value(key: key, value: value, child: child);
|
||||
}
|
||||
|
||||
class _NumericProxyProvider<T, T2, T3, T4, T5, T6, R extends ChangeNotifier>
|
||||
extends ProxyProviderBase<R> implements SingleChildCloneableWidget {
|
||||
_NumericProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
dispose: ChangeNotifierProvider._disposer,
|
||||
);
|
||||
|
||||
/// The widget that is below the current [Provider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.proxyprovider.builder}
|
||||
final Function builder;
|
||||
|
||||
@override
|
||||
_NumericProxyProvider<T, T2, T3, T4, T5, T6, R> cloneWithChild(Widget child) {
|
||||
return _NumericProxyProvider(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, R value) {
|
||||
return ChangeNotifierProvider<R>.value(
|
||||
value: value,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
R didChangeDependencies(BuildContext context, R previous) {
|
||||
final arguments = <dynamic>[
|
||||
context,
|
||||
Provider.of<T>(context),
|
||||
];
|
||||
|
||||
if (T2 != Void) arguments.add(Provider.of<T2>(context));
|
||||
if (T3 != Void) arguments.add(Provider.of<T3>(context));
|
||||
if (T4 != Void) arguments.add(Provider.of<T4>(context));
|
||||
if (T5 != Void) arguments.add(Provider.of<T5>(context));
|
||||
if (T6 != Void) arguments.add(Provider.of<T6>(context));
|
||||
|
||||
arguments.add(previous);
|
||||
return Function.apply(builder, arguments) as R;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ChangeNotifierProxyProvider<T, R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, Void, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder<T, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder<T, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder<T, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ChangeNotifierProxyProvider2<T, T2, R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, T2, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider2({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder2<T, T2, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder2<T, T2, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder2<T, T2, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ChangeNotifierProxyProvider3<T, T2, T3, R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, T2, T3, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider3({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder3<T, T2, T3, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder3<T, T2, T3, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder3<T, T2, T3, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ChangeNotifierProxyProvider4<T, T2, T3, T4, R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider4({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder4<T, T2, T3, T4, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder4<T, T2, T3, T4, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder4<T, T2, T3, T4, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
|
||||
class ChangeNotifierProxyProvider5<T, T2, T3, T4, T5, R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, T5, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider5({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder5<T, T2, T3, T4, T5, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder5<T, T2, T3, T4, T5, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder5<T, T2, T3, T4, T5, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ChangeNotifierProxyProvider6<T, T2, T3, T4, T5, T6,
|
||||
R extends ChangeNotifier>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, T5, T6, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ChangeNotifierProxyProvider6({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> builder,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R>;
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
import 'package:flutter_web/widgets.dart';
|
||||
|
||||
import 'provider.dart';
|
||||
|
||||
/// {@template provider.consumer}
|
||||
/// Obtain [Provider<T>] from its ancestors and pass its value to [builder].
|
||||
///
|
||||
/// [builder] must not be null and may be called multiple times (such as when provided value change).
|
||||
///
|
||||
/// ## Performance optimizations:
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
/// {@endtemplate}
|
||||
class Consumer<T> extends StatelessWidget {
|
||||
/// {@template provider.consumer.constructor}
|
||||
/// Consumes a [Provider<T>]
|
||||
/// {@endtemplate}
|
||||
Consumer({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
// fork of the documentation from https://docs.flutter.io/flutter/widgets/AnimatedBuilder/child.html
|
||||
/// The child widget to pass to [builder].
|
||||
/// {@template provider.consumer.child}
|
||||
///
|
||||
/// If a builder callback's return value contains a subtree that does not depend on the provided value,
|
||||
/// it's more efficient to build that subtree once instead of rebuilding it on every change of the provided value.
|
||||
///
|
||||
/// If the pre-built subtree is passed as the child parameter, [Consumer] will pass it back to the builder function so that it can be incorporated into the build.
|
||||
///
|
||||
/// Using this pre-built child is entirely optional, but can improve performance significantly in some cases and is therefore a good practice.
|
||||
/// {@endtemplate}
|
||||
final Widget child;
|
||||
|
||||
/// {@template provider.consumer.builder}
|
||||
/// Build a widget tree based on the value from a [Provider<T>].
|
||||
///
|
||||
/// Must not be null.
|
||||
/// {@endtemplate}
|
||||
final Widget Function(BuildContext context, T value, Widget child) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<T>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.consumer}
|
||||
class Consumer2<A, B> extends StatelessWidget {
|
||||
/// {@macro provider.consumer.constructor}
|
||||
Consumer2({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
/// The child widget to pass to [builder].
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.consumer.builder}
|
||||
final Widget Function(BuildContext context, A value, B value2, Widget chi)
|
||||
builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<A>(context),
|
||||
Provider.of<B>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.consumer}
|
||||
class Consumer3<A, B, C> extends StatelessWidget {
|
||||
/// {@macro provider.consumer.constructor}
|
||||
Consumer3({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
/// The child widget to pass to [builder].
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.consumer.builder}
|
||||
final Widget Function(
|
||||
BuildContext context, A value, B value2, C value3, Widget child) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<A>(context),
|
||||
Provider.of<B>(context),
|
||||
Provider.of<C>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.consumer}
|
||||
class Consumer4<A, B, C, D> extends StatelessWidget {
|
||||
/// {@macro provider.consumer.constructor}
|
||||
Consumer4({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
/// The child widget to pass to [builder].
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.consumer.builder}
|
||||
final Widget Function(BuildContext context, A value, B value2, C value3,
|
||||
D value4, Widget child) builder;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<A>(context),
|
||||
Provider.of<B>(context),
|
||||
Provider.of<C>(context),
|
||||
Provider.of<D>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.consumer}
|
||||
class Consumer5<A, B, C, D, E> extends StatelessWidget {
|
||||
/// {@macro provider.consumer.constructor}
|
||||
Consumer5({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
/// The child widget to pass to [builder].
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.consumer.builder}
|
||||
final Widget Function(BuildContext context, A value, B value2, C value3,
|
||||
D value4, E value5, Widget child) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<A>(context),
|
||||
Provider.of<B>(context),
|
||||
Provider.of<C>(context),
|
||||
Provider.of<D>(context),
|
||||
Provider.of<E>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.consumer}
|
||||
class Consumer6<A, B, C, D, E, F> extends StatelessWidget {
|
||||
/// {@macro provider.consumer.constructor}
|
||||
Consumer6({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(key: key);
|
||||
|
||||
/// The child widget to pass to [builder].
|
||||
///
|
||||
/// {@macro provider.consumer.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.consumer.builder}
|
||||
final Widget Function(BuildContext context, A value, B value2, C value3,
|
||||
D value4, E value5, F value6, Widget child) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return builder(
|
||||
context,
|
||||
Provider.of<A>(context),
|
||||
Provider.of<B>(context),
|
||||
Provider.of<C>(context),
|
||||
Provider.of<D>(context),
|
||||
Provider.of<E>(context),
|
||||
Provider.of<F>(context),
|
||||
child,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
import 'package:flutter_web/widgets.dart';
|
||||
import 'package:provider/src/provider.dart' show Provider;
|
||||
|
||||
/// A function that creates an object of type [T].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [BuilderStateDelegate]
|
||||
typedef ValueBuilder<T> = T Function(BuildContext context);
|
||||
|
||||
/// A function that disposes an object of type [T].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [BuilderStateDelegate]
|
||||
typedef Disposer<T> = void Function(BuildContext context, T value);
|
||||
|
||||
/// The state of a [DelegateWidget].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ValueStateDelegate]
|
||||
/// * [BuilderStateDelegate]
|
||||
abstract class StateDelegate {
|
||||
BuildContext _context;
|
||||
|
||||
/// The location in the tree where this widget builds.
|
||||
///
|
||||
/// See also [State.context].
|
||||
BuildContext get context => _context;
|
||||
|
||||
StateSetter _setState;
|
||||
|
||||
/// Notify the framework that the internal state of this object has changed.
|
||||
///
|
||||
/// See the discussion on [State.setState] for more information.
|
||||
@protected
|
||||
StateSetter get setState => _setState;
|
||||
|
||||
/// Called on [State.initState] or after [DelegateWidget] is rebuilt
|
||||
/// with a [StateDelegate] of a different [runtimeType].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void initDelegate() {}
|
||||
|
||||
/// Called whenever [State.didUpdateWidget] is called
|
||||
///
|
||||
/// It is guaranteed for [old] to have the same [runtimeType] as `this`.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void didUpdateDelegate(covariant StateDelegate old) {}
|
||||
|
||||
/// Called when [DelegateWidget] is unmounted or if it is rebuilt
|
||||
/// with a [StateDelegate] of a different [runtimeType].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
/// A [StatefulWidget] that delegates its [State] implementation to a [StateDelegate].
|
||||
///
|
||||
/// This is useful for widgets that must switch between different [State] implementation
|
||||
/// under the same [runtimeType].
|
||||
///
|
||||
/// A typical use-case is a non-leaf widget with constructors that behaves differently, as it is necessary for
|
||||
/// all of its constructors to share the same [runtimeType] or else its descendants would loose
|
||||
/// their state.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [StateDelegate], the equivalent of [State] but for [DelegateWidget].
|
||||
/// * [Provider], a concrete implementation of [DelegateWidget].
|
||||
abstract class DelegateWidget extends StatefulWidget {
|
||||
/// Initializes [key] for subclasses.
|
||||
///
|
||||
/// The argument [delegate] must not be `null`.
|
||||
const DelegateWidget({
|
||||
Key key,
|
||||
this.delegate,
|
||||
}) : assert(delegate != null),
|
||||
super(key: key);
|
||||
|
||||
/// The current state of [DelegateWidget].
|
||||
///
|
||||
/// It should not be `null`.
|
||||
@protected
|
||||
final StateDelegate delegate;
|
||||
|
||||
/// Describes the part of the user interface represented by this widget.
|
||||
///
|
||||
/// It is fine for [build] to depend on the content of [delegate].
|
||||
///
|
||||
/// This method is strictly equivalent to [State.build].
|
||||
@protected
|
||||
Widget build(BuildContext context);
|
||||
|
||||
@override
|
||||
StatefulElement createElement() => _DelegateElement(this);
|
||||
|
||||
@override
|
||||
_DelegateWidgetState createState() => _DelegateWidgetState();
|
||||
}
|
||||
|
||||
class _DelegateWidgetState extends State<DelegateWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_mountDelegate();
|
||||
_initDelegate();
|
||||
}
|
||||
|
||||
void _initDelegate() {
|
||||
assert(() {
|
||||
(context as _DelegateElement)._debugIsInitDelegate = true;
|
||||
return true;
|
||||
}());
|
||||
widget.delegate.initDelegate();
|
||||
assert(() {
|
||||
(context as _DelegateElement)._debugIsInitDelegate = false;
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
void _mountDelegate() {
|
||||
widget.delegate
|
||||
.._context = context
|
||||
.._setState = setState;
|
||||
}
|
||||
|
||||
void _unmountDelegate(StateDelegate delegate) {
|
||||
delegate
|
||||
.._context = null
|
||||
.._setState = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DelegateWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.delegate != oldWidget.delegate) {
|
||||
_mountDelegate();
|
||||
if (widget.delegate.runtimeType != oldWidget.delegate.runtimeType) {
|
||||
oldWidget.delegate.dispose();
|
||||
_initDelegate();
|
||||
} else {
|
||||
widget.delegate.didUpdateDelegate(oldWidget.delegate);
|
||||
}
|
||||
_unmountDelegate(oldWidget.delegate);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.build(context);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.delegate.dispose();
|
||||
_unmountDelegate(widget.delegate);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _DelegateElement extends StatefulElement {
|
||||
_DelegateElement(DelegateWidget widget) : super(widget);
|
||||
|
||||
bool _debugIsInitDelegate = false;
|
||||
|
||||
@override
|
||||
DelegateWidget get widget => super.widget as DelegateWidget;
|
||||
|
||||
@override
|
||||
InheritedWidget inheritFromElement(Element ancestor, {Object aspect}) {
|
||||
assert(() {
|
||||
if (_debugIsInitDelegate) {
|
||||
final targetType = ancestor.widget.runtimeType;
|
||||
// error copied from StatefulElement
|
||||
throw FlutterError(
|
||||
'inheritFromWidgetOfExactType($targetType) or inheritFromElement() was called before ${widget.delegate.runtimeType}.initDelegate() completed.\n'
|
||||
'When an inherited widget changes, for example if the value of Theme.of() changes, '
|
||||
'its dependent widgets are rebuilt. If the dependent widget\'s reference to '
|
||||
'the inherited widget is in a constructor or an initDelegate() method, '
|
||||
'then the rebuilt dependent widget will not reflect the changes in the '
|
||||
'inherited widget.\n'
|
||||
'Typically references to inherited widgets should occur in widget build() methods. Alternatively, '
|
||||
'initialization based on inherited widgets can be placed in the didChangeDependencies method, which '
|
||||
'is called after initDelegate and whenever the dependencies change thereafter.');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return super.inheritFromElement(ancestor, aspect: aspect);
|
||||
}
|
||||
}
|
||||
|
||||
/// A base class for [StateDelegate] that exposes a [value] of type [T].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SingleValueDelegate], which extends [ValueStateDelegate] to store
|
||||
/// an immutable value.
|
||||
/// * [BuilderStateDelegate], which extends [ValueStateDelegate]
|
||||
/// to build [value] from a function and dispose it when the widget is unmounted.
|
||||
abstract class ValueStateDelegate<T> extends StateDelegate {
|
||||
/// The member [value] should not be mutated directly.
|
||||
T get value;
|
||||
}
|
||||
|
||||
/// Stores an immutable value.
|
||||
class SingleValueDelegate<T> extends ValueStateDelegate<T> {
|
||||
/// Initializes [value] for subclasses.
|
||||
SingleValueDelegate(this.value);
|
||||
|
||||
@override
|
||||
final T value;
|
||||
}
|
||||
|
||||
/// A [StateDelegate] that creates and dispose a value from functions.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ValueStateDelegate], which [BuilderStateDelegate] implements.
|
||||
class BuilderStateDelegate<T> extends ValueStateDelegate<T> {
|
||||
/// The parameter `builder` must not be `null`.
|
||||
BuilderStateDelegate(this._builder, {Disposer<T> dispose})
|
||||
: assert(_builder != null),
|
||||
_dispose = dispose;
|
||||
|
||||
/// A callback used to create [value].
|
||||
///
|
||||
/// Once [value] is initialized, [_builder] will never be called again
|
||||
/// and [value] will never change.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [value], which [_builder] creates.
|
||||
final ValueBuilder<T> _builder;
|
||||
final Disposer<T> _dispose;
|
||||
|
||||
T _value;
|
||||
@override
|
||||
T get value => _value;
|
||||
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
_value = _builder(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(BuilderStateDelegate<T> old) {
|
||||
super.didUpdateDelegate(old);
|
||||
_value = old.value;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dispose?.call(context, value);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [DelegateWidget] that accepts only [ValueStateDelegate] as [delegate].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DelegateWidget]
|
||||
/// * [ValueStateDelegate]
|
||||
abstract class ValueDelegateWidget<T> extends DelegateWidget {
|
||||
/// Initializes [key] for subclasses.
|
||||
///
|
||||
/// The argument [delegate] must not be `null`.
|
||||
ValueDelegateWidget({
|
||||
Key key,
|
||||
@required ValueStateDelegate<T> delegate,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
@override
|
||||
@protected
|
||||
ValueStateDelegate<T> get delegate =>
|
||||
super.delegate as ValueStateDelegate<T>;
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
import 'package:flutter_web/foundation.dart';
|
||||
import 'package:flutter_web/widgets.dart';
|
||||
|
||||
import 'change_notifier_provider.dart' show ChangeNotifierProvider;
|
||||
import 'delegate_widget.dart';
|
||||
import 'provider.dart';
|
||||
import 'proxy_provider.dart';
|
||||
import 'value_listenable_provider.dart' show ValueListenableProvider;
|
||||
|
||||
/// Listens to a [Listenable], expose it to its descendants
|
||||
/// and rebuilds dependents whenever the listener emits an event.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChangeNotifierProvider], a subclass of [ListenableProvider] specific to [ChangeNotifier].
|
||||
/// * [ValueListenableProvider], which listens to a [ValueListenable] but exposes only [ValueListenable.value] instead of the whole object.
|
||||
/// * [Listenable]
|
||||
class ListenableProvider<T extends Listenable> extends ValueDelegateWidget<T>
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Creates a [Listenable] using [builder] and subscribes to it.
|
||||
///
|
||||
/// [dispose] can optionally passed to free resources
|
||||
/// when [ListenableProvider] is removed from the tree.
|
||||
///
|
||||
/// [builder] must not be `null`.
|
||||
ListenableProvider({
|
||||
Key key,
|
||||
@required ValueBuilder<T> builder,
|
||||
Disposer<T> dispose,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: _BuilderListenableDelegate(builder, dispose: dispose),
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Listens to [value] and expose it to all of [ListenableProvider] descendants.
|
||||
///
|
||||
/// Rebuilding [ListenableProvider] without
|
||||
/// changing the instance of [value] will not rebuild dependants.
|
||||
ListenableProvider.value({
|
||||
Key key,
|
||||
@required T value,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: _ValueListenableDelegate(value),
|
||||
child: child,
|
||||
);
|
||||
|
||||
ListenableProvider._({
|
||||
Key key,
|
||||
@required _ListenableDelegateMixin<T> delegate,
|
||||
// TODO: updateShouldNotify for when the listenable instance change with `.value` constructor
|
||||
this.child,
|
||||
}) : super(
|
||||
key: key,
|
||||
delegate: delegate,
|
||||
);
|
||||
|
||||
/// The widget that is below the current [ListenableProvider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
ListenableProvider<T> cloneWithChild(Widget child) {
|
||||
return ListenableProvider._(
|
||||
key: key,
|
||||
delegate: delegate as _ListenableDelegateMixin<T>,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final delegate = this.delegate as _ListenableDelegateMixin<T>;
|
||||
return InheritedProvider<T>(
|
||||
value: delegate.value,
|
||||
updateShouldNotify: delegate.updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ValueListenableDelegate<T extends Listenable>
|
||||
extends SingleValueDelegate<T> with _ListenableDelegateMixin<T> {
|
||||
_ValueListenableDelegate(T value) : super(value);
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(_ValueListenableDelegate<T> oldDelegate) {
|
||||
super.didUpdateDelegate(oldDelegate);
|
||||
if (oldDelegate.value != value) {
|
||||
_removeListener?.call();
|
||||
if (value != null) startListening(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _BuilderListenableDelegate<T extends Listenable>
|
||||
extends BuilderStateDelegate<T> with _ListenableDelegateMixin<T> {
|
||||
_BuilderListenableDelegate(ValueBuilder<T> builder, {Disposer<T> dispose})
|
||||
: super(builder, dispose: dispose);
|
||||
}
|
||||
|
||||
mixin _ListenableDelegateMixin<T extends Listenable>
|
||||
on ValueStateDelegate<T> {
|
||||
UpdateShouldNotify<T> updateShouldNotify;
|
||||
VoidCallback _removeListener;
|
||||
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
if (value != null) startListening(value);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(StateDelegate old) {
|
||||
super.didUpdateDelegate(old);
|
||||
final delegate = old as _ListenableDelegateMixin<T>;
|
||||
|
||||
_removeListener = delegate._removeListener;
|
||||
updateShouldNotify = delegate.updateShouldNotify;
|
||||
}
|
||||
|
||||
void startListening(T listenable) {
|
||||
/// The number of time [Listenable] called its listeners.
|
||||
///
|
||||
/// It is used to differentiate external rebuilds from rebuilds caused by the listenable emitting an event.
|
||||
/// This allows [InheritedWidget.updateShouldNotify] to return true only in the latter scenario.
|
||||
var buildCount = 0;
|
||||
final setState = this.setState;
|
||||
final listener = () => setState(() => buildCount++);
|
||||
|
||||
var capturedBuildCount = buildCount;
|
||||
updateShouldNotify = (_, __) {
|
||||
final res = buildCount != capturedBuildCount;
|
||||
capturedBuildCount = buildCount;
|
||||
return res;
|
||||
};
|
||||
|
||||
listenable.addListener(listener);
|
||||
_removeListener = () {
|
||||
listenable.removeListener(listener);
|
||||
_removeListener = null;
|
||||
updateShouldNotify = null;
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeListener?.call();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _NumericProxyProvider<T, T2, T3, T4, T5, T6, R extends Listenable>
|
||||
extends ProxyProviderBase<R> implements SingleChildCloneableWidget {
|
||||
_NumericProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required this.builder,
|
||||
Disposer<R> dispose,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
dispose: dispose,
|
||||
);
|
||||
|
||||
/// The widget that is below the current [Provider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.proxyprovider.builder}
|
||||
final Function builder;
|
||||
|
||||
@override
|
||||
_NumericProxyProvider<T, T2, T3, T4, T5, T6, R> cloneWithChild(Widget child) {
|
||||
return _NumericProxyProvider(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, R value) {
|
||||
return ListenableProvider<R>.value(
|
||||
value: value,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
R didChangeDependencies(BuildContext context, R previous) {
|
||||
final arguments = <dynamic>[
|
||||
context,
|
||||
Provider.of<T>(context),
|
||||
];
|
||||
|
||||
if (T2 != Void) arguments.add(Provider.of<T2>(context));
|
||||
if (T3 != Void) arguments.add(Provider.of<T3>(context));
|
||||
if (T4 != Void) arguments.add(Provider.of<T4>(context));
|
||||
if (T5 != Void) arguments.add(Provider.of<T5>(context));
|
||||
if (T6 != Void) arguments.add(Provider.of<T6>(context));
|
||||
|
||||
arguments.add(previous);
|
||||
return Function.apply(builder, arguments) as R;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider<T, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, Void, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder<T, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder<T, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder<T, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider2<T, T2, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, T2, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider2({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder2<T, T2, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder2<T, T2, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder2<T, T2, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider3<T, T2, T3, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, T2, T3, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider3({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder3<T, T2, T3, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder3<T, T2, T3, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder3<T, T2, T3, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider4<T, T2, T3, T4, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider4({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder4<T, T2, T3, T4, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder4<T, T2, T3, T4, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder4<T, T2, T3, T4, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider5<T, T2, T3, T4, T5, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, T5, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider5({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder5<T, T2, T3, T4, T5, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder5<T, T2, T3, T4, T5, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder5<T, T2, T3, T4, T5, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ListenableProxyProvider6<T, T2, T3, T4, T5, T6, R extends Listenable>
|
||||
extends _NumericProxyProvider<T, T2, T3, T4, T5, T6, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ListenableProxyProvider6({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> builder,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R>;
|
||||
}
|
|
@ -0,0 +1,347 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_web/foundation.dart';
|
||||
import 'package:flutter_web/widgets.dart';
|
||||
import 'package:provider/src/delegate_widget.dart';
|
||||
|
||||
/// A function that returns true when the update from [previous] to [current]
|
||||
/// should notify listeners, if any.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [InheritedWidget.updateShouldNotify]
|
||||
typedef UpdateShouldNotify<T> = bool Function(T previous, T current);
|
||||
|
||||
/// Returns the type [T].
|
||||
/// See https://stackoverflow.com/questions/52891537/how-to-get-generic-type
|
||||
/// and https://github.com/dart-lang/sdk/issues/11923.
|
||||
Type _typeOf<T>() => T;
|
||||
|
||||
/// A base class for providers so that [MultiProvider] can regroup them into a
|
||||
/// linear list.
|
||||
abstract class SingleChildCloneableWidget implements Widget {
|
||||
/// Clones the current provider with a new [child].
|
||||
///
|
||||
/// Note for implementers: all other values, including [Key] must be
|
||||
/// preserved.
|
||||
SingleChildCloneableWidget cloneWithChild(Widget child);
|
||||
}
|
||||
|
||||
/// A generic implementation of an [InheritedWidget].
|
||||
///
|
||||
/// Any descendant of this widget can obtain `value` using [Provider.of].
|
||||
///
|
||||
/// Do not use this class directly unless you are creating a custom "Provider".
|
||||
/// Instead use [Provider] class, which wraps [InheritedProvider].
|
||||
class InheritedProvider<T> extends InheritedWidget {
|
||||
/// Allow customizing [updateShouldNotify].
|
||||
const InheritedProvider({
|
||||
Key key,
|
||||
@required T value,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : _value = value,
|
||||
_updateShouldNotify = updateShouldNotify,
|
||||
super(key: key, child: child);
|
||||
|
||||
/// The currently exposed value.
|
||||
///
|
||||
/// Mutating `value` should be avoided. Instead rebuild the widget tree
|
||||
/// and replace [InheritedProvider] with one that holds the new value.
|
||||
final T _value;
|
||||
final UpdateShouldNotify<T> _updateShouldNotify;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(InheritedProvider<T> oldWidget) {
|
||||
if (_updateShouldNotify != null) {
|
||||
return _updateShouldNotify(oldWidget._value, _value);
|
||||
}
|
||||
return oldWidget._value != _value;
|
||||
}
|
||||
}
|
||||
|
||||
/// A provider that merges multiple providers into a single linear widget tree.
|
||||
/// It is used to improve readability and reduce boilderplate code of having to
|
||||
/// nest mutliple layers of providers.
|
||||
///
|
||||
/// As such, we're going from:
|
||||
///
|
||||
/// ```dart
|
||||
/// Provider<Foo>.value(
|
||||
/// value: foo,
|
||||
/// child: Provider<Bar>.value(
|
||||
/// value: bar,
|
||||
/// child: Provider<Baz>.value(
|
||||
/// value: baz,
|
||||
/// child: someWidget,
|
||||
/// )
|
||||
/// )
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// To:
|
||||
///
|
||||
/// ```dart
|
||||
/// MultiProvider(
|
||||
/// providers: [
|
||||
/// Provider<Foo>.value(value: foo),
|
||||
/// Provider<Bar>.value(value: bar),
|
||||
/// Provider<Baz>.value(value: baz),
|
||||
/// ],
|
||||
/// child: someWidget,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// The widget tree representation of the two approaches are identical.
|
||||
class MultiProvider extends StatelessWidget
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Build a tree of providers from a list of [SingleChildCloneableWidget].
|
||||
const MultiProvider({
|
||||
Key key,
|
||||
@required this.providers,
|
||||
this.child,
|
||||
}) : assert(providers != null),
|
||||
super(key: key);
|
||||
|
||||
/// The list of providers that will be transformed into a tree from top to
|
||||
/// bottom.
|
||||
///
|
||||
/// Example: with [A, B, C] and [child], the resulting widget tree looks like:
|
||||
/// A
|
||||
/// |
|
||||
/// B
|
||||
/// |
|
||||
/// C
|
||||
/// |
|
||||
/// child
|
||||
final List<SingleChildCloneableWidget> providers;
|
||||
|
||||
/// The child of the last provider in [providers].
|
||||
///
|
||||
/// If [providers] is empty, [MultiProvider] just returns [child].
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var tree = child;
|
||||
for (final provider in providers.reversed) {
|
||||
tree = provider.cloneWithChild(tree);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
@override
|
||||
MultiProvider cloneWithChild(Widget child) {
|
||||
return MultiProvider(
|
||||
key: key,
|
||||
providers: providers,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Provider] that manages the lifecycle of the value it provides by
|
||||
/// delegating to a pair of [ValueBuilder] and [Disposer].
|
||||
///
|
||||
/// It is usually used to avoid making a [StatefulWidget] for something trivial,
|
||||
/// such as instantiating a BLoC.
|
||||
///
|
||||
/// [Provider] is the equivalent of a [State.initState] combined with
|
||||
/// [State.dispose]. [ValueBuilder] is called only once in [State.initState].
|
||||
/// We cannot use [InheritedWidget] as it requires the value to be
|
||||
/// constructor-initialized and final.
|
||||
///
|
||||
/// The following example instantiates a `Model` once, and disposes it when
|
||||
/// [Provider] is removed from the tree.
|
||||
///
|
||||
/// {@template provider.updateshouldnotify}
|
||||
/// [updateShouldNotify] can optionally be passed to avoid unnecessaryly rebuilding dependants when nothing changed.
|
||||
/// Defaults to `(previous, next) => previous != next`. See [InheritedWidget.updateShouldNotify] for more informations.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// ```dart
|
||||
/// class Model {
|
||||
/// void dispose() {}
|
||||
/// }
|
||||
///
|
||||
/// class Stateless extends StatelessWidget {
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Provider<Model>(
|
||||
/// builder: (context) => Model(),
|
||||
/// dispose: (context, value) => value.dispose(),
|
||||
/// child: ...,
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
class Provider<T> extends ValueDelegateWidget<T>
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Allows to specify parameters to [Provider].
|
||||
Provider({
|
||||
Key key,
|
||||
@required ValueBuilder<T> builder,
|
||||
Disposer<T> dispose,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate<T>(builder, dispose: dispose),
|
||||
updateShouldNotify: null,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Allows to specify parameters to [Provider].
|
||||
Provider.value({
|
||||
Key key,
|
||||
@required T value,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate<T>(value),
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
Provider._({
|
||||
Key key,
|
||||
@required ValueStateDelegate<T> delegate,
|
||||
this.updateShouldNotify,
|
||||
this.child,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
/// Obtains the nearest [Provider<T>] up its widget tree and returns its value.
|
||||
///
|
||||
/// If [listen] is `true` (default), later value changes will trigger a new
|
||||
/// [State.build] to widgets, and [State.didChangeDependencies] for
|
||||
/// [StatefulWidget].
|
||||
static T of<T>(BuildContext context, {bool listen = true}) {
|
||||
// this is required to get generic Type
|
||||
final type = _typeOf<InheritedProvider<T>>();
|
||||
final provider = listen
|
||||
? context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>
|
||||
: context.ancestorInheritedElementForWidgetOfExactType(type)?.widget
|
||||
as InheritedProvider<T>;
|
||||
|
||||
if (provider == null) {
|
||||
throw ProviderNotFoundError(T, context.widget.runtimeType);
|
||||
}
|
||||
|
||||
return provider._value;
|
||||
}
|
||||
|
||||
/// A sanity check to prevent misuse of [Provider] when a variant should be used.
|
||||
///
|
||||
/// By default, [debugCheckInvalidValueType] will throw if `value` is a [Listenable]
|
||||
/// or a [Stream].
|
||||
/// In release mode, [debugCheckInvalidValueType] does nothing.
|
||||
///
|
||||
/// This check can be disabled altogether by setting [debugCheckInvalidValueType]
|
||||
/// to `null` like so:
|
||||
///
|
||||
/// ```dart
|
||||
/// void main() {
|
||||
/// Provider.debugCheckInvalidValueType = null;
|
||||
/// runApp(MyApp());
|
||||
/// }
|
||||
/// ```
|
||||
static void Function<T>(T value) debugCheckInvalidValueType = <T>(T value) {
|
||||
assert(() {
|
||||
if (value is Listenable || value is Stream) {
|
||||
throw FlutterError('''
|
||||
Tried to use Provider with a subtype of Listenable/Stream ($T).
|
||||
|
||||
This is likely a mistake, as Provider will not automatically update dependents
|
||||
when $T is updated. Instead, consider changing Provider for more specific
|
||||
implementation that handles the update mecanism, such as:
|
||||
|
||||
- ListenableProvider
|
||||
- ChangeNotifierProvider
|
||||
- ValueListenableProvider
|
||||
- StreamProvider
|
||||
|
||||
Alternatively, if you are making your own provider, consider using InheritedProvider.
|
||||
|
||||
If you think that this is not an error, you can disable this check by setting
|
||||
Provider.debugCheckInvalidValueType to `null` in your main file:
|
||||
|
||||
```
|
||||
void main() {
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
''');
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
};
|
||||
|
||||
/// User-provided custom logic for [InheritedWidget.updateShouldNotify].
|
||||
final UpdateShouldNotify<T> updateShouldNotify;
|
||||
|
||||
@override
|
||||
Provider<T> cloneWithChild(Widget child) {
|
||||
return Provider._(
|
||||
key: key,
|
||||
delegate: delegate,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// The widget that is below the current [Provider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(() {
|
||||
Provider.debugCheckInvalidValueType?.call<T>(delegate.value);
|
||||
return true;
|
||||
}());
|
||||
return InheritedProvider<T>(
|
||||
value: delegate.value,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The error that will be thrown if [Provider.of<T>] fails to find a
|
||||
/// [Provider<T>] as an ancestor of the [BuildContext] used.
|
||||
class ProviderNotFoundError extends Error {
|
||||
/// The type of the value being retrieved
|
||||
final Type valueType;
|
||||
|
||||
/// The type of the Widget requesting the value
|
||||
final Type widgetType;
|
||||
|
||||
/// Create a ProviderNotFound error with the type represented as a String.
|
||||
ProviderNotFoundError(
|
||||
this.valueType,
|
||||
this.widgetType,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
Error: Could not find the correct Provider<$valueType> above this $widgetType Widget
|
||||
|
||||
To fix, please:
|
||||
|
||||
* Ensure the Provider<$valueType> is an ancestor to this $widgetType Widget
|
||||
* Provide types to Provider<$valueType>
|
||||
* Provide types to Consumer<$valueType>
|
||||
* Provide types to Provider.of<$valueType>()
|
||||
* Always use package imports. Ex: `import 'package:my_app/my_code.dart';
|
||||
* Ensure the correct `context` is being used.
|
||||
|
||||
If none of these solutions work, please file a bug at:
|
||||
https://github.com/rrousselGit/provider/issues
|
||||
''';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
import 'package:flutter_web/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'delegate_widget.dart';
|
||||
import 'provider.dart';
|
||||
|
||||
typedef ProviderBuilder<R> = Widget Function(
|
||||
BuildContext context, R value, Widget child);
|
||||
|
||||
typedef ProxyProviderBuilder<T, R> = R Function(
|
||||
BuildContext context, T value, R previous);
|
||||
|
||||
typedef ProxyProviderBuilder2<T, T2, R> = R Function(
|
||||
BuildContext context, T value, T2 value2, R previous);
|
||||
|
||||
typedef ProxyProviderBuilder3<T, T2, T3, R> = R Function(
|
||||
BuildContext context, T value, T2 value2, T3 value3, R previous);
|
||||
|
||||
typedef ProxyProviderBuilder4<T, T2, T3, T4, R> = R Function(
|
||||
BuildContext context, T value, T2 value2, T3 value3, T4 value4, R previous);
|
||||
|
||||
typedef ProxyProviderBuilder5<T, T2, T3, T4, T5, R> = R Function(
|
||||
BuildContext context,
|
||||
T value,
|
||||
T2 value2,
|
||||
T3 value3,
|
||||
T4 value4,
|
||||
T5 value5,
|
||||
R previous,
|
||||
);
|
||||
|
||||
typedef ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> = R Function(
|
||||
BuildContext context,
|
||||
T value,
|
||||
T2 value2,
|
||||
T3 value3,
|
||||
T4 value4,
|
||||
T5 value5,
|
||||
T6 value6,
|
||||
R previous,
|
||||
);
|
||||
|
||||
/// A [StatefulWidget] that uses [ProxyProviderState] as [State].
|
||||
abstract class ProxyProviderWidget extends StatefulWidget {
|
||||
/// Initializes [key] for subclasses.
|
||||
const ProxyProviderWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ProxyProviderState<ProxyProviderWidget> createState();
|
||||
|
||||
@override
|
||||
ProxyProviderElement createElement() => ProxyProviderElement(this);
|
||||
}
|
||||
|
||||
/// A [State] with an added life-cycle: [didUpdateDependencies].
|
||||
///
|
||||
/// Widgets such as [ProxyProvider] are expected to build their
|
||||
/// value from within [didUpdateDependencies] instead of [didChangeDependencies].
|
||||
abstract class ProxyProviderState<T extends ProxyProviderWidget>
|
||||
extends State<T> {
|
||||
/// To not confuse with [didChangeDependencies].
|
||||
///
|
||||
/// As opposed to [didChangeDependencies], [didUpdateDependencies] is
|
||||
/// guaranteed to be followed by a call to [build], and will be called only
|
||||
/// once after _all_ dependencies have changed.
|
||||
///
|
||||
/// This guarantees that everything is up to date when [didUpdateDependencies]
|
||||
/// is called, and that the widget will not be unmounted before updates are
|
||||
/// applied. It is therefore safe to make network http calls or mutations
|
||||
/// inside this life-cycle.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void didUpdateDependencies() {}
|
||||
}
|
||||
|
||||
/// An [Element] that uses a [ProxyProviderWidget] as its configuration.
|
||||
class ProxyProviderElement extends StatefulElement {
|
||||
/// Creates an element that uses the given widget as its configuration.
|
||||
ProxyProviderElement(ProxyProviderWidget widget) : super(widget);
|
||||
|
||||
@override
|
||||
ProxyProviderWidget get widget => super.widget as ProxyProviderWidget;
|
||||
|
||||
@override
|
||||
ProxyProviderState<ProxyProviderWidget> get state =>
|
||||
super.state as ProxyProviderState<ProxyProviderWidget>;
|
||||
|
||||
bool _didChangeDependencies = true;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_didChangeDependencies = true;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build() {
|
||||
if (_didChangeDependencies) {
|
||||
_didChangeDependencies = false;
|
||||
state.didUpdateDependencies();
|
||||
}
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
abstract class Void {}
|
||||
|
||||
/// A base class for custom "Proxy provider".
|
||||
///
|
||||
/// See [ProxyProvider] for a concrete implementation.
|
||||
abstract class ProxyProviderBase<R> extends ProxyProviderWidget {
|
||||
/// Initializes [key], [initialBuilder] and [dispose] for subclasses.
|
||||
ProxyProviderBase({
|
||||
Key key,
|
||||
this.initialBuilder,
|
||||
this.dispose,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Builds the initial value passed as `previous` to [didChangeDependencies].
|
||||
///
|
||||
/// If omitted, [didChangeDependencies] will be called with `null` instead.
|
||||
final ValueBuilder<R> initialBuilder;
|
||||
|
||||
/// Optionally allows to clean-up resources when the widget is removed from
|
||||
/// the tree.
|
||||
final Disposer<R> dispose;
|
||||
|
||||
@override
|
||||
_ProxyProviderState<R> createState() => _ProxyProviderState();
|
||||
|
||||
/// Builds the value passed to [build] by combining [InheritedWidget].
|
||||
///
|
||||
/// [didChangeDependencies] will be called once when the widget is mounted,
|
||||
/// and once whenever any of the [InheritedWidget] which [ProxyProviderBase]
|
||||
/// depends on updates.
|
||||
///
|
||||
/// It is safe to perform side-effects in this method.
|
||||
R didChangeDependencies(BuildContext context, R previous);
|
||||
|
||||
/// An equivalent of [StatelessWidget.build].
|
||||
///
|
||||
/// `value` is the latest result of [didChangeDependencies].
|
||||
///
|
||||
/// [build] should avoid depending on [InheritedWidget]. Instead these
|
||||
/// [InheritedWidget] should be used inside [didChangeDependencies].
|
||||
Widget build(BuildContext context, R value);
|
||||
}
|
||||
|
||||
class _ProxyProviderState<R> extends ProxyProviderState<ProxyProviderBase<R>> {
|
||||
R _value;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = widget.initialBuilder?.call(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDependencies() {
|
||||
super.didUpdateDependencies();
|
||||
_value = widget.didChangeDependencies(context, _value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.build(context, _value);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.dispose != null) {
|
||||
widget.dispose(context, _value);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
// ignore: public_member_api_docs
|
||||
class NumericProxyProvider<T, T2, T3, T4, T5, T6, R>
|
||||
extends ProxyProviderBase<R> implements SingleChildCloneableWidget {
|
||||
// ignore: public_member_api_docs
|
||||
NumericProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required this.builder,
|
||||
this.updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
this.child,
|
||||
}) : assert(builder != null),
|
||||
super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
dispose: dispose,
|
||||
);
|
||||
|
||||
/// The widget that is below the current [Provider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@template provider.proxyprovider.builder}
|
||||
/// Builds the value passed to [InheritedProvider] by combining [InheritedWidget].
|
||||
///
|
||||
/// [builder] will be called once when the widget is mounted,
|
||||
/// and once whenever any of the [InheritedWidget] which [ProxyProvider]
|
||||
/// depends on updates.
|
||||
///
|
||||
/// It is safe to perform side-effects in this method.
|
||||
/// {@endtemplate}
|
||||
final Function builder;
|
||||
|
||||
/// The [UpdateShouldNotify] passed to [InheritedProvider].
|
||||
final UpdateShouldNotify<R> updateShouldNotify;
|
||||
|
||||
@override
|
||||
NumericProxyProvider<T, T2, T3, T4, T5, T6, R> cloneWithChild(Widget child) {
|
||||
return NumericProxyProvider(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, R value) {
|
||||
assert(() {
|
||||
Provider.debugCheckInvalidValueType?.call(value);
|
||||
return true;
|
||||
}());
|
||||
return InheritedProvider<R>(
|
||||
value: value,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
R didChangeDependencies(BuildContext context, R previous) {
|
||||
final arguments = <dynamic>[
|
||||
context,
|
||||
Provider.of<T>(context),
|
||||
];
|
||||
|
||||
if (T2 != Void) arguments.add(Provider.of<T2>(context));
|
||||
if (T3 != Void) arguments.add(Provider.of<T3>(context));
|
||||
if (T4 != Void) arguments.add(Provider.of<T4>(context));
|
||||
if (T5 != Void) arguments.add(Provider.of<T5>(context));
|
||||
if (T6 != Void) arguments.add(Provider.of<T6>(context));
|
||||
|
||||
arguments.add(previous);
|
||||
return Function.apply(builder, arguments) as R;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template provider.proxyprovider}
|
||||
/// A provider that builds a value based on other providers.
|
||||
///
|
||||
/// The exposed value is built through [builder], and then passed
|
||||
/// to [InheritedProvider].
|
||||
///
|
||||
/// As opposed to the `builder` parameter of [Provider], [builder]
|
||||
/// may be called more than once. It will be called once when the widget is
|
||||
/// mounted, then once whenever any of the [InheritedWidget] which [ProxyProvider]
|
||||
/// depends emits an update.
|
||||
///
|
||||
/// [ProxyProvider] comes in different variants such as [ProxyProvider2].
|
||||
/// This only changes the [builder] function, such that it takes
|
||||
/// a different number of arguments.
|
||||
/// The `2` in [ProxyProvider2] means that [builder] builds its
|
||||
/// value from **2** other providers.
|
||||
///
|
||||
/// All variations of [builder] will receive the [BuildContext]
|
||||
/// as first parameter, and the previously built value as last parameter.
|
||||
///
|
||||
/// This previously built value will be `null` by default, unless
|
||||
/// [initialBuilder] is specified – in which case, it will be the
|
||||
/// value returned by [initialBuilder].
|
||||
///
|
||||
/// [builder] must not be `null`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Provider], which matches the behavior of [ProxyProvider] without
|
||||
/// dependending on other providers.
|
||||
/// {@endtemplate}
|
||||
class ProxyProvider<T, R>
|
||||
extends NumericProxyProvider<T, Void, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder<T, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder<T, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder<T, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ProxyProvider2<T, T2, R>
|
||||
extends NumericProxyProvider<T, T2, Void, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider2({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder2<T, T2, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder2<T, T2, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder2<T, T2, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ProxyProvider3<T, T2, T3, R>
|
||||
extends NumericProxyProvider<T, T2, T3, Void, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider3({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder3<T, T2, T3, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder3<T, T2, T3, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder3<T, T2, T3, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ProxyProvider4<T, T2, T3, T4, R>
|
||||
extends NumericProxyProvider<T, T2, T3, T4, Void, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider4({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder4<T, T2, T3, T4, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder4<T, T2, T3, T4, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder4<T, T2, T3, T4, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ProxyProvider5<T, T2, T3, T4, T5, R>
|
||||
extends NumericProxyProvider<T, T2, T3, T4, T5, Void, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider5({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder5<T, T2, T3, T4, T5, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder5<T, T2, T3, T4, T5, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder5<T, T2, T3, T4, T5, R>;
|
||||
}
|
||||
|
||||
/// {@macro provider.proxyprovider}
|
||||
class ProxyProvider6<T, T2, T3, T4, T5, T6, R>
|
||||
extends NumericProxyProvider<T, T2, T3, T4, T5, T6, R> {
|
||||
/// Initializes [key] for subclasses.
|
||||
ProxyProvider6({
|
||||
Key key,
|
||||
ValueBuilder<R> initialBuilder,
|
||||
@required ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> builder,
|
||||
UpdateShouldNotify<R> updateShouldNotify,
|
||||
Disposer<R> dispose,
|
||||
Widget child,
|
||||
}) : super(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: builder,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
dispose: dispose,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@override
|
||||
ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R> get builder =>
|
||||
super.builder as ProxyProviderBuilder6<T, T2, T3, T4, T5, T6, R>;
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import 'package:flutter)_web/foundation.dart';
|
||||
import 'package:flutter)_web/widgets.dart';
|
||||
|
||||
import 'delegate_widget.dart';
|
||||
import 'listenable_provider.dart' show ListenableProvider;
|
||||
import 'provider.dart';
|
||||
|
||||
/// Listens to a [ValueListenable] and expose its current value.
|
||||
class ValueListenableProvider<T> extends ValueDelegateWidget<ValueListenable<T>>
|
||||
implements SingleChildCloneableWidget {
|
||||
/// Creates a [ValueNotifier] using [builder] and automatically dispose it
|
||||
/// when [ValueListenableProvider] is removed from the tree.
|
||||
///
|
||||
/// [builder] must not be `null`.
|
||||
///
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ValueListenable]
|
||||
/// * [ListenableProvider], similar to [ValueListenableProvider] but for any kind of [Listenable].
|
||||
ValueListenableProvider({
|
||||
Key key,
|
||||
@required ValueBuilder<ValueNotifier<T>> builder,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate<ValueNotifier<T>>(
|
||||
builder,
|
||||
dispose: _dispose,
|
||||
),
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
/// Listens to [value] and exposes its current value.
|
||||
///
|
||||
/// Changing [value] will stop listening to the previous [value] and listen the new one.
|
||||
/// Removing [ValueListenableProvider] from the tree will also stop listening to [value].
|
||||
///
|
||||
/// ```dart
|
||||
/// ValueListenable<int> foo;
|
||||
///
|
||||
/// ValueListenableProvider<int>.value(
|
||||
/// valueListenable: foo,
|
||||
/// child: Container(),
|
||||
/// );
|
||||
/// ```
|
||||
ValueListenableProvider.value({
|
||||
Key key,
|
||||
@required ValueListenable<T> value,
|
||||
UpdateShouldNotify<T> updateShouldNotify,
|
||||
Widget child,
|
||||
}) : this._(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(value),
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
|
||||
ValueListenableProvider._({
|
||||
Key key,
|
||||
@required ValueStateDelegate<ValueListenable<T>> delegate,
|
||||
this.updateShouldNotify,
|
||||
this.child,
|
||||
}) : super(key: key, delegate: delegate);
|
||||
|
||||
static void _dispose(BuildContext context, ValueNotifier notifier) {
|
||||
notifier.dispose();
|
||||
}
|
||||
|
||||
/// The widget that is below the current [ValueListenableProvider] widget in the
|
||||
/// tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// {@macro provider.updateshouldnotify}
|
||||
final UpdateShouldNotify<T> updateShouldNotify;
|
||||
|
||||
@override
|
||||
ValueListenableProvider<T> cloneWithChild(Widget child) {
|
||||
return ValueListenableProvider._(
|
||||
key: key,
|
||||
delegate: delegate,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<T>(
|
||||
valueListenable: delegate.value,
|
||||
builder: (_, value, child) {
|
||||
return InheritedProvider<T>(
|
||||
value: value,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
name: provider
|
||||
description: A dependency injection system built with widgets for widgets. provider is mostly syntax sugar for InheritedWidget, to make common use-cases straightforward.
|
||||
version: 3.0.0+1
|
||||
homepage: https://github.com/rrousselGit/provider
|
||||
authors:
|
||||
- Remi Rousselet <darky12s@gmail.com>
|
||||
- Flutter Team <flutter-dev@googlegroups.com>
|
||||
|
||||
environment:
|
||||
# You must be using Flutter >=1.5.0 or Dart >=2.3.0
|
||||
sdk: '>=2.3.0-dev.0.1 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter_web: any
|
||||
flutter_web_ui: any
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.4.0
|
||||
build_web_compilers: ^2.0.0
|
||||
pedantic: ^1.0.0
|
||||
|
||||
dependency_overrides:
|
||||
flutter_web:
|
||||
git:
|
||||
url: https://github.com/flutter/flutter_web
|
||||
path: packages/flutter_web
|
||||
flutter_web_ui:
|
||||
git:
|
||||
url: https://github.com/flutter/flutter_web
|
||||
path: packages/flutter_web_ui
|
|
@ -0,0 +1,336 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('ChangeNotifierProvider', () {
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
var notifier = ChangeNotifier();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: notifier),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ChangeNotifierProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ChangeNotifier(),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
group('default constructor', () {
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
key: keyProvider,
|
||||
value: notifier,
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('works with null (default)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
testWidgets('works with null (builder)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
group('stateful constructor', () {
|
||||
testWidgets('called with context', (tester) async {
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
key: key,
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
verify(builder(key.currentContext)).called(1);
|
||||
});
|
||||
test('throws if builder is null', () {
|
||||
expect(
|
||||
// ignore: prefer_const_constructors
|
||||
() => ChangeNotifierProvider(
|
||||
builder: null,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
key: keyProvider,
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('stateful builder called once', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
when(builder(any)).thenReturn(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ChangeNotifierProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose called on unmount', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
when(builder(any)).thenReturn(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ChangeNotifierProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([notifier.removeListener(listener), notifier.dispose()]);
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final notifier = MockNotifier();
|
||||
var notifier2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>.value(
|
||||
value: notifier,
|
||||
child: Container(),
|
||||
));
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider<ChangeNotifier>(
|
||||
builder: (_) {
|
||||
return notifier2;
|
||||
},
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
verify(notifier.removeListener(listener)).called(1);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful notifier',
|
||||
(tester) async {
|
||||
final ChangeNotifier notifier = MockNotifier();
|
||||
var notifier2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => notifier,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final listener = verify(notifier.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(notifier);
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), notifier2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
notifier.removeListener(listener),
|
||||
notifier.dispose(),
|
||||
]);
|
||||
verifyNoMoreInteractions(notifier);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ChangeNotifierProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets('changing notifier rebuilds descendants', (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
var notifier = ChangeNotifier();
|
||||
Widget build() {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: Builder(builder: (context) {
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, true);
|
||||
|
||||
var previousNotifier = notifier;
|
||||
notifier = ChangeNotifier();
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, true);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(previousNotifier.hasListeners, false);
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(notifier.hasListeners, false);
|
||||
});
|
||||
testWidgets("rebuilding with the same provider don't rebuilds descendants",
|
||||
(tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: builder,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
|
||||
await tester.pumpWidget(ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
});
|
||||
testWidgets('notifylistener rebuilds descendants', (tester) async {
|
||||
final notifier = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: (context) {
|
||||
// subscribe
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
var changeNotifierProvider = ChangeNotifierProvider.value(
|
||||
value: notifier,
|
||||
child: child,
|
||||
);
|
||||
await tester.pumpWidget(changeNotifierProvider);
|
||||
|
||||
clearInteractions(builder);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
notifier.notifyListeners();
|
||||
await Future<void>.value();
|
||||
await tester.pump();
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), notifier);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart' show ProxyProviderBase;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class _ListenableCombined = Combined with ChangeNotifier;
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<_ListenableCombined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ChangeNotifierProxyProvider', () {
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() =>
|
||||
ChangeNotifierProxyProvider<A, _ListenableCombined>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, ChangeNotifier>(
|
||||
initialBuilder: (_) => null,
|
||||
builder: (_, __, value) => value,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('rebuilds dependendents when listeners are called',
|
||||
(tester) async {
|
||||
final notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, ValueNotifier<int>>(
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
)
|
||||
],
|
||||
child: Consumer<ValueNotifier<int>>(builder: (_, value, __) {
|
||||
return Text(
|
||||
value.value.toString(),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('0'), findsNothing);
|
||||
});
|
||||
testWidgets('disposes of created value', (tester) async {
|
||||
final notifier = MockNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ChangeNotifierProxyProvider<int, MockNotifier>(
|
||||
key: key,
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(notifier.dispose()).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('ChangeNotifierProxyProvider variants', () {
|
||||
Finder findProxyProvider() => find
|
||||
.byWidgetPredicate((widget) => widget is ProxyProviderBase<Combined>);
|
||||
testWidgets('ChangeNotifierProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider2<A, B, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
_ListenableCombined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider3<A, B, C, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider4<A, B, C, D, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider5<A, B, C, D, E, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(context, _ListenableCombined(null, null, null), a,
|
||||
b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ChangeNotifierProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ChangeNotifierProxyProvider6<A, B, C, D, E, F, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
Element findElementOfWidget<T extends Widget>() {
|
||||
return find.byType(T).first.evaluate().first;
|
||||
}
|
||||
|
||||
Type typeOf<T>() => T;
|
||||
|
||||
class ValueBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context);
|
||||
}
|
||||
|
||||
class DisposerMock<T> extends Mock {
|
||||
void call(BuildContext context, T value);
|
||||
}
|
||||
|
||||
class MockNotifier extends Mock implements ChangeNotifier {}
|
||||
|
||||
class BuilderMock extends Mock {
|
||||
Widget call(BuildContext context);
|
||||
}
|
||||
|
||||
class UpdateShouldNotifyMock<T> extends Mock {
|
||||
bool call(T old, T newValue);
|
||||
}
|
||||
|
||||
class A with DiagnosticableTreeMixin {}
|
||||
|
||||
class B with DiagnosticableTreeMixin {}
|
||||
|
||||
class C with DiagnosticableTreeMixin {}
|
||||
|
||||
class D with DiagnosticableTreeMixin {}
|
||||
|
||||
class E with DiagnosticableTreeMixin {}
|
||||
|
||||
class F with DiagnosticableTreeMixin {}
|
||||
|
||||
class ConsumerBuilderMock extends Mock {
|
||||
Widget call(Combined foo);
|
||||
}
|
||||
|
||||
class CombinerMock extends Mock {
|
||||
Combined call(BuildContext context, A a, Combined foo);
|
||||
}
|
||||
|
||||
class ProviderBuilderMock extends Mock {
|
||||
Widget call(BuildContext context, Combined value, Widget child);
|
||||
}
|
||||
|
||||
class Combined extends DiagnosticableTree {
|
||||
final A a;
|
||||
final B b;
|
||||
final C c;
|
||||
final D d;
|
||||
final E e;
|
||||
final F f;
|
||||
final Combined previous;
|
||||
final BuildContext context;
|
||||
|
||||
Combined(this.context, this.previous, this.a,
|
||||
[this.b, this.c, this.d, this.e, this.f]);
|
||||
|
||||
@override
|
||||
// ignore: hash_and_equals
|
||||
bool operator ==(Object other) =>
|
||||
other is Combined &&
|
||||
other.context == context &&
|
||||
other.previous == previous &&
|
||||
other.a == a &&
|
||||
other.b == b &&
|
||||
other.c == c &&
|
||||
other.e == e &&
|
||||
other.f == f;
|
||||
|
||||
// fancy toString for debug purposes.
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.properties.addAll([
|
||||
DiagnosticsProperty('a', a, defaultValue: null),
|
||||
DiagnosticsProperty('b', b, defaultValue: null),
|
||||
DiagnosticsProperty('c', c, defaultValue: null),
|
||||
DiagnosticsProperty('d', d, defaultValue: null),
|
||||
DiagnosticsProperty('e', e, defaultValue: null),
|
||||
DiagnosticsProperty('f', f, defaultValue: null),
|
||||
DiagnosticsProperty('previous', previous, defaultValue: null),
|
||||
DiagnosticsProperty('context', context, defaultValue: null),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class MyListenable extends ChangeNotifier {}
|
||||
|
||||
class MyStream extends Stream<void> {
|
||||
@override
|
||||
StreamSubscription<void> listen(void Function(void event) onData,
|
||||
{Function onError, void Function() onDone, bool cancelOnError}) {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class ConsumerBuilderMock extends Mock {
|
||||
Widget call(Combined foo);
|
||||
}
|
||||
|
||||
class Combined {
|
||||
final A a;
|
||||
final B b;
|
||||
final C c;
|
||||
final D d;
|
||||
final E e;
|
||||
final F f;
|
||||
final Widget child;
|
||||
final BuildContext context;
|
||||
|
||||
Combined(this.context, this.child, this.a,
|
||||
[this.b, this.c, this.d, this.e, this.f]);
|
||||
|
||||
@override
|
||||
// ignore: hash_and_equals
|
||||
bool operator ==(Object other) =>
|
||||
other is Combined &&
|
||||
other.context == context &&
|
||||
other.child == child &&
|
||||
other.a == a &&
|
||||
other.b == b &&
|
||||
other.c == c &&
|
||||
other.e == e &&
|
||||
other.f == f;
|
||||
}
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
final provider = MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
],
|
||||
);
|
||||
|
||||
final mock = ConsumerBuilderMock();
|
||||
setUp(() {
|
||||
when(mock(any)).thenReturn(Container());
|
||||
});
|
||||
tearDown(() {
|
||||
clearInteractions(mock);
|
||||
});
|
||||
|
||||
group('consumer', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer<A>(
|
||||
key: key,
|
||||
builder: (context, value, child) =>
|
||||
mock(Combined(context, child, value)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer<int>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('consumer2', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer2<A, B>(
|
||||
key: key,
|
||||
builder: (context, value, v2, child) =>
|
||||
mock(Combined(context, child, value, v2)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer2<A, B>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer3', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer3<A, B, C>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, child) =>
|
||||
mock(Combined(context, child, value, v2, v3)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer3<A, B, C>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer4', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer4<A, B, C, D>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer4<A, B, C, D>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer5', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer5<A, B, C, D, E>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, v5, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4, v5)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d, e)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer5<A, B, C, D, E>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
group('consumer6', () {
|
||||
testWidgets('obtains value from Provider<T>', (tester) async {
|
||||
final key = GlobalKey();
|
||||
final child = Container();
|
||||
|
||||
await tester.pumpWidget(
|
||||
provider.cloneWithChild(
|
||||
Consumer6<A, B, C, D, E, F>(
|
||||
key: key,
|
||||
builder: (context, value, v2, v3, v4, v5, v6, child) =>
|
||||
mock(Combined(context, child, value, v2, v3, v4, v5, v6)),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
verify(mock(Combined(key.currentContext, child, a, b, c, d, e, f)));
|
||||
});
|
||||
testWidgets('crashed with no builder', (tester) async {
|
||||
expect(
|
||||
() => Consumer6<A, B, C, D, E, F>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
// ignore_for_file: invalid_use_of_protected_member
|
||||
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('DelegateWidget', () {
|
||||
testWidgets(
|
||||
"can't call context.inheritFromWidgetOfExactType from first initDelegate",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: InitDelegate(),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets(
|
||||
"can't call context.inheritFromWidgetOfExactType from initDelegate after an update",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: SingleValueDelegate(42),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
await tester.pumpWidget(Provider.value(
|
||||
value: 42,
|
||||
child: TestDelegateWidget(
|
||||
delegate: InitDelegate(),
|
||||
child: Container(),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('mount initializes setState and context and calls initDelegate',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, key.currentContext);
|
||||
expect(state.setState, key.currentState.setState);
|
||||
|
||||
verify(state.initDelegateMock(
|
||||
key.currentContext,
|
||||
key.currentState.setState,
|
||||
)).called(1);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state.disposeMock);
|
||||
});
|
||||
testWidgets(
|
||||
'rebuilding with delegate of the same type calls didUpdateDelegate',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final state2 = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state2,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state.disposeMock);
|
||||
|
||||
expect(state2.context, context);
|
||||
expect(state2.setState, setState);
|
||||
verify(state2.didUpdateDelegate(state)).called(1);
|
||||
verifyZeroInteractions(state2.initDelegateMock);
|
||||
verifyNoMoreInteractions(state2.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state2.disposeMock);
|
||||
});
|
||||
testWidgets(
|
||||
'rebuilding with delegate of a different type disposes the previous and init the new one',
|
||||
(tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final state2 = MockStateDelegate<String>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state2,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verify(state.disposeMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state.disposeMock);
|
||||
|
||||
expect(state2.context, key.currentContext);
|
||||
expect(state2.setState, key.currentState.setState);
|
||||
|
||||
verify(state2.initDelegateMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state2.initDelegateMock);
|
||||
verifyNoMoreInteractions(state2.didUpdateDelegateMock);
|
||||
verifyZeroInteractions(state2.disposeMock);
|
||||
});
|
||||
|
||||
testWidgets('unmounting the widget calls delegate.dispose', (tester) async {
|
||||
final state = MockStateDelegate<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(TestDelegateWidget(
|
||||
key: key,
|
||||
delegate: state,
|
||||
child: Container(),
|
||||
));
|
||||
clearInteractions(state.initDelegateMock);
|
||||
|
||||
final context = key.currentContext;
|
||||
final setState = key.currentState.setState;
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
expect(state.context, isNull);
|
||||
expect(state.setState, isNull);
|
||||
verifyZeroInteractions(state.initDelegateMock);
|
||||
verifyZeroInteractions(state.didUpdateDelegateMock);
|
||||
verify(state.disposeMock(context, setState)).called(1);
|
||||
verifyNoMoreInteractions(state.disposeMock);
|
||||
});
|
||||
|
||||
test('throws if delegate is null', () {
|
||||
expect(
|
||||
() => TestDelegateWidget(child: Container()),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('SingleValueDelegate', () {
|
||||
test('implements ValueStateDelegate', () {
|
||||
expect(
|
||||
SingleValueDelegate(0),
|
||||
isInstanceOf<ValueStateDelegate<int>>(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('stores and update value', (tester) async {
|
||||
int value;
|
||||
BuildContext context;
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(BuilderDelegateWidget<SingleValueDelegate<int>>(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(0),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(0));
|
||||
|
||||
await tester.pumpWidget(BuilderDelegateWidget<SingleValueDelegate<int>>(
|
||||
key: key,
|
||||
delegate: SingleValueDelegate(42),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
});
|
||||
});
|
||||
|
||||
group('BuilderStateDelegate', () {
|
||||
test('implements ValueStateDelegate', () {
|
||||
expect(
|
||||
BuilderStateDelegate((_) => 42),
|
||||
isInstanceOf<ValueStateDelegate<int>>(),
|
||||
);
|
||||
});
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() => BuilderStateDelegate<dynamic>(null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('initialize value and never recreate it', (tester) async {
|
||||
int value;
|
||||
BuildContext context;
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate((_) => 42),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: BuilderStateDelegate((_) => 0),
|
||||
builder: (c, d) {
|
||||
value = d.value;
|
||||
context = c;
|
||||
return Container();
|
||||
},
|
||||
));
|
||||
|
||||
expect(context, equals(key.currentContext));
|
||||
expect(value, equals(42));
|
||||
});
|
||||
|
||||
testWidgets('initialize value and never recreate it', (tester) async {
|
||||
final disposeMock = DisposerMock<int>();
|
||||
final key = GlobalKey();
|
||||
final delegate2 = BuilderStateDelegate(
|
||||
(_) => 42,
|
||||
dispose: disposeMock,
|
||||
);
|
||||
|
||||
await tester
|
||||
.pumpWidget(BuilderDelegateWidget<BuilderStateDelegate<int>>(
|
||||
key: key,
|
||||
delegate: delegate2,
|
||||
builder: (_, __) => Container(),
|
||||
));
|
||||
|
||||
final context = key.currentContext;
|
||||
|
||||
verifyZeroInteractions(disposeMock);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(disposeMock(context, 42)).called(1);
|
||||
verifyNoMoreInteractions(disposeMock);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class InitDelegate extends StateDelegate {
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
Provider.of<int>(context);
|
||||
}
|
||||
}
|
||||
|
||||
class InitDelegateMock extends Mock {
|
||||
void call(BuildContext context, StateSetter setState);
|
||||
}
|
||||
|
||||
class DidUpdateDelegateMock extends Mock {
|
||||
void call(StateDelegate old);
|
||||
}
|
||||
|
||||
class DisposeMock extends Mock {
|
||||
void call(BuildContext context, StateSetter setState);
|
||||
}
|
||||
|
||||
class MockStateDelegate<T> extends StateDelegate {
|
||||
final disposeMock = DisposeMock();
|
||||
final initDelegateMock = InitDelegateMock();
|
||||
final didUpdateDelegateMock = DidUpdateDelegateMock();
|
||||
|
||||
@override
|
||||
void initDelegate() {
|
||||
super.initDelegate();
|
||||
initDelegateMock(context, setState);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateDelegate(StateDelegate old) {
|
||||
super.didUpdateDelegate(old);
|
||||
didUpdateDelegateMock(old);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeMock(context, setState);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class BuilderDelegateWidget<T extends ValueStateDelegate<dynamic>>
|
||||
extends ValueDelegateWidget<dynamic> {
|
||||
BuilderDelegateWidget({Key key, this.builder, T delegate})
|
||||
: super(key: key, delegate: delegate);
|
||||
|
||||
final Widget Function(BuildContext context, T delegate) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, delegate as T);
|
||||
}
|
||||
|
||||
class TestDelegateWidget extends DelegateWidget {
|
||||
TestDelegateWidget({Key key, this.child, StateDelegate delegate})
|
||||
: super(key: key, delegate: delegate);
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => child;
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
// tests forked from stream_provider_test.dart
|
||||
// by replacing Stream with Future and StreamController with Completer
|
||||
|
||||
class ErrorBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context, Object error);
|
||||
}
|
||||
|
||||
class MockFuture<T> extends Mock implements Future<T> {}
|
||||
|
||||
void main() {
|
||||
group('FutureProvider', () {
|
||||
testWidgets('update when value change', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
completer.complete(0);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
});
|
||||
|
||||
testWidgets("don't notify descendants when rebuilding by default",
|
||||
(tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('pass down keys', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
key: key,
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<FutureProvider>());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
final completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
completer.complete(1);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets("don't listen future again if it doesn't change",
|
||||
(tester) async {
|
||||
final future = MockFuture<int>();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: future,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(future.then<void>(any, onError: anyNamed('onError'))).called(1);
|
||||
verifyNoMoreInteractions(future);
|
||||
});
|
||||
|
||||
testWidgets('future emits error and catchError is missing', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
completer.completeError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
final exception = tester.takeException() as Object;
|
||||
expect(exception, isFlutterError);
|
||||
expect(exception.toString(), equals('''
|
||||
An exception was throw by Future<int> listened by
|
||||
FutureProvider<int>, but no `catchError` was provided.
|
||||
|
||||
Exception:
|
||||
42
|
||||
'''));
|
||||
});
|
||||
testWidgets('calls catchError if future emits error', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
final key = GlobalKey();
|
||||
final catchError = ErrorBuilderMock<int>();
|
||||
when(catchError(any, 42)).thenReturn(0);
|
||||
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: completer.future,
|
||||
catchError: catchError,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
completer.completeError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
|
||||
final context = findElementOfWidget<FutureProvider<int>>();
|
||||
|
||||
verify(catchError(context, 42));
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
FutureProvider<int>.value(value: Future<int>.value()),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = FutureProvider<int>.value(
|
||||
value: Future<int>.value(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = FutureProvider<int>(
|
||||
builder: (_) => Future<int>.value(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider<int>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
|
||||
group('stateful constructor', () {
|
||||
test('crashes if builder is null', () {
|
||||
expect(
|
||||
() => FutureProvider<int>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('create future with builder', (tester) async {
|
||||
final completer = Completer<int>();
|
||||
|
||||
final builder = ValueBuilderMock<Future<int>>();
|
||||
when(builder(any)).thenAnswer((_) => completer.future);
|
||||
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<FutureProvider<int>>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
|
||||
// extra build to see if builder isn't called again
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
var completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => completer.future,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
completer.complete(1);
|
||||
// futures are asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final key = GlobalKey();
|
||||
final completer = Completer<int>();
|
||||
await tester.pumpWidget(FutureProvider<int>.value(
|
||||
value: completer.future,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => Future.value(42),
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets('Changing from stateful to default constructor',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(FutureProvider<int>(
|
||||
builder: (_) => Future.value(0),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(FutureProvider.value(
|
||||
value: Future.value(1),
|
||||
child: Container(key: key),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('ListenableProvider', () {
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
var listenable = ChangeNotifier();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
ListenableProvider.value(value: listenable),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ListenableProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ChangeNotifier(),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
dispose: (_, n) {},
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
});
|
||||
|
||||
group('value constructor', () {
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
key: keyProvider,
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets("don't listen again if listenable instance doesn't change",
|
||||
(tester) async {
|
||||
final listenable = MockNotifier();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(listenable.addListener(any)).called(1);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('works with null (default)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
testWidgets('works with null (builder)', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), null);
|
||||
});
|
||||
group('stateful constructor', () {
|
||||
testWidgets('called with context', (tester) async {
|
||||
final builder = ValueBuilderMock<ChangeNotifier>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
key: key,
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
verify(builder(key.currentContext)).called(1);
|
||||
});
|
||||
test('throws if builder is null', () {
|
||||
expect(
|
||||
// ignore: prefer_const_constructors
|
||||
() => ListenableProvider(
|
||||
builder: null,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
testWidgets('pass down key', (tester) async {
|
||||
final keyProvider = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
key: keyProvider,
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
expect(
|
||||
keyProvider.currentWidget,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
testWidgets('stateful builder called once', (tester) async {
|
||||
final listenable = MockNotifier();
|
||||
final builder = ValueBuilderMock<Listenable>();
|
||||
when(builder(any)).thenReturn(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('dispose called on unmount', (tester) async {
|
||||
final listenable = MockNotifier();
|
||||
final builder = ValueBuilderMock<Listenable>();
|
||||
final disposer = DisposerMock<Listenable>();
|
||||
when(builder(any)).thenReturn(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: builder,
|
||||
dispose: disposer,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
verifyNoMoreInteractions(builder);
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
listenable.removeListener(listener),
|
||||
disposer(context, listenable),
|
||||
]);
|
||||
verifyNoMoreInteractions(builder);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets('dispose can be null', (tester) async {
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: (_) => ChangeNotifier(),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final listenable = MockNotifier();
|
||||
var listenable2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>.value(
|
||||
value: listenable,
|
||||
child: Container(),
|
||||
));
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider<ChangeNotifier>(
|
||||
builder: (_) {
|
||||
return listenable2;
|
||||
},
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
verify(listenable.removeListener(listener)).called(1);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful listenable',
|
||||
(tester) async {
|
||||
final ChangeNotifier listenable = MockNotifier();
|
||||
final disposer = DisposerMock<Listenable>();
|
||||
var listenable2 = ChangeNotifier();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(ListenableProvider(
|
||||
builder: (_) => listenable,
|
||||
dispose: disposer,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<ListenableProvider<ChangeNotifier>>();
|
||||
|
||||
final listener = verify(listenable.addListener(captureAny)).captured.first
|
||||
as VoidCallback;
|
||||
clearInteractions(listenable);
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<ChangeNotifier>(key.currentContext), listenable2);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyInOrder([
|
||||
listenable.removeListener(listener),
|
||||
disposer(context, listenable),
|
||||
]);
|
||||
verifyNoMoreInteractions(listenable);
|
||||
});
|
||||
|
||||
testWidgets('changing listenable rebuilds descendants', (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
var listenable = ChangeNotifier();
|
||||
Widget build() {
|
||||
return ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: Builder(builder: (context) {
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, true);
|
||||
|
||||
var previousNotifier = listenable;
|
||||
listenable = ChangeNotifier();
|
||||
await tester.pumpWidget(build());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, true);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(previousNotifier.hasListeners, false);
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(listenable.hasListeners, false);
|
||||
});
|
||||
testWidgets("rebuilding with the same provider don't rebuilds descendants",
|
||||
(tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: builder,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
listenable.notifyListeners();
|
||||
await tester.pump();
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
|
||||
await tester.pumpWidget(ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
});
|
||||
testWidgets('notifylistener rebuilds descendants', (tester) async {
|
||||
final listenable = ChangeNotifier();
|
||||
final keyChild = GlobalKey();
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenReturn(Container());
|
||||
|
||||
final child = Builder(
|
||||
key: keyChild,
|
||||
builder: (context) {
|
||||
// subscribe
|
||||
Provider.of<ChangeNotifier>(context);
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
var changeNotifierProvider = ListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
);
|
||||
await tester.pumpWidget(changeNotifierProvider);
|
||||
|
||||
clearInteractions(builder);
|
||||
// ignore: invalid_use_of_protected_member
|
||||
listenable.notifyListeners();
|
||||
await Future<void>.value();
|
||||
await tester.pump();
|
||||
verify(builder(any)).called(1);
|
||||
expect(Provider.of<ChangeNotifier>(keyChild.currentContext), listenable);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart' show ProxyProviderBase;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class _ListenableCombined = Combined with ChangeNotifier;
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<_ListenableCombined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ListenableProxyProvider', () {
|
||||
test('throws if builder is missing', () {
|
||||
expect(
|
||||
() => ListenableProxyProvider<A, _ListenableCombined>(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('rebuilds dependendents when listeners are called',
|
||||
(tester) async {
|
||||
final notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ListenableProxyProvider<int, ValueNotifier<int>>(
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
)
|
||||
],
|
||||
child: Consumer<ValueNotifier<int>>(builder: (_, value, __) {
|
||||
return Text(
|
||||
value.value.toString(),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('0'), findsNothing);
|
||||
});
|
||||
testWidgets('disposes of created value', (tester) async {
|
||||
final dispose = DisposerMock<ValueNotifier<int>>();
|
||||
final notifier = ValueNotifier(0);
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: 0),
|
||||
ListenableProxyProvider<int, ValueNotifier<int>>(
|
||||
key: key,
|
||||
initialBuilder: (_) => notifier,
|
||||
builder: (_, count, value) => value..value = count,
|
||||
dispose: dispose,
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final context = key.currentContext;
|
||||
verifyZeroInteractions(dispose);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(dispose(context, notifier)).called(1);
|
||||
verifyNoMoreInteractions(dispose);
|
||||
});
|
||||
});
|
||||
|
||||
group('ListenableProxyProvider variants', () {
|
||||
Finder findProxyProvider() => find
|
||||
.byWidgetPredicate((widget) => widget is ProxyProviderBase<Combined>);
|
||||
testWidgets('ListenableProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider2<A, B, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
_ListenableCombined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider3<A, B, C, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider4<A, B, C, D, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider5<A, B, C, D, E, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(context, _ListenableCombined(null, null, null), a,
|
||||
b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ListenableProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ListenableProxyProvider6<A, B, C, D, E, F, _ListenableCombined>(
|
||||
initialBuilder: (_) => _ListenableCombined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
_ListenableCombined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
_ListenableCombined(
|
||||
context, _ListenableCombined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/widgets.dart' hide TypeMatcher;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
Type _typeOf<T>() => T;
|
||||
|
||||
Matcher throwsProviderNotFound({Type widgetType, Type valueType}) {
|
||||
return throwsA(const TypeMatcher<ProviderNotFoundError>()
|
||||
.having((err) => err.valueType, 'valueType', valueType)
|
||||
.having((err) => err.widgetType, 'widgetType', widgetType));
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('MultiProvider', () {
|
||||
test('cloneWithChild works', () {
|
||||
final provider = MultiProvider(
|
||||
providers: [],
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, newChild);
|
||||
expect(clone.providers, provider.providers);
|
||||
expect(clone.key, provider.key);
|
||||
});
|
||||
test('throw if providers is null', () {
|
||||
expect(
|
||||
() => MultiProvider(providers: null, child: Container()),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('MultiProvider with empty providers returns child',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(const MultiProvider(
|
||||
providers: [],
|
||||
child: Text(
|
||||
'Foo',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('Foo'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MultiProvider children can only access parent providers',
|
||||
(tester) async {
|
||||
final k1 = GlobalKey();
|
||||
final k2 = GlobalKey();
|
||||
final k3 = GlobalKey();
|
||||
final p1 = Provider.value(key: k1, value: 42);
|
||||
final p2 = Provider.value(key: k2, value: 'foo');
|
||||
final p3 = Provider.value(key: k3, value: 44.0);
|
||||
|
||||
final keyChild = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [p1, p2, p3],
|
||||
child: Text('Foo', key: keyChild, textDirection: TextDirection.ltr),
|
||||
));
|
||||
|
||||
expect(find.text('Foo'), findsOneWidget);
|
||||
|
||||
// p1 cannot access to p1/p2/p3
|
||||
expect(
|
||||
() => Provider.of<int>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: int, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<String>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: String, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<double>(k1.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<int>>()),
|
||||
);
|
||||
|
||||
// p2 can access only p1
|
||||
expect(Provider.of<int>(k2.currentContext), 42);
|
||||
expect(
|
||||
() => Provider.of<String>(k2.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: String, widgetType: _typeOf<Provider<String>>()),
|
||||
);
|
||||
expect(
|
||||
() => Provider.of<double>(k2.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<String>>()),
|
||||
);
|
||||
|
||||
// p3 can access both p1 and p2
|
||||
expect(Provider.of<int>(k3.currentContext), 42);
|
||||
expect(Provider.of<String>(k3.currentContext), 'foo');
|
||||
expect(
|
||||
() => Provider.of<double>(k3.currentContext),
|
||||
throwsProviderNotFound(
|
||||
valueType: double, widgetType: _typeOf<Provider<double>>()),
|
||||
);
|
||||
|
||||
// the child can access them all
|
||||
expect(Provider.of<int>(keyChild.currentContext), 42);
|
||||
expect(Provider.of<String>(keyChild.currentContext), 'foo');
|
||||
expect(Provider.of<double>(keyChild.currentContext), 44);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart' hide TypeMatcher;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:test_api/test_api.dart' show TypeMatcher;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
group('Provider', () {
|
||||
testWidgets('throws if the provided value is a Listenable/Stream',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyListenable(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyStream(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('debugCheckInvalidValueType can be disabled', (tester) async {
|
||||
final previous = Provider.debugCheckInvalidValueType;
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
addTearDown(() => Provider.debugCheckInvalidValueType = previous);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyListenable(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider.value(
|
||||
value: MyStream(),
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
});
|
||||
test('cloneWithChild works', () {
|
||||
final provider = Provider.value(
|
||||
value: 42,
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
updateShouldNotify: (int _, int __) => true,
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, equals(newChild));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(provider.updateShouldNotify, equals(clone.updateShouldNotify));
|
||||
});
|
||||
testWidgets('simple usage', (tester) async {
|
||||
var buildCount = 0;
|
||||
int value;
|
||||
double second;
|
||||
|
||||
// We voluntarily reuse the builder instance so that later call to pumpWidget
|
||||
// don't call builder again unless subscribed to an inheritedWidget
|
||||
final builder = Builder(
|
||||
builder: (context) {
|
||||
buildCount++;
|
||||
value = Provider.of(context);
|
||||
second = Provider.of(context, listen: false);
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 42,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(value, equals(42));
|
||||
expect(second, equals(24.0));
|
||||
expect(buildCount, equals(1));
|
||||
|
||||
// nothing changed
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 42,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
// didn't rebuild
|
||||
expect(buildCount, equals(1));
|
||||
|
||||
// changed a value we are subscribed to
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 24.0,
|
||||
child: Provider<int>.value(
|
||||
value: 43,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(value, equals(43));
|
||||
expect(second, equals(24.0));
|
||||
// got rebuilt
|
||||
expect(buildCount, equals(2));
|
||||
|
||||
// changed a value we are _not_ subscribed to
|
||||
await tester.pumpWidget(
|
||||
Provider<double>.value(
|
||||
value: 20.0,
|
||||
child: Provider<int>.value(
|
||||
value: 43,
|
||||
child: builder,
|
||||
),
|
||||
),
|
||||
);
|
||||
// didn't get rebuilt
|
||||
expect(buildCount, equals(2));
|
||||
});
|
||||
|
||||
testWidgets('throws an error if no provider found', (tester) async {
|
||||
await tester.pumpWidget(Builder(builder: (context) {
|
||||
Provider.of<String>(context);
|
||||
return Container();
|
||||
}));
|
||||
|
||||
expect(
|
||||
tester.takeException(),
|
||||
const TypeMatcher<ProviderNotFoundError>()
|
||||
.having((err) => err.valueType, 'valueType', String)
|
||||
.having((err) => err.widgetType, 'widgetType', Builder)
|
||||
.having((err) => err.toString(), 'toString()', '''
|
||||
Error: Could not find the correct Provider<String> above this Builder Widget
|
||||
|
||||
To fix, please:
|
||||
|
||||
* Ensure the Provider<String> is an ancestor to this Builder Widget
|
||||
* Provide types to Provider<String>
|
||||
* Provide types to Consumer<String>
|
||||
* Provide types to Provider.of<String>()
|
||||
* Always use package imports. Ex: `import 'package:my_app/my_code.dart';
|
||||
* Ensure the correct `context` is being used.
|
||||
|
||||
If none of these solutions work, please file a bug at:
|
||||
https://github.com/rrousselGit/provider/issues
|
||||
'''),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('update should notify', (tester) async {
|
||||
int old;
|
||||
int curr;
|
||||
var callCount = 0;
|
||||
final updateShouldNotify = (int o, int c) {
|
||||
callCount++;
|
||||
old = o;
|
||||
curr = c;
|
||||
return o != c;
|
||||
};
|
||||
|
||||
var buildCount = 0;
|
||||
int buildValue;
|
||||
final builder = Builder(builder: (BuildContext context) {
|
||||
buildValue = Provider.of(context);
|
||||
buildCount++;
|
||||
return Container();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 24,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(0));
|
||||
expect(buildCount, equals(1));
|
||||
expect(buildValue, equals(24));
|
||||
|
||||
// value changed
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 25,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(1));
|
||||
expect(old, equals(24));
|
||||
expect(curr, equals(25));
|
||||
expect(buildCount, equals(2));
|
||||
expect(buildValue, equals(25));
|
||||
|
||||
// value didnt' change
|
||||
await tester.pumpWidget(
|
||||
Provider<int>.value(
|
||||
value: 25,
|
||||
updateShouldNotify: updateShouldNotify,
|
||||
child: builder,
|
||||
),
|
||||
);
|
||||
expect(callCount, equals(2));
|
||||
expect(old, equals(25));
|
||||
expect(curr, equals(25));
|
||||
expect(buildCount, equals(2));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,552 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:provider/src/proxy_provider.dart'
|
||||
show NumericProxyProvider, Void;
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
Finder findProvider<T>() => find.byWidgetPredicate(
|
||||
// comparing `runtimeType` instead of using `is` because `is` accepts subclasses but InheritedWidgets don't.
|
||||
(widget) => widget.runtimeType == typeOf<InheritedProvider<T>>());
|
||||
|
||||
void main() {
|
||||
final a = A();
|
||||
final b = B();
|
||||
final c = C();
|
||||
final d = D();
|
||||
final e = E();
|
||||
final f = F();
|
||||
|
||||
final combinedConsumerMock = ConsumerBuilderMock();
|
||||
setUp(() => when(combinedConsumerMock(any)).thenReturn(Container()));
|
||||
tearDown(() {
|
||||
clearInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
final mockConsumer = Consumer<Combined>(
|
||||
builder: (context, combined, child) => combinedConsumerMock(combined),
|
||||
);
|
||||
|
||||
group('ProxyProvider', () {
|
||||
final combiner = CombinerMock();
|
||||
setUp(() {
|
||||
when(combiner(any, any, any)).thenAnswer((Invocation invocation) {
|
||||
return Combined(
|
||||
invocation.positionalArguments.first as BuildContext,
|
||||
invocation.positionalArguments[2] as Combined,
|
||||
invocation.positionalArguments[1] as A,
|
||||
);
|
||||
});
|
||||
});
|
||||
tearDown(() => clearInteractions(combiner));
|
||||
|
||||
Finder findProxyProvider<T>() => find.byWidgetPredicate(
|
||||
(widget) => widget is NumericProxyProvider<T, Void, Void, Void, Void,
|
||||
Void, Combined>,
|
||||
);
|
||||
|
||||
testWidgets('throws if the provided value is a Listenable/Stream',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyListenable>(
|
||||
builder: (_, __, ___) => MyListenable(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyStream>(
|
||||
builder: (_, __, ___) => MyStream(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isFlutterError);
|
||||
});
|
||||
testWidgets('debugCheckInvalidValueType can be disabled', (tester) async {
|
||||
final previous = Provider.debugCheckInvalidValueType;
|
||||
Provider.debugCheckInvalidValueType = null;
|
||||
addTearDown(() => Provider.debugCheckInvalidValueType = previous);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyListenable>(
|
||||
builder: (_, __, ___) => MyListenable(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, MyStream>(
|
||||
builder: (_, __, ___) => MyStream(),
|
||||
)
|
||||
],
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('initialBuilder creates initial value', (tester) async {
|
||||
final initialBuilder = ValueBuilderMock<Combined>();
|
||||
final key = GlobalKey();
|
||||
|
||||
when(initialBuilder(any)).thenReturn(Combined(null, null, null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
key: key,
|
||||
initialBuilder: initialBuilder,
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final details = verify(initialBuilder(captureAny))..called(1);
|
||||
expect(details.captured.first, equals(key.currentContext));
|
||||
|
||||
verify(combiner(key.currentContext, a, Combined(null, null, null)));
|
||||
});
|
||||
testWidgets('consume another providers', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verify(combinedConsumerMock(Combined(context, null, a))).called(1);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
|
||||
verify(combiner(context, a, null)).called(1);
|
||||
verifyNoMoreInteractions(combiner);
|
||||
});
|
||||
|
||||
test('throws if builder is null', () {
|
||||
// ignore: prefer_const_constructors
|
||||
expect(() => ProxyProvider<A, Combined>(builder: null),
|
||||
throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('rebuild descendants if value change', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final a2 = A();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a2),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: combiner,
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verifyInOrder([
|
||||
combiner(context, a, null),
|
||||
combinedConsumerMock(Combined(context, null, a)),
|
||||
combiner(context, a2, Combined(context, null, a)),
|
||||
combinedConsumerMock(Combined(context, Combined(context, null, a), a2)),
|
||||
]);
|
||||
|
||||
verifyNoMoreInteractions(combiner);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
});
|
||||
testWidgets('call dispose when unmounted with the latest result',
|
||||
(tester) async {
|
||||
final dispose = DisposerMock<Combined>();
|
||||
final dispose2 = DisposerMock<Combined>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: combiner, dispose: dispose)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final a2 = A();
|
||||
|
||||
// ProxyProvider creates a new Combined instance
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a2),
|
||||
ProxyProvider<A, Combined>(builder: combiner, dispose: dispose2)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyZeroInteractions(dispose);
|
||||
verify(
|
||||
dispose2(context, Combined(context, Combined(context, null, a), a2)));
|
||||
});
|
||||
|
||||
testWidgets("don't rebuild descendants if value doesn't change",
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: (c, a, p) => combiner(c, a, null),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: a,
|
||||
updateShouldNotify: (A _, A __) => true,
|
||||
),
|
||||
ProxyProvider<A, Combined>(
|
||||
builder: (c, a, p) {
|
||||
combiner(c, a, p);
|
||||
return p;
|
||||
},
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
verifyInOrder([
|
||||
combiner(context, a, null),
|
||||
combinedConsumerMock(Combined(context, null, a)),
|
||||
combiner(context, a, Combined(context, null, a)),
|
||||
]);
|
||||
|
||||
verifyNoMoreInteractions(combiner);
|
||||
verifyNoMoreInteractions(combinedConsumerMock);
|
||||
});
|
||||
|
||||
testWidgets('pass down updateShouldNotify', (tester) async {
|
||||
var buildCount = 0;
|
||||
final child = Builder(builder: (context) {
|
||||
buildCount++;
|
||||
|
||||
return Text(
|
||||
'$buildCount ${Provider.of<String>(context)}',
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
});
|
||||
|
||||
final shouldNotify = UpdateShouldNotifyMock<String>();
|
||||
when(shouldNotify('Hello', 'Hello')).thenReturn(false);
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider<String>.value(
|
||||
value: 'Hello', updateShouldNotify: (_, __) => true),
|
||||
ProxyProvider<String, String>(
|
||||
builder: (_, value, __) => value,
|
||||
updateShouldNotify: shouldNotify,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider<String>.value(
|
||||
value: 'Hello', updateShouldNotify: (_, __) => true),
|
||||
ProxyProvider<String, String>(
|
||||
builder: (_, value, __) => value,
|
||||
updateShouldNotify: shouldNotify,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(shouldNotify('Hello', 'Hello')).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
|
||||
expect(find.text('2 Hello'), findsNothing);
|
||||
expect(find.text('1 Hello'), findsOneWidget);
|
||||
});
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) => Combined(c, p, a)),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
final context = tester.element(findProxyProvider<A>());
|
||||
|
||||
expect(
|
||||
Provider.of<Combined>(key.currentContext),
|
||||
Combined(context, null, a),
|
||||
);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ProxyProvider<A, B>(
|
||||
key: const Key('42'),
|
||||
initialBuilder: (_) => null,
|
||||
builder: (_, __, ___) => null,
|
||||
updateShouldNotify: (_, __) => null,
|
||||
dispose: (_, __) {},
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialBuilder, equals(provider.initialBuilder));
|
||||
expect(clone.builder, equals(provider.builder));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.dispose, equals(provider.dispose));
|
||||
// expect(clone.providerBuilder, equals(provider.providerBuilder));
|
||||
});
|
||||
|
||||
// useful for libraries such as Mobx where events are synchronously dispatched
|
||||
testWidgets(
|
||||
'builder callback can trigger descendants setState synchronously',
|
||||
(tester) async {
|
||||
var statefulBuildCount = 0;
|
||||
void Function(VoidCallback) setState;
|
||||
|
||||
final statefulBuilder = StatefulBuilder(builder: (_, s) {
|
||||
setState = s;
|
||||
statefulBuildCount++;
|
||||
return Container();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) => Combined(c, p, a)),
|
||||
],
|
||||
child: statefulBuilder,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: A()),
|
||||
ProxyProvider<A, Combined>(builder: (c, a, p) {
|
||||
setState(() {});
|
||||
return Combined(c, p, a);
|
||||
}),
|
||||
],
|
||||
child: statefulBuilder,
|
||||
));
|
||||
|
||||
expect(
|
||||
statefulBuildCount,
|
||||
2,
|
||||
reason: 'builder must not be called asynchronously',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('ProxyProvider variants', () {
|
||||
Finder findProxyProvider<A, B, C, D, E, F>() => find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is NumericProxyProvider<A, B, C, D, E, F, Combined>,
|
||||
);
|
||||
testWidgets('ProxyProvider2', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider2<A, B, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, previous) =>
|
||||
Combined(context, previous, a, b),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, Void, Void, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider3', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider3<A, B, C, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, previous) =>
|
||||
Combined(context, previous, a, b, c),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, C, Void, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider4', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider4<A, B, C, D, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, previous) =>
|
||||
Combined(context, previous, a, b, c, d),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context =
|
||||
tester.element(findProxyProvider<A, B, C, D, Void, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider5', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider5<A, B, C, D, E, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, previous) =>
|
||||
Combined(context, previous, a, b, c, d, e),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A, B, C, D, E, Void>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d, e, null),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
testWidgets('ProxyProvider6', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: a),
|
||||
Provider.value(value: b),
|
||||
Provider.value(value: c),
|
||||
Provider.value(value: d),
|
||||
Provider.value(value: e),
|
||||
Provider.value(value: f),
|
||||
ProxyProvider6<A, B, C, D, E, F, Combined>(
|
||||
initialBuilder: (_) => Combined(null, null, null),
|
||||
builder: (context, a, b, c, d, e, f, previous) =>
|
||||
Combined(context, previous, a, b, c, d, e, f),
|
||||
)
|
||||
],
|
||||
child: mockConsumer,
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(findProxyProvider<A, B, C, D, E, F>());
|
||||
|
||||
verify(
|
||||
combinedConsumerMock(
|
||||
Combined(context, Combined(null, null, null), a, b, c, d, e, f),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('root/package/provider/README.md and root/README.mf are identical',
|
||||
() async {
|
||||
final root = await File.fromUri(Uri.parse(
|
||||
'${Directory.current.parent.parent.parent.path}/README.md'))
|
||||
.readAsString();
|
||||
final local = await File.fromUri(
|
||||
Uri.parse('${Directory.current.parent.path}/README.md'))
|
||||
.readAsString();
|
||||
|
||||
expect(root, equals(local));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/src/provider.dart';
|
||||
|
||||
class ValueBuilder extends Mock {
|
||||
int call(BuildContext context);
|
||||
}
|
||||
|
||||
class Dispose extends Mock {
|
||||
void call(BuildContext context, int value);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('cloneWithChild works', () {
|
||||
final provider = Provider(
|
||||
builder: (_) => 42,
|
||||
child: Container(),
|
||||
key: const ValueKey(42),
|
||||
);
|
||||
|
||||
final newChild = Container();
|
||||
final clone = provider.cloneWithChild(newChild);
|
||||
expect(clone.child, equals(newChild));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(provider.updateShouldNotify, equals(clone.updateShouldNotify));
|
||||
});
|
||||
test('asserts', () {
|
||||
expect(
|
||||
() => Provider<dynamic>(builder: null, child: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
// don't throw
|
||||
Provider<dynamic>(builder: (_) => null, child: null);
|
||||
});
|
||||
|
||||
testWidgets('calls builder only once', (tester) async {
|
||||
final builder = ValueBuilder();
|
||||
await tester.pumpWidget(Provider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(Provider<int>(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('dispose', (tester) async {
|
||||
final dispose = Dispose();
|
||||
const key = ValueKey(42);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Provider<int>(
|
||||
key: key,
|
||||
builder: (_) => 42,
|
||||
dispose: dispose,
|
||||
child: Container(),
|
||||
),
|
||||
);
|
||||
|
||||
final context = tester.element(find.byKey(key));
|
||||
|
||||
verifyZeroInteractions(dispose);
|
||||
await tester.pumpWidget(Container());
|
||||
verify(dispose(context, 42)).called(1);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
class ErrorBuilderMock<T> extends Mock {
|
||||
T call(BuildContext context, Object error);
|
||||
}
|
||||
|
||||
class MockStreamController<T> extends Mock implements StreamController<T> {}
|
||||
|
||||
class MockStream<T> extends Mock implements Stream<T> {}
|
||||
|
||||
void main() {
|
||||
group('streamProvider', () {
|
||||
testWidgets('update when value change (default) ', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final providerKey = GlobalKey();
|
||||
final childKey = GlobalKey();
|
||||
BuildContext context;
|
||||
|
||||
await tester.pumpWidget(StreamProvider(
|
||||
key: providerKey,
|
||||
builder: (c) {
|
||||
context = c;
|
||||
return controller.stream;
|
||||
},
|
||||
child: Container(key: childKey),
|
||||
));
|
||||
|
||||
expect(context, equals(providerKey.currentContext));
|
||||
expect(Provider.of<int>(childKey.currentContext), null);
|
||||
|
||||
controller.add(0);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(childKey.currentContext), 0);
|
||||
});
|
||||
testWidgets('update when value change (.value)', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
controller.add(0);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
});
|
||||
|
||||
testWidgets("don't notify descendants when rebuilding by default",
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: child,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: child,
|
||||
));
|
||||
|
||||
verify(builder(any)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('pass down keys', (tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
key: key,
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<StreamProvider>());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
final controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
controller.add(1);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets("don't listen again if stream instance doesn't change",
|
||||
(tester) async {
|
||||
final stream = MockStream<int>();
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: stream,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(
|
||||
stream.listen(any,
|
||||
onError: anyNamed('onError'),
|
||||
onDone: anyNamed('onDone'),
|
||||
cancelOnError: anyNamed('cancelOnError')),
|
||||
).called(1);
|
||||
verifyNoMoreInteractions(stream);
|
||||
});
|
||||
|
||||
testWidgets('throws if stream has error and catchError is missing',
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
controller.addError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
final exception = tester.takeException() as Object;
|
||||
expect(exception, isFlutterError);
|
||||
expect(exception.toString(), equals('''
|
||||
An exception was throw by _ControllerStream<int> listened by
|
||||
StreamProvider<int>, but no `catchError` was provided.
|
||||
|
||||
Exception:
|
||||
42
|
||||
'''));
|
||||
});
|
||||
testWidgets('calls catchError if present and stream has error',
|
||||
(tester) async {
|
||||
final controller = StreamController<int>();
|
||||
final key = GlobalKey();
|
||||
final catchError = ErrorBuilderMock<int>();
|
||||
when(catchError(any, 42)).thenReturn(0);
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: controller.stream,
|
||||
catchError: catchError,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
controller.addError(42);
|
||||
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 0);
|
||||
|
||||
final context = findElementOfWidget<StreamProvider<int>>();
|
||||
|
||||
verify(catchError(context, 42));
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
StreamProvider<int>.value(value: const Stream<int>.empty()),
|
||||
],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = StreamProvider<int>.value(
|
||||
value: const Stream<int>.empty(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = StreamProvider<int>.controller(
|
||||
builder: (_) => StreamController<int>(),
|
||||
initialData: 42,
|
||||
child: Container(),
|
||||
catchError: (_, __) => 42,
|
||||
key: const Key('42'),
|
||||
updateShouldNotify: (_, __) => true,
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
expect(clone.key, equals(provider.key));
|
||||
expect(clone.initialData, equals(provider.initialData));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.catchError, equals(provider.catchError));
|
||||
});
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(StreamProvider<int>.value(
|
||||
value: null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
});
|
||||
|
||||
group('stateful constructor', () {
|
||||
test('crashes if builder is null', () {
|
||||
expect(
|
||||
() => StreamProvider<int>.controller(builder: null),
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('works with null', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => null,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), null);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
});
|
||||
|
||||
testWidgets('create and dispose stream with builder', (tester) async {
|
||||
final realController = StreamController<int>();
|
||||
final controller = MockStreamController<int>();
|
||||
when(controller.stream).thenAnswer((_) => realController.stream);
|
||||
|
||||
final builder = ValueBuilderMock<StreamController<int>>();
|
||||
when(builder(any)).thenReturn(controller);
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final context = findElementOfWidget<StreamProvider<int>>();
|
||||
|
||||
verify(builder(context)).called(1);
|
||||
clearInteractions(controller);
|
||||
|
||||
// extra build to see if builder isn't called again
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: builder,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verifyNoMoreInteractions(builder);
|
||||
verify(controller.close());
|
||||
});
|
||||
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(null, 1)).thenReturn(true);
|
||||
|
||||
var controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
controller.add(1);
|
||||
// adding to stream is asynchronous so we have to delay the pump
|
||||
await Future.microtask(tester.pump);
|
||||
|
||||
verify(shouldNotify(null, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Changing from default to stateful constructor calls stateful builder',
|
||||
(tester) async {
|
||||
final key = GlobalKey();
|
||||
final controller = StreamController<int>();
|
||||
await tester.pumpWidget(StreamProvider<int>.value(
|
||||
value: controller.stream,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final realController2 = StreamController<int>();
|
||||
final controller2 = MockStreamController<int>();
|
||||
when(controller2.stream).thenAnswer((_) => realController2.stream);
|
||||
|
||||
realController2.add(42);
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller2,
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(controller2.close()).called(1);
|
||||
});
|
||||
testWidgets(
|
||||
'Changing from stateful to default constructor dispose correctly stateful stream',
|
||||
(tester) async {
|
||||
final realController = StreamController<int>();
|
||||
final controller = MockStreamController<int>();
|
||||
when(controller.stream).thenAnswer((_) => realController.stream);
|
||||
|
||||
final key = GlobalKey();
|
||||
|
||||
await tester.pumpWidget(StreamProvider<int>.controller(
|
||||
builder: (_) => controller,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
await tester.pumpWidget(StreamProvider.value(
|
||||
value: Stream<int>.fromIterable([42]),
|
||||
child: Container(key: key),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
|
||||
verify(controller.close()).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'common.dart';
|
||||
|
||||
class ValueNotifierMock<T> extends Mock implements ValueNotifier<T> {}
|
||||
|
||||
void main() {
|
||||
group('valueListenableProvider', () {
|
||||
testWidgets(
|
||||
'disposing ValueListenableProvider on a builder constructor disposes of the ValueNotifier',
|
||||
(tester) async {
|
||||
final mock = ValueNotifierMock<int>();
|
||||
await tester.pumpWidget(ValueListenableProvider<int>(
|
||||
builder: (_) => mock,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
final listener =
|
||||
verify(mock.addListener(captureAny)).captured.first as VoidCallback;
|
||||
|
||||
clearInteractions(mock);
|
||||
await tester.pumpWidget(Container());
|
||||
verifyInOrder([
|
||||
mock.removeListener(listener),
|
||||
mock.dispose(),
|
||||
]);
|
||||
verifyNoMoreInteractions(mock);
|
||||
});
|
||||
testWidgets('rebuilds when value change', (tester) async {
|
||||
final listenable = ValueNotifier(0);
|
||||
|
||||
final child = Builder(
|
||||
builder: (context) => Text(Provider.of<int>(context).toString(),
|
||||
textDirection: TextDirection.ltr));
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
listenable.value++;
|
||||
await tester.pump();
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("don't rebuild dependents by default", (tester) async {
|
||||
final builder = BuilderMock();
|
||||
when(builder(any)).thenAnswer((invocation) {
|
||||
final context = invocation.positionalArguments.first as BuildContext;
|
||||
Provider.of<int>(context);
|
||||
return Container();
|
||||
});
|
||||
|
||||
final listenable = ValueNotifier(0);
|
||||
final child = Builder(builder: builder);
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verify(builder(any)).called(1);
|
||||
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: listenable,
|
||||
child: child,
|
||||
));
|
||||
verifyNoMoreInteractions(builder);
|
||||
});
|
||||
|
||||
testWidgets('pass keys', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
key: key,
|
||||
value: ValueNotifier(42),
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
expect(key.currentWidget, isInstanceOf<ValueListenableProvider<int>>());
|
||||
});
|
||||
|
||||
testWidgets("don't listen again if stream instance doesn't change",
|
||||
(tester) async {
|
||||
final valueNotifier = ValueNotifierMock<int>();
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: valueNotifier,
|
||||
child: Container(),
|
||||
));
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: valueNotifier,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verify(valueNotifier.addListener(any)).called(1);
|
||||
verify(valueNotifier.value);
|
||||
verifyNoMoreInteractions(valueNotifier);
|
||||
});
|
||||
testWidgets('pass updateShouldNotify', (tester) async {
|
||||
final shouldNotify = UpdateShouldNotifyMock<int>();
|
||||
when(shouldNotify(0, 1)).thenReturn(true);
|
||||
|
||||
var notifier = ValueNotifier(0);
|
||||
await tester.pumpWidget(ValueListenableProvider.value(
|
||||
value: notifier,
|
||||
updateShouldNotify: shouldNotify,
|
||||
child: Container(),
|
||||
));
|
||||
|
||||
verifyZeroInteractions(shouldNotify);
|
||||
|
||||
notifier.value++;
|
||||
await tester.pump();
|
||||
|
||||
verify(shouldNotify(0, 1)).called(1);
|
||||
verifyNoMoreInteractions(shouldNotify);
|
||||
});
|
||||
|
||||
testWidgets('works with MultiProvider', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [ValueListenableProvider.value(value: ValueNotifier(42))],
|
||||
child: Container(key: key),
|
||||
));
|
||||
|
||||
expect(Provider.of<int>(key.currentContext), 42);
|
||||
});
|
||||
|
||||
test('works with MultiProvider #2', () {
|
||||
final provider = ValueListenableProvider.value(
|
||||
key: const Key('42'),
|
||||
value: ValueNotifier<int>(42),
|
||||
child: Container(),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
});
|
||||
test('works with MultiProvider #3', () {
|
||||
final provider = ValueListenableProvider<int>(
|
||||
builder: (_) => ValueNotifier<int>(42),
|
||||
child: Container(),
|
||||
key: const Key('42'),
|
||||
);
|
||||
var child2 = Container();
|
||||
final clone = provider.cloneWithChild(child2);
|
||||
|
||||
expect(clone.child, equals(child2));
|
||||
expect(clone.key, equals(provider.key));
|
||||
// ignore: invalid_use_of_protected_member
|
||||
expect(clone.delegate, equals(provider.delegate));
|
||||
expect(clone.updateShouldNotify, equals(provider.updateShouldNotify));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
cd $1
|
||||
flutter packages get
|
||||
flutter format --set-exit-if-changed lib test
|
||||
flutter analyze --no-current-package lib test/
|
||||
flutter test --no-pub --coverage
|
||||
# resets to the original state
|
||||
cd -
|
Loading…
Reference in New Issue