-
-
Notifications
You must be signed in to change notification settings - Fork 500
/
provider.dart
337 lines (308 loc) · 10.2 KB
/
provider.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:nested/nested.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
part 'inherited_provider.dart';
part 'deferred_inherited_provider.dart';
/// A provider that merges multiple providers into a single linear widget tree.
/// It is used to improve readability and reduce boilerplate code of having to
/// nest multiple layers of providers.
///
/// As such, we're going from:
///
/// ```dart
/// Provider<Something>(
/// create: (_) => Something(),
/// child: Provider<SomethingElse>(
/// create: (_) => SomethingElse(),
/// child: Provider<AnotherThing>(
/// create: (_) => AnotherThing(),
/// child: someWidget,
/// ),
/// ),
/// ),
/// ```
///
/// To:
///
/// ```dart
/// MultiProvider(
/// providers: [
/// Provider<Something>(create: (_) => Something()),
/// Provider<SomethingElse>(create: (_) => SomethingElse()),
/// Provider<AnotherThing>(create: (_) => AnotherThing()),
/// ],
/// child: someWidget,
/// )
/// ```
///
/// The widget tree representation of the two approaches are identical.
class MultiProvider extends Nested {
/// Build a tree of providers from a list of [SingleChildWidget].
MultiProvider({
Key key,
@required List<SingleChildWidget> providers,
Widget child,
}) : assert(providers != null),
super(key: key, children: providers, child: child);
}
/// A [Provider] that manages the lifecycle of the value it provides by
/// delegating to a pair of [Create] and [Dispose].
///
/// 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]. [Create] 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.
///
/// ```dart
/// class Model {
/// void dispose() {}
/// }
///
/// class Stateless extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return Provider<Model>(
/// create: (context) => Model(),
/// dispose: (context, value) => value.dispose(),
/// child: ...,
/// );
/// }
/// }
/// ```
///
/// It is worth noting that the `create` callback is lazily called.
/// It is called the first time the value is read, instead of the first time
/// [Provider] is inserted in the widget tree.
///
/// This behavior can be disabled by passing `lazy: false` to [Provider].
///
/// ## Testing
///
/// When testing widgets that consumes providers, it is necessary to
/// add the proper providers in the widget tree above the tested widget.
///
/// A typical test may look like this:
///
/// ```dart
/// final foo = MockFoo();
///
/// await tester.pumpWidget(
/// Provider<Foo>.value(
/// value: foo,
/// child: TestedWidget(),
/// ),
/// );
/// ```
///
/// Note this example purposefully specified the object type, instead of having
/// it infered.
/// Since we used a mocked class (typically using `mockito`), then we have to
/// downcast the mock to the type of the mocked class.
/// Otherwise, the type inference will resolve to `Provider<MockFoo>` instead of
/// `Provider<Foo>`, which will cause `Provider.of<Foo>` to fail.
class Provider<T> extends InheritedProvider<T> {
/// Creates a value, store it, and expose it to its descendants.
///
/// The value can be optionally disposed using [dispose] callback.
/// This callback which will be called when [Provider] is unmounted from the
/// widget tree.
Provider({
Key key,
@required Create<T> create,
Dispose<T> dispose,
bool lazy,
Widget child,
}) : assert(create != null),
super(
key: key,
lazy: lazy,
create: create,
dispose: dispose,
debugCheckInvalidValueType:
kReleaseMode ? null : (T value) => Provider.debugCheckInvalidValueType?.call<T>(value),
child: child,
);
/// Expose an existing value without disposing it.
///
/// {@template provider.updateshouldnotify}
/// `updateShouldNotify` can optionally be passed to avoid unnecessarily
/// rebuilding dependents when [Provider] is rebuilt but `value` did not change.
///
/// Defaults to `(previous, next) => previous != next`.
/// See [InheritedWidget.updateShouldNotify] for more information.
/// {@endtemplate}
Provider.value({
Key key,
@required T value,
UpdateShouldNotify<T> updateShouldNotify,
Widget child,
}) : assert(() {
Provider.debugCheckInvalidValueType?.call<T>(value);
return true;
}()),
super.value(
key: key,
value: value,
updateShouldNotify: updateShouldNotify,
child: child,
);
/// Obtains the nearest [Provider<T>] up its widget tree and returns its
/// value.
///
/// If [listen] is `true`, later value changes will trigger a new
/// [State.build] to widgets, and [State.didChangeDependencies] for
/// [StatefulWidget].
///
/// By default, `listen` is inferred based on wether the widget tree is
/// currently building or not:
///
/// - if widgets are building, `listen` is `true`
/// - if widgets aren't, `listen` is `false`.
///
/// As such, it is fine to call `Provider.of` inside event handlers without
/// specifying `listen: false`:
///
/// ```dart
/// RaisedButton(
/// onPressed: () {
/// Provider.of<Model>(context); // no need to pass listen:false
///
/// Provider.of<Model>(context, listen: false); // unnecessary flag
/// }
/// )
/// ```
///
/// On the other hand, `listen: false` is necessary to be able to call
/// `Provider.of` inside [State.initState] or the `create` method of providers
/// like so:
///
/// ```dart
/// Provider(
/// create: (context) {
/// return Model(Provider.of<Something>(context, listen: false)),
/// },
/// )
/// ```
static T of<T>(BuildContext context, {bool listen = true}) {
assert(
T != dynamic,
'''
Tried to call Provider.of<dynamic>. This is likely a mistake and is therefore
unsupported.
If you want to expose a variable that can be anything, consider changing
`dynamic` to `Object` instead.
''',
);
assert(
context.owner.debugBuilding || listen == false || _debugIsInInheritedProviderUpdate,
'''
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<$T>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
''',
);
InheritedContext<T> inheritedElement;
if (context.widget is _DefaultInheritedProviderScope<T>) {
// An InheritedProvider<T>'s update tries to obtain a parent provider of
// the same type.
context.visitAncestorElements((parent) {
inheritedElement = parent.getElementForInheritedWidgetOfExactType<_DefaultInheritedProviderScope<T>>()
as _DefaultInheritedProviderScopeElement<T>;
return false;
});
} else {
inheritedElement = context.getElementForInheritedWidgetOfExactType<_DefaultInheritedProviderScope<T>>()
as _DefaultInheritedProviderScopeElement<T>;
}
if (inheritedElement == null) {
throw ProviderNotFoundException(T, context.widget.runtimeType);
}
if (listen) {
context.dependOnInheritedElement(inheritedElement as InheritedElement);
}
return inheritedElement.value;
}
/// A sanity check to prevent misuse of [Provider] when a variant should be
/// used instead.
///
/// 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 mechanism, 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;
}());
};
}
/// The error that will be thrown if [Provider.of] fails to find a [Provider]
/// as an ancestor of the [BuildContext] used.
class ProviderNotFoundException implements Exception {
/// 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.
ProviderNotFoundException(
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
''';
}
}