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