1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:async' show StreamSubscription; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | |
9 | import 'framework.dart'; |
10 | |
11 | /// Base class for widgets that build themselves based on interaction with |
12 | /// a specified [Stream]. |
13 | /// |
14 | /// A [StreamBuilderBase] is stateful and maintains a summary of the interaction |
15 | /// so far. The type of the summary and how it is updated with each interaction |
16 | /// is defined by sub-classes. |
17 | /// |
18 | /// Examples of summaries include: |
19 | /// |
20 | /// * the running average of a stream of integers; |
21 | /// * the current direction and speed based on a stream of geolocation data; |
22 | /// * a graph displaying data points from a stream. |
23 | /// |
24 | /// In general, the summary is the result of a fold computation over the data |
25 | /// items and errors received from the stream along with pseudo-events |
26 | /// representing termination or change of stream. The initial summary is |
27 | /// specified by sub-classes by overriding [initial]. The summary updates on |
28 | /// receipt of stream data and errors are specified by overriding [afterData] and |
29 | /// [afterError], respectively. If needed, the summary may be updated on stream |
30 | /// termination by overriding [afterDone]. Finally, the summary may be updated |
31 | /// on change of stream by overriding [afterDisconnected] and [afterConnected]. |
32 | /// |
33 | /// `T` is the type of stream events. |
34 | /// |
35 | /// `S` is the type of interaction summary. |
36 | /// |
37 | /// See also: |
38 | /// |
39 | /// * [StreamBuilder], which is specialized for the case where only the most |
40 | /// recent interaction is needed for widget building. |
41 | abstract class StreamBuilderBase<T, S> extends StatefulWidget { |
42 | /// Creates a [StreamBuilderBase] connected to the specified [stream]. |
43 | const StreamBuilderBase({ super.key, required this.stream }); |
44 | |
45 | /// The asynchronous computation to which this builder is currently connected, |
46 | /// possibly null. When changed, the current summary is updated using |
47 | /// [afterDisconnected], if the previous stream was not null, followed by |
48 | /// [afterConnected], if the new stream is not null. |
49 | final Stream<T>? stream; |
50 | |
51 | /// Returns the initial summary of stream interaction, typically representing |
52 | /// the fact that no interaction has happened at all. |
53 | /// |
54 | /// Sub-classes must override this method to provide the initial value for |
55 | /// the fold computation. |
56 | S initial(); |
57 | |
58 | /// Returns an updated version of the [current] summary reflecting that we |
59 | /// are now connected to a stream. |
60 | /// |
61 | /// The default implementation returns [current] as is. |
62 | S afterConnected(S current) => current; |
63 | |
64 | /// Returns an updated version of the [current] summary following a data event. |
65 | /// |
66 | /// Sub-classes must override this method to specify how the current summary |
67 | /// is combined with the new data item in the fold computation. |
68 | S afterData(S current, T data); |
69 | |
70 | /// Returns an updated version of the [current] summary following an error |
71 | /// with a stack trace. |
72 | /// |
73 | /// The default implementation returns [current] as is. |
74 | S afterError(S current, Object error, StackTrace stackTrace) => current; |
75 | |
76 | /// Returns an updated version of the [current] summary following stream |
77 | /// termination. |
78 | /// |
79 | /// The default implementation returns [current] as is. |
80 | S afterDone(S current) => current; |
81 | |
82 | /// Returns an updated version of the [current] summary reflecting that we |
83 | /// are no longer connected to a stream. |
84 | /// |
85 | /// The default implementation returns [current] as is. |
86 | S afterDisconnected(S current) => current; |
87 | |
88 | /// Returns a Widget based on the [currentSummary]. |
89 | Widget build(BuildContext context, S currentSummary); |
90 | |
91 | @override |
92 | State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>(); |
93 | } |
94 | |
95 | /// State for [StreamBuilderBase]. |
96 | class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> { |
97 | StreamSubscription<T>? _subscription; |
98 | late S _summary; |
99 | |
100 | @override |
101 | void initState() { |
102 | super.initState(); |
103 | _summary = widget.initial(); |
104 | _subscribe(); |
105 | } |
106 | |
107 | @override |
108 | void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) { |
109 | super.didUpdateWidget(oldWidget); |
110 | if (oldWidget.stream != widget.stream) { |
111 | if (_subscription != null) { |
112 | _unsubscribe(); |
113 | _summary = widget.afterDisconnected(_summary); |
114 | } |
115 | _subscribe(); |
116 | } |
117 | } |
118 | |
119 | @override |
120 | Widget build(BuildContext context) => widget.build(context, _summary); |
121 | |
122 | @override |
123 | void dispose() { |
124 | _unsubscribe(); |
125 | super.dispose(); |
126 | } |
127 | |
128 | void _subscribe() { |
129 | if (widget.stream != null) { |
130 | _subscription = widget.stream!.listen((T data) { |
131 | setState(() { |
132 | _summary = widget.afterData(_summary, data); |
133 | }); |
134 | }, onError: (Object error, StackTrace stackTrace) { |
135 | setState(() { |
136 | _summary = widget.afterError(_summary, error, stackTrace); |
137 | }); |
138 | }, onDone: () { |
139 | setState(() { |
140 | _summary = widget.afterDone(_summary); |
141 | }); |
142 | }); |
143 | _summary = widget.afterConnected(_summary); |
144 | } |
145 | } |
146 | |
147 | void _unsubscribe() { |
148 | if (_subscription != null) { |
149 | _subscription!.cancel(); |
150 | _subscription = null; |
151 | } |
152 | } |
153 | } |
154 | |
155 | /// The state of connection to an asynchronous computation. |
156 | /// |
157 | /// The usual flow of state is as follows: |
158 | /// |
159 | /// 1. [none], maybe with some initial data. |
160 | /// 2. [waiting], indicating that the asynchronous operation has begun, |
161 | /// typically with the data being null. |
162 | /// 3. [active], with data being non-null, and possible changing over time. |
163 | /// 4. [done], with data being non-null. |
164 | /// |
165 | /// See also: |
166 | /// |
167 | /// * [AsyncSnapshot], which augments a connection state with information |
168 | /// received from the asynchronous computation. |
169 | enum ConnectionState { |
170 | /// Not currently connected to any asynchronous computation. |
171 | /// |
172 | /// For example, a [FutureBuilder] whose [FutureBuilder.future] is null. |
173 | none, |
174 | |
175 | /// Connected to an asynchronous computation and awaiting interaction. |
176 | waiting, |
177 | |
178 | /// Connected to an active asynchronous computation. |
179 | /// |
180 | /// For example, a [Stream] that has returned at least one value, but is not |
181 | /// yet done. |
182 | active, |
183 | |
184 | /// Connected to a terminated asynchronous computation. |
185 | done, |
186 | } |
187 | |
188 | /// Immutable representation of the most recent interaction with an asynchronous |
189 | /// computation. |
190 | /// |
191 | /// See also: |
192 | /// |
193 | /// * [StreamBuilder], which builds itself based on a snapshot from interacting |
194 | /// with a [Stream]. |
195 | /// * [FutureBuilder], which builds itself based on a snapshot from interacting |
196 | /// with a [Future]. |
197 | @immutable |
198 | class AsyncSnapshot<T> { |
199 | /// Creates an [AsyncSnapshot] with the specified [connectionState], |
200 | /// and optionally either [data] or [error] with an optional [stackTrace] |
201 | /// (but not both data and error). |
202 | const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace) |
203 | : assert(data == null || error == null), |
204 | assert(stackTrace == null || error != null); |
205 | |
206 | /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error. |
207 | const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null); |
208 | |
209 | /// Creates an [AsyncSnapshot] in [ConnectionState.waiting] with null data and error. |
210 | const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null); |
211 | |
212 | /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data]. |
213 | const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null); |
214 | |
215 | /// Creates an [AsyncSnapshot] in the specified [state] with the specified [error] |
216 | /// and a [stackTrace]. |
217 | /// |
218 | /// If no [stackTrace] is explicitly specified, [StackTrace.empty] will be used instead. |
219 | const AsyncSnapshot.withError( |
220 | ConnectionState state, |
221 | Object error, [ |
222 | StackTrace stackTrace = StackTrace.empty, |
223 | ]) : this._(state, null, error, stackTrace); |
224 | |
225 | /// Current state of connection to the asynchronous computation. |
226 | final ConnectionState connectionState; |
227 | |
228 | /// The latest data received by the asynchronous computation. |
229 | /// |
230 | /// If this is non-null, [hasData] will be true. |
231 | /// |
232 | /// If [error] is not null, this will be null. See [hasError]. |
233 | /// |
234 | /// If the asynchronous computation has never returned a value, this may be |
235 | /// set to an initial data value specified by the relevant widget. See |
236 | /// [FutureBuilder.initialData] and [StreamBuilder.initialData]. |
237 | final T? data; |
238 | |
239 | /// Returns latest data received, failing if there is no data. |
240 | /// |
241 | /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData] |
242 | /// nor [hasError]. |
243 | T get requireData { |
244 | if (hasData) { |
245 | return data!; |
246 | } |
247 | if (hasError) { |
248 | Error.throwWithStackTrace(error!, stackTrace!); |
249 | } |
250 | throw StateError('Snapshot has neither data nor error' ); |
251 | } |
252 | |
253 | /// The latest error object received by the asynchronous computation. |
254 | /// |
255 | /// If this is non-null, [hasError] will be true. |
256 | /// |
257 | /// If [data] is not null, this will be null. |
258 | final Object? error; |
259 | |
260 | /// The latest stack trace object received by the asynchronous computation. |
261 | /// |
262 | /// This will not be null iff [error] is not null. Consequently, [stackTrace] |
263 | /// will be non-null when [hasError] is true. |
264 | /// |
265 | /// However, even when not null, [stackTrace] might be empty. The stack trace |
266 | /// is empty when there is an error but no stack trace has been provided. |
267 | final StackTrace? stackTrace; |
268 | |
269 | /// Returns a snapshot like this one, but in the specified [state]. |
270 | /// |
271 | /// The [data], [error], and [stackTrace] fields persist unmodified, even if |
272 | /// the new state is [ConnectionState.none]. |
273 | AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace); |
274 | |
275 | /// Returns whether this snapshot contains a non-null [data] value. |
276 | /// |
277 | /// This can be false even when the asynchronous computation has completed |
278 | /// successfully, if the computation did not return a non-null value. For |
279 | /// example, a [Future<void>] will complete with the null value even if it |
280 | /// completes successfully. |
281 | bool get hasData => data != null; |
282 | |
283 | /// Returns whether this snapshot contains a non-null [error] value. |
284 | /// |
285 | /// This is always true if the asynchronous computation's last result was |
286 | /// failure. |
287 | bool get hasError => error != null; |
288 | |
289 | @override |
290 | String toString() => ' ${objectRuntimeType(this, 'AsyncSnapshot' )}( $connectionState, $data, $error, $stackTrace)' ; |
291 | |
292 | @override |
293 | bool operator ==(Object other) { |
294 | if (identical(this, other)) { |
295 | return true; |
296 | } |
297 | return other is AsyncSnapshot<T> |
298 | && other.connectionState == connectionState |
299 | && other.data == data |
300 | && other.error == error |
301 | && other.stackTrace == stackTrace; |
302 | } |
303 | |
304 | @override |
305 | int get hashCode => Object.hash(connectionState, data, error); |
306 | } |
307 | |
308 | /// Signature for strategies that build widgets based on asynchronous |
309 | /// interaction. |
310 | /// |
311 | /// See also: |
312 | /// |
313 | /// * [StreamBuilder], which delegates to an [AsyncWidgetBuilder] to build |
314 | /// itself based on a snapshot from interacting with a [Stream]. |
315 | /// * [FutureBuilder], which delegates to an [AsyncWidgetBuilder] to build |
316 | /// itself based on a snapshot from interacting with a [Future]. |
317 | typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot); |
318 | |
319 | /// Widget that builds itself based on the latest snapshot of interaction with |
320 | /// a [Stream]. |
321 | /// |
322 | /// {@youtube 560 315 https://www.youtube.com/watch?v=MkKEWHfy99Y} |
323 | /// |
324 | /// Widget rebuilding is scheduled by each interaction, using [State.setState], |
325 | /// but is otherwise decoupled from the timing of the stream. The [builder] |
326 | /// is called at the discretion of the Flutter pipeline, and will thus receive a |
327 | /// timing-dependent sub-sequence of the snapshots that represent the |
328 | /// interaction with the stream. |
329 | /// |
330 | /// As an example, when interacting with a stream producing the integers |
331 | /// 0 through 9, the [builder] may be called with any ordered sub-sequence |
332 | /// of the following snapshots that includes the last one (the one with |
333 | /// ConnectionState.done): |
334 | /// |
335 | /// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)` |
336 | /// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)` |
337 | /// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)` |
338 | /// * ... |
339 | /// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)` |
340 | /// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)` |
341 | /// |
342 | /// The actual sequence of invocations of the [builder] depends on the relative |
343 | /// timing of events produced by the stream and the build rate of the Flutter |
344 | /// pipeline. |
345 | /// |
346 | /// Changing the [StreamBuilder] configuration to another stream during event |
347 | /// generation introduces snapshot pairs of the form: |
348 | /// |
349 | /// * `AsyncSnapshot<int>.withData(ConnectionState.none, 5)` |
350 | /// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, 5)` |
351 | /// |
352 | /// The latter will be produced only when the new stream is non-null, and the |
353 | /// former only when the old stream is non-null. |
354 | /// |
355 | /// The stream may produce errors, resulting in snapshots of the form: |
356 | /// |
357 | /// * `AsyncSnapshot<int>.withError(ConnectionState.active, 'some error', someStackTrace)` |
358 | /// |
359 | /// The data and error fields of snapshots produced are only changed when the |
360 | /// state is `ConnectionState.active`. |
361 | /// |
362 | /// The initial snapshot data can be controlled by specifying [initialData]. |
363 | /// This should be used to ensure that the first frame has the expected value, |
364 | /// as the builder will always be called before the stream listener has a chance |
365 | /// to be processed. |
366 | /// |
367 | /// {@tool dartpad} |
368 | /// This sample shows a [StreamBuilder] that listens to a Stream that emits bids |
369 | /// for an auction. Every time the StreamBuilder receives a bid from the Stream, |
370 | /// it will display the price of the bid below an icon. If the Stream emits an |
371 | /// error, the error is displayed below an error icon. When the Stream finishes |
372 | /// emitting bids, the final price is displayed. |
373 | /// |
374 | /// ** See code in examples/api/lib/widgets/async/stream_builder.0.dart ** |
375 | /// {@end-tool} |
376 | /// |
377 | /// See also: |
378 | /// |
379 | /// * [ValueListenableBuilder], which wraps a [ValueListenable] instead of a |
380 | /// [Stream]. |
381 | /// * [StreamBuilderBase], which supports widget building based on a computation |
382 | /// that spans all interactions made with the stream. |
383 | class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> { |
384 | /// Creates a new [StreamBuilder] that builds itself based on the latest |
385 | /// snapshot of interaction with the specified [stream] and whose build |
386 | /// strategy is given by [builder]. |
387 | /// |
388 | /// The [initialData] is used to create the initial snapshot. |
389 | const StreamBuilder({ |
390 | super.key, |
391 | this.initialData, |
392 | required super.stream, |
393 | required this.builder, |
394 | }); |
395 | |
396 | /// The build strategy currently used by this builder. |
397 | /// |
398 | /// This builder must only return a widget and should not have any side |
399 | /// effects as it may be called multiple times. |
400 | final AsyncWidgetBuilder<T> builder; |
401 | |
402 | /// The data that will be used to create the initial snapshot. |
403 | /// |
404 | /// Providing this value (presumably obtained synchronously somehow when the |
405 | /// [Stream] was created) ensures that the first frame will show useful data. |
406 | /// Otherwise, the first frame will be built with the value null, regardless |
407 | /// of whether a value is available on the stream: since streams are |
408 | /// asynchronous, no events from the stream can be obtained before the initial |
409 | /// build. |
410 | final T? initialData; |
411 | |
412 | @override |
413 | AsyncSnapshot<T> initial() => initialData == null |
414 | ? AsyncSnapshot<T>.nothing() |
415 | : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T); |
416 | |
417 | @override |
418 | AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting); |
419 | |
420 | @override |
421 | AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) { |
422 | return AsyncSnapshot<T>.withData(ConnectionState.active, data); |
423 | } |
424 | |
425 | @override |
426 | AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) { |
427 | return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace); |
428 | } |
429 | |
430 | @override |
431 | AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done); |
432 | |
433 | @override |
434 | AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none); |
435 | |
436 | @override |
437 | Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary); |
438 | } |
439 | |
440 | /// A widget that builds itself based on the latest snapshot of interaction with |
441 | /// a [Future]. |
442 | /// |
443 | /// {@youtube 560 315 https://www.youtube.com/watch?v=zEdw_1B7JHY} |
444 | /// |
445 | /// ## Managing the future |
446 | /// |
447 | /// The [future] must have been obtained earlier, e.g. during [State.initState], |
448 | /// [State.didUpdateWidget], or [State.didChangeDependencies]. It must not be |
449 | /// created during the [State.build] or [StatelessWidget.build] method call when |
450 | /// constructing the [FutureBuilder]. If the [future] is created at the same |
451 | /// time as the [FutureBuilder], then every time the [FutureBuilder]'s parent is |
452 | /// rebuilt, the asynchronous task will be restarted. |
453 | /// |
454 | /// A general guideline is to assume that every `build` method could get called |
455 | /// every frame, and to treat omitted calls as an optimization. |
456 | /// |
457 | /// ## Timing |
458 | /// |
459 | /// Widget rebuilding is scheduled by the completion of the future, using |
460 | /// [State.setState], but is otherwise decoupled from the timing of the future. |
461 | /// The [builder] callback is called at the discretion of the Flutter pipeline, and |
462 | /// will thus receive a timing-dependent sub-sequence of the snapshots that |
463 | /// represent the interaction with the future. |
464 | /// |
465 | /// A side-effect of this is that providing a new but already-completed future |
466 | /// to a [FutureBuilder] will result in a single frame in the |
467 | /// [ConnectionState.waiting] state. This is because there is no way to |
468 | /// synchronously determine that a [Future] has already completed. |
469 | /// |
470 | /// ## Builder contract |
471 | /// |
472 | /// For a future that completes successfully with data, assuming [initialData] |
473 | /// is null, the [builder] will be called with either both or only the latter of |
474 | /// the following snapshots: |
475 | /// |
476 | /// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)` |
477 | /// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')` |
478 | /// |
479 | /// If that same future instead completed with an error, the [builder] would be |
480 | /// called with either both or only the latter of: |
481 | /// |
482 | /// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)` |
483 | /// * `AsyncSnapshot<String>.withError(ConnectionState.done, 'some error', someStackTrace)` |
484 | /// |
485 | /// The initial snapshot data can be controlled by specifying [initialData]. You |
486 | /// would use this facility to ensure that if the [builder] is invoked before |
487 | /// the future completes, the snapshot carries data of your choice rather than |
488 | /// the default null value. |
489 | /// |
490 | /// The data and error fields of the snapshot change only as the connection |
491 | /// state field transitions from `waiting` to `done`, and they will be retained |
492 | /// when changing the [FutureBuilder] configuration to another future. If the |
493 | /// old future has already completed successfully with data as above, changing |
494 | /// configuration to a new future results in snapshot pairs of the form: |
495 | /// |
496 | /// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')` |
497 | /// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')` |
498 | /// |
499 | /// In general, the latter will be produced only when the new future is |
500 | /// non-null, and the former only when the old future is non-null. |
501 | /// |
502 | /// A [FutureBuilder] behaves identically to a [StreamBuilder] configured with |
503 | /// `future?.asStream()`, except that snapshots with `ConnectionState.active` |
504 | /// may appear for the latter, depending on how the stream is implemented. |
505 | /// |
506 | /// {@tool dartpad} |
507 | /// This sample shows a [FutureBuilder] that displays a loading spinner while it |
508 | /// loads data. It displays a success icon and text if the [Future] completes |
509 | /// with a result, or an error icon and text if the [Future] completes with an |
510 | /// error. Assume the `_calculation` field is set by pressing a button elsewhere |
511 | /// in the UI. |
512 | /// |
513 | /// ** See code in examples/api/lib/widgets/async/future_builder.0.dart ** |
514 | /// {@end-tool} |
515 | class FutureBuilder<T> extends StatefulWidget { |
516 | /// Creates a widget that builds itself based on the latest snapshot of |
517 | /// interaction with a [Future]. |
518 | const FutureBuilder({ |
519 | super.key, |
520 | required this.future, |
521 | this.initialData, |
522 | required this.builder, |
523 | }); |
524 | |
525 | /// The asynchronous computation to which this builder is currently connected, |
526 | /// possibly null. |
527 | /// |
528 | /// If no future has yet completed, including in the case where [future] is |
529 | /// null, the data provided to the [builder] will be set to [initialData]. |
530 | final Future<T>? future; |
531 | |
532 | /// The build strategy currently used by this builder. |
533 | /// |
534 | /// The builder is provided with an [AsyncSnapshot] object whose |
535 | /// [AsyncSnapshot.connectionState] property will be one of the following |
536 | /// values: |
537 | /// |
538 | /// * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will |
539 | /// be set to [initialData], unless a future has previously completed, in |
540 | /// which case the previous result persists. |
541 | /// |
542 | /// * [ConnectionState.waiting]: [future] is not null, but has not yet |
543 | /// completed. The [AsyncSnapshot.data] will be set to [initialData], |
544 | /// unless a future has previously completed, in which case the previous |
545 | /// result persists. |
546 | /// |
547 | /// * [ConnectionState.done]: [future] is not null, and has completed. If the |
548 | /// future completed successfully, the [AsyncSnapshot.data] will be set to |
549 | /// the value to which the future completed. If it completed with an error, |
550 | /// [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be |
551 | /// set to the error object. |
552 | /// |
553 | /// This builder must only return a widget and should not have any side |
554 | /// effects as it may be called multiple times. |
555 | final AsyncWidgetBuilder<T> builder; |
556 | |
557 | /// The data that will be used to create the snapshots provided until a |
558 | /// non-null [future] has completed. |
559 | /// |
560 | /// If the future completes with an error, the data in the [AsyncSnapshot] |
561 | /// provided to the [builder] will become null, regardless of [initialData]. |
562 | /// (The error itself will be available in [AsyncSnapshot.error], and |
563 | /// [AsyncSnapshot.hasError] will be true.) |
564 | final T? initialData; |
565 | |
566 | /// Whether the latest error received by the asynchronous computation should |
567 | /// be rethrown or swallowed. This property is useful for debugging purposes. |
568 | /// |
569 | /// When set to true, will rethrow the latest error only in debug mode. |
570 | /// |
571 | /// Defaults to `false`, resulting in swallowing of errors. |
572 | static bool debugRethrowError = false; |
573 | |
574 | @override |
575 | State<FutureBuilder<T>> createState() => _FutureBuilderState<T>(); |
576 | } |
577 | |
578 | /// State for [FutureBuilder]. |
579 | class _FutureBuilderState<T> extends State<FutureBuilder<T>> { |
580 | /// An object that identifies the currently active callbacks. Used to avoid |
581 | /// calling setState from stale callbacks, e.g. after disposal of this state, |
582 | /// or after widget reconfiguration to a new Future. |
583 | Object? _activeCallbackIdentity; |
584 | late AsyncSnapshot<T> _snapshot; |
585 | |
586 | @override |
587 | void initState() { |
588 | super.initState(); |
589 | _snapshot = widget.initialData == null |
590 | ? AsyncSnapshot<T>.nothing() |
591 | : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T); |
592 | _subscribe(); |
593 | } |
594 | |
595 | @override |
596 | void didUpdateWidget(FutureBuilder<T> oldWidget) { |
597 | super.didUpdateWidget(oldWidget); |
598 | if (oldWidget.future == widget.future) { |
599 | return; |
600 | } |
601 | if (_activeCallbackIdentity != null) { |
602 | _unsubscribe(); |
603 | _snapshot = _snapshot.inState(ConnectionState.none); |
604 | } |
605 | _subscribe(); |
606 | } |
607 | |
608 | @override |
609 | Widget build(BuildContext context) => widget.builder(context, _snapshot); |
610 | |
611 | @override |
612 | void dispose() { |
613 | _unsubscribe(); |
614 | super.dispose(); |
615 | } |
616 | |
617 | void _subscribe() { |
618 | if (widget.future == null) { |
619 | // There is no future to subscribe to, do nothing. |
620 | return; |
621 | } |
622 | final Object callbackIdentity = Object(); |
623 | _activeCallbackIdentity = callbackIdentity; |
624 | widget.future!.then<void>((T data) { |
625 | if (_activeCallbackIdentity == callbackIdentity) { |
626 | setState(() { |
627 | _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data); |
628 | }); |
629 | } |
630 | }, onError: (Object error, StackTrace stackTrace) { |
631 | if (_activeCallbackIdentity == callbackIdentity) { |
632 | setState(() { |
633 | _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace); |
634 | }); |
635 | } |
636 | assert(() { |
637 | if (FutureBuilder.debugRethrowError) { |
638 | Future<Object>.error(error, stackTrace); |
639 | } |
640 | return true; |
641 | }()); |
642 | }); |
643 | // An implementation like `SynchronousFuture` may have already called the |
644 | // .then closure. Do not overwrite it in that case. |
645 | if (_snapshot.connectionState != ConnectionState.done) { |
646 | _snapshot = _snapshot.inState(ConnectionState.waiting); |
647 | } |
648 | } |
649 | |
650 | void _unsubscribe() { |
651 | _activeCallbackIdentity = null; |
652 | } |
653 | } |
654 | |