NEW: added provider plugin

This commit is contained in:
JohnE 2019-06-11 13:55:39 -07:00
parent 9a8c050ad8
commit 5e28ef8ea5
38 changed files with 6903 additions and 0 deletions

View File

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

182
packages/provider/README.md Normal file
View File

@ -0,0 +1,182 @@
[![Build Status](https://travis-ci.org/rrousselGit/provider.svg?branch=master)](https://travis-ci.org/rrousselGit/provider)
[![pub package](https://img.shields.io/pub/v/provider.svg)](https://pub.dartlang.org/packages/provider) [![codecov](https://codecov.io/gh/rrousselGit/provider/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/provider) [![Gitter](https://badges.gitter.im/flutter_provider/community.svg)](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. |

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,182 @@
[![Build Status](https://travis-ci.org/rrousselGit/provider.svg?branch=master)](https://travis-ci.org/rrousselGit/provider)
[![pub package](https://img.shields.io/pub/v/provider.svg)](https://pub.dartlang.org/packages/provider) [![codecov](https://codecov.io/gh/rrousselGit/provider/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/provider) [![Gitter](https://badges.gitter.im/flutter_provider/community.svg)](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. |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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