async.dart 24.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async' show StreamSubscription;
6

7 8
import 'package:flutter/foundation.dart';

9
import 'framework.dart';
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

/// Base class for widgets that build themselves based on interaction with
/// a specified [Stream].
///
/// A [StreamBuilderBase] is stateful and maintains a summary of the interaction
/// so far. The type of the summary and how it is updated with each interaction
/// is defined by sub-classes.
///
/// Examples of summaries include:
///
/// * the running average of a stream of integers;
/// * the current direction and speed based on a stream of geolocation data;
/// * a graph displaying data points from a stream.
///
/// In general, the summary is the result of a fold computation over the data
/// items and errors received from the stream along with pseudo-events
/// representing termination or change of stream. The initial summary is
/// specified by sub-classes by overriding [initial]. The summary updates on
/// receipt of stream data and errors are specified by overriding [afterData] and
/// [afterError], respectively. If needed, the summary may be updated on stream
/// 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 38
///
/// See also:
///
39 40
///  * [StreamBuilder], which is specialized for the case where only the most
///    recent interaction is needed for widget building.
41 42
abstract class StreamBuilderBase<T, S> extends StatefulWidget {
  /// Creates a [StreamBuilderBase] connected to the specified [stream].
43
  const StreamBuilderBase({ super.key, 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 48
  /// [afterDisconnected], if the previous stream was not null, followed by
  /// [afterConnected], if the new stream is not null.
49
  final Stream<T>? stream;
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

  /// Returns the initial summary of stream interaction, typically representing
  /// the fact that no interaction has happened at all.
  ///
  /// Sub-classes must override this method to provide the initial value for
  /// the fold computation.
  S initial();

  /// Returns an updated version of the [current] summary reflecting that we
  /// are now connected to a stream.
  ///
  /// The default implementation returns [current] as is.
  S afterConnected(S current) => current;

  /// Returns an updated version of the [current] summary following a data event.
  ///
  /// Sub-classes must override this method to specify how the current summary
  /// is combined with the new data item in the fold computation.
  S afterData(S current, T data);

70 71
  /// Returns an updated version of the [current] summary following an error
  /// 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91

  /// Returns an updated version of the [current] summary following stream
  /// termination.
  ///
  /// The default implementation returns [current] as is.
  S afterDone(S current) => current;

  /// Returns an updated version of the [current] summary reflecting that we
  /// are no longer connected to a stream.
  ///
  /// The default implementation returns [current] as is.
  S afterDisconnected(S current) => current;

  /// Returns a Widget based on the [currentSummary].
  Widget build(BuildContext context, S currentSummary);

  @override
92
  State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
93 94 95 96
}

/// State for [StreamBuilderBase].
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
97 98
  StreamSubscription<T>? _subscription; // ignore: cancel_subscriptions
  late S _summary;
99 100 101 102

  @override
  void initState() {
    super.initState();
103
    _summary = widget.initial();
104 105 106 107
    _subscribe();
  }

  @override
108
  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
109
    super.didUpdateWidget(oldWidget);
110
    if (oldWidget.stream != widget.stream) {
111 112
      if (_subscription != null) {
        _unsubscribe();
113
        _summary = widget.afterDisconnected(_summary);
114 115 116 117 118 119
      }
      _subscribe();
    }
  }

  @override
120
  Widget build(BuildContext context) => widget.build(context, _summary);
121 122 123 124 125 126 127 128

  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  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 139
        });
      }, onDone: () {
        setState(() {
140
          _summary = widget.afterDone(_summary);
141 142
        });
      });
143
      _summary = widget.afterConnected(_summary);
144 145 146 147 148
    }
  }

  void _unsubscribe() {
    if (_subscription != null) {
149
      _subscription!.cancel();
150 151 152 153 154 155 156
      _subscription = null;
    }
  }
}

/// The state of connection to an asynchronous computation.
///
157 158 159 160 161 162 163 164
/// The usual flow of state is as follows:
///
/// 1. [none], maybe with some initial data.
/// 2. [waiting], indicating that the asynchronous operation has begun,
///    typically with the data being null.
/// 3. [active], with data being non-null, and possible changing over time.
/// 4. [done], with data being non-null.
///
165 166
/// See also:
///
167 168
///  * [AsyncSnapshot], which augments a connection state with information
///    received from the asynchronous computation.
169 170
enum ConnectionState {
  /// Not currently connected to any asynchronous computation.
171 172
  ///
  /// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
173 174 175 176 177 178
  none,

  /// Connected to an asynchronous computation and awaiting interaction.
  waiting,

  /// Connected to an active asynchronous computation.
179 180 181
  ///
  /// For example, a [Stream] that has returned at least one value, but is not
  /// yet done.
182 183 184 185 186 187 188 189 190 191 192
  active,

  /// Connected to a terminated asynchronous computation.
  done,
}

/// Immutable representation of the most recent interaction with an asynchronous
/// computation.
///
/// See also:
///
193 194 195 196
///  * [StreamBuilder], which builds itself based on a snapshot from interacting
///    with a [Stream].
///  * [FutureBuilder], which builds itself based on a snapshot from interacting
///    with a [Future].
197
@immutable
198
class AsyncSnapshot<T> {
199
  /// Creates an [AsyncSnapshot] with the specified [connectionState],
200 201 202
  /// and optionally either [data] or [error] with an optional [stackTrace]
  /// (but not both data and error).
  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 216 217 218 219 220 221 222 223
  /// Creates an [AsyncSnapshot] in the specified [state] with the specified [error]
  /// and a [stackTrace].
  ///
  /// If no [stackTrace] is explicitly specified, [StackTrace.empty] will be used instead.
  const AsyncSnapshot.withError(
    ConnectionState state,
    Object error, [
    StackTrace stackTrace = StackTrace.empty,
  ]) : this._(state, null, error, stackTrace);
224

225
  /// Current state of connection to the asynchronous computation.
226 227
  final ConnectionState connectionState;

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 235 236
  /// If the asynchronous computation has never returned a value, this may be
  /// set to an initial data value specified by the relevant widget. See
  /// [FutureBuilder.initialData] and [StreamBuilder.initialData].
237
  final T? data;
238 239

  /// Returns latest data received, failing if there is no data.
240
  ///
241 242 243
  /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
  /// nor [hasError].
  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 254 255 256 257
  /// The latest error object received by the asynchronous computation.
  ///
  /// If this is non-null, [hasError] will be true.
  ///
  /// If [data] is not null, this will be null.
258
  final Object? error;
259

260 261 262 263 264 265 266 267 268
  /// The latest stack trace object received by the asynchronous computation.
  ///
  /// This will not be null iff [error] is not null. Consequently, [stackTrace]
  /// will be non-null when [hasError] is true.
  ///
  /// However, even when not null, [stackTrace] might be empty. The stack trace
  /// is empty when there is an error but no stack trace has been provided.
  final StackTrace? stackTrace;

269
  /// Returns a snapshot like this one, but in the specified [state].
270
  ///
271 272 273
  /// The [data], [error], and [stackTrace] fields persist unmodified, even if
  /// the new state is [ConnectionState.none].
  AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace);
274 275 276 277 278 279 280 281

  /// Returns whether this snapshot contains a non-null [data] value.
  ///
  /// This can be false even when the asynchronous computation has completed
  /// successfully, if the computation did not return a non-null value. For
  /// example, a [Future<void>] will complete with the null value even if it
  /// completes successfully.
  bool get hasData => data != null;
282

283 284 285 286
  /// Returns whether this snapshot contains a non-null [error] value.
  ///
  /// This is always true if the asynchronous computation's last result was
  /// failure.
287 288 289
  bool get hasError => error != null;

  @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 298 299
    return other is AsyncSnapshot<T>
        && other.connectionState == connectionState
        && other.data == data
300 301
        && other.error == error
        && other.stackTrace == stackTrace;
302 303 304
  }

  @override
305
  int get hashCode => Object.hash(connectionState, data, error);
306 307 308 309 310 311 312
}

/// Signature for strategies that build widgets based on asynchronous
/// interaction.
///
/// See also:
///
313 314 315 316
///  * [StreamBuilder], which delegates to an [AsyncWidgetBuilder] to build
///    itself based on a snapshot from interacting with a [Stream].
///  * [FutureBuilder], which delegates to an [AsyncWidgetBuilder] to build
///    itself based on a snapshot from interacting with a [Future].
317
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
318 319 320 321

/// Widget that builds itself based on the latest snapshot of interaction with
/// a [Stream].
///
322 323
/// {@youtube 560 315 https://www.youtube.com/watch?v=MkKEWHfy99Y}
///
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 327 328 329 330
/// is called at the discretion of the Flutter pipeline, and will thus receive a
/// timing-dependent sub-sequence of the snapshots that represent the
/// interaction with the stream.
///
/// 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 333 334
/// of the following snapshots that includes the last one (the one with
/// ConnectionState.done):
///
335 336 337
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
338
/// * ...
339 340
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
341
///
342 343 344
/// The actual sequence of invocations of the [builder] depends on the relative
/// timing of events produced by the stream and the build rate of the Flutter
/// pipeline.
345 346
///
/// Changing the [StreamBuilder] configuration to another stream during event
347
/// generation introduces snapshot pairs of the form:
348
///
349 350
/// * `AsyncSnapshot<int>.withData(ConnectionState.none, 5)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, 5)`
351
///
352 353
/// The latter will be produced only when the new stream is non-null, and the
/// 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 360 361
///
/// The data and error fields of snapshots produced are only changed when the
/// state is `ConnectionState.active`.
///
362
/// The initial snapshot data can be controlled by specifying [initialData].
363 364
/// This should be used to ensure that the first frame has the expected value,
/// as the builder will always be called before the stream listener has a chance
365
/// to be processed.
366
///
367
/// {@tool dartpad}
368 369 370 371 372
/// This sample shows a [StreamBuilder] that listens to a Stream that emits bids
/// for an auction. Every time the StreamBuilder receives a bid from the Stream,
/// it will display the price of the bid below an icon. If the Stream emits an
/// error, the error is displayed below an error icon. When the Stream finishes
/// 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 378 379 380 381 382
///
/// See also:
///
///  * [ValueListenableBuilder], which wraps a [ValueListenable] instead of a
///    [Stream].
///  * [StreamBuilderBase], which supports widget building based on a computation
///    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 387
  /// strategy is given by [builder].
  ///
388
  /// The [initialData] is used to create the initial snapshot.
389 390
  ///
  /// The [builder] must not be null.
391
  const StreamBuilder({
392
    super.key,
393
    this.initialData,
394
    super.stream,
395
    required this.builder,
396
  });
397

398
  /// The build strategy currently used by this builder.
399 400 401
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.
402 403
  final AsyncWidgetBuilder<T> builder;

404 405
  /// The data that will be used to create the initial snapshot.
  ///
406
  /// Providing this value (presumably obtained synchronously somehow when the
407
  /// [Stream] was created) ensures that the first frame will show useful data.
408 409 410 411
  /// Otherwise, the first frame will be built with the value null, regardless
  /// of whether a value is available on the stream: since streams are
  /// asynchronous, no events from the stream can be obtained before the initial
  /// build.
412
  final T? initialData;
413

414
  @override
415 416
  AsyncSnapshot<T> initial() => initialData == null
      ? AsyncSnapshot<T>.nothing()
417
      : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
418 419 420 421 422 423

  @override
  AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);

  @override
  AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
424
    return AsyncSnapshot<T>.withData(ConnectionState.active, data);
425 426 427
  }

  @override
428 429
  AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
    return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
430 431 432 433 434 435 436 437 438 439 440 441
  }

  @override
  AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);

  @override
  AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);

  @override
  Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
}

442
/// A widget that builds itself based on the latest snapshot of interaction with
443 444
/// a [Future].
///
445 446
/// {@youtube 560 315 https://www.youtube.com/watch?v=zEdw_1B7JHY}
///
447 448
/// ## Managing the future
///
449
/// The [future] must have been obtained earlier, e.g. during [State.initState],
450
/// [State.didUpdateWidget], or [State.didChangeDependencies]. It must not be
451 452 453 454 455 456 457 458 459 460
/// created during the [State.build] or [StatelessWidget.build] method call when
/// constructing the [FutureBuilder]. If the [future] is created at the same
/// time as the [FutureBuilder], then every time the [FutureBuilder]'s parent is
/// rebuilt, the asynchronous task will be restarted.
///
/// A general guideline is to assume that every `build` method could get called
/// every frame, and to treat omitted calls as an optimization.
///
/// ## Timing
///
461 462
/// Widget rebuilding is scheduled by the completion of the future, using
/// [State.setState], but is otherwise decoupled from the timing of the future.
463
/// The [builder] callback is called at the discretion of the Flutter pipeline, and
464 465 466
/// will thus receive a timing-dependent sub-sequence of the snapshots that
/// represent the interaction with the future.
///
467 468 469 470 471 472 473
/// A side-effect of this is that providing a new but already-completed future
/// to a [FutureBuilder] will result in a single frame in the
/// [ConnectionState.waiting] state. This is because there is no way to
/// synchronously determine that a [Future] has already completed.
///
/// ## Builder contract
///
474 475 476
/// For a future that completes successfully with data, assuming [initialData]
/// is null, the [builder] will be called with either both or only the latter of
/// the following snapshots:
477
///
478 479
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
480
///
481 482
/// If that same future instead completed with an error, the [builder] would be
/// called with either both or only the latter of:
483
///
484 485
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.withError(ConnectionState.done, 'some error', someStackTrace)`
486 487 488 489 490
///
/// The initial snapshot data can be controlled by specifying [initialData]. You
/// would use this facility to ensure that if the [builder] is invoked before
/// the future completes, the snapshot carries data of your choice rather than
/// the default null value.
491
///
492 493 494 495 496 497
/// The data and error fields of the snapshot change only as the connection
/// state field transitions from `waiting` to `done`, and they will be retained
/// when changing the [FutureBuilder] configuration to another future. If the
/// old future has already completed successfully with data as above, changing
/// configuration to a new future results in snapshot pairs of the form:
///
498 499
/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
500 501
///
/// In general, the latter will be produced only when the new future is
502
/// non-null, and the former only when the old future is non-null.
503 504 505 506
///
/// A [FutureBuilder] behaves identically to a [StreamBuilder] configured with
/// `future?.asStream()`, except that snapshots with `ConnectionState.active`
/// may appear for the latter, depending on how the stream is implemented.
507
///
508
/// {@tool dartpad}
509 510 511 512 513
/// This sample shows a [FutureBuilder] that displays a loading spinner while it
/// loads data. It displays a success icon and text if the [Future] completes
/// with a result, or an error icon and text if the [Future] completes with an
/// error. Assume the `_calculation` field is set by pressing a button elsewhere
/// in the UI.
514
///
515
/// ** See code in examples/api/lib/widgets/async/future_builder.0.dart **
516
/// {@end-tool}
517
class FutureBuilder<T> extends StatefulWidget {
518 519 520
  /// Creates a widget that builds itself based on the latest snapshot of
  /// interaction with a [Future].
  ///
521
  /// The [builder] must not be null.
522
  const FutureBuilder({
523
    super.key,
524
    this.future,
525
    this.initialData,
526
    required this.builder,
527
  });
528 529

  /// The asynchronous computation to which this builder is currently connected,
530
  /// possibly null.
531 532
  ///
  /// If no future has yet completed, including in the case where [future] is
533
  /// null, the data provided to the [builder] will be set to [initialData].
534
  final Future<T>? future;
535

536 537 538 539 540 541
  /// The build strategy currently used by this builder.
  ///
  /// The builder is provided with an [AsyncSnapshot] object whose
  /// [AsyncSnapshot.connectionState] property will be one of the following
  /// values:
  ///
542 543 544
  ///  * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will
  ///    be set to [initialData], unless a future has previously completed, in
  ///    which case the previous result persists.
545
  ///
546 547 548 549
  ///  * [ConnectionState.waiting]: [future] is not null, but has not yet
  ///    completed. The [AsyncSnapshot.data] will be set to [initialData],
  ///    unless a future has previously completed, in which case the previous
  ///    result persists.
550 551 552 553 554 555
  ///
  ///  * [ConnectionState.done]: [future] is not null, and has completed. If the
  ///    future completed successfully, the [AsyncSnapshot.data] will be set to
  ///    the value to which the future completed. If it completed with an error,
  ///    [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
  ///    set to the error object.
556 557 558
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.
559 560
  final AsyncWidgetBuilder<T> builder;

561 562 563
  /// The data that will be used to create the snapshots provided until a
  /// non-null [future] has completed.
  ///
564 565 566
  /// If the future completes with an error, the data in the [AsyncSnapshot]
  /// provided to the [builder] will become null, regardless of [initialData].
  /// (The error itself will be available in [AsyncSnapshot.error], and
567
  /// [AsyncSnapshot.hasError] will be true.)
568
  final T? initialData;
569

570 571 572 573 574 575 576 577
  /// Whether the latest error received by the asynchronous computation should
  /// be rethrown or swallowed. This property is useful for debugging purposes.
  ///
  /// When set to true, will rethrow the latest error only in debug mode.
  ///
  /// Defaults to `false`, resulting in swallowing of errors.
  static bool debugRethrowError = false;

578
  @override
579
  State<FutureBuilder<T>> createState() => _FutureBuilderState<T>();
580 581 582
}

/// State for [FutureBuilder].
583
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
584 585 586
  /// An object that identifies the currently active callbacks. Used to avoid
  /// calling setState from stale callbacks, e.g. after disposal of this state,
  /// or after widget reconfiguration to a new Future.
587 588
  Object? _activeCallbackIdentity;
  late AsyncSnapshot<T> _snapshot;
589 590 591 592

  @override
  void initState() {
    super.initState();
593 594
    _snapshot = widget.initialData == null
        ? AsyncSnapshot<T>.nothing()
595
        : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
596 597 598 599
    _subscribe();
  }

  @override
600
  void didUpdateWidget(FutureBuilder<T> oldWidget) {
601
    super.didUpdateWidget(oldWidget);
602
    if (oldWidget.future != widget.future) {
603 604 605 606 607 608 609 610 611
      if (_activeCallbackIdentity != null) {
        _unsubscribe();
        _snapshot = _snapshot.inState(ConnectionState.none);
      }
      _subscribe();
    }
  }

  @override
612
  Widget build(BuildContext context) => widget.builder(context, _snapshot);
613 614 615 616 617 618 619 620

  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  void _subscribe() {
621
    if (widget.future != null) {
622
      final Object callbackIdentity = Object();
623
      _activeCallbackIdentity = callbackIdentity;
624
      widget.future!.then<void>((T data) {
625 626
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
627
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
628 629
          });
        }
630
      }, onError: (Object error, StackTrace stackTrace) {
631 632
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
633
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
634 635
          });
        }
636
        assert(() {
637
          if (FutureBuilder.debugRethrowError) {
638 639 640 641
            Future<Object>.error(error, stackTrace);
          }
          return true;
        }());
642
      });
643 644 645 646 647
      // An implementation like `SynchronousFuture` may have already called the
      // .then closure. Do not overwrite it in that case.
      if (_snapshot.connectionState != ConnectionState.done) {
        _snapshot = _snapshot.inState(ConnectionState.waiting);
      }
648 649 650 651 652 653 654
    }
  }

  void _unsubscribe() {
    _activeCallbackIdentity = null;
  }
}