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

/// Widgets that handle interaction with asynchronous computations.
///
/// Asynchronous computations are represented by [Future]s and [Stream]s.

import 'dart:async' show Future, Stream, StreamSubscription;

11 12
import 'package:flutter/foundation.dart';

13
import 'framework.dart';
14

15 16
// Examples can assume:
// dynamic _lot;
17
// Future<String> _calculation;
18

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
/// 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
39
/// on change of stream by overriding [afterDisconnected] and [afterConnected].
40
///
41
/// `T` is the type of stream events.
42
///
43
/// `S` is the type of interaction summary.
44 45 46
///
/// See also:
///
47 48
///  * [StreamBuilder], which is specialized for the case where only the most
///    recent interaction is needed for widget building.
49 50
abstract class StreamBuilderBase<T, S> extends StatefulWidget {
  /// Creates a [StreamBuilderBase] connected to the specified [stream].
51
  const StreamBuilderBase({ Key? key, this.stream }) : super(key: key);
52 53

  /// The asynchronous computation to which this builder is currently connected,
54
  /// possibly null. When changed, the current summary is updated using
55 56
  /// [afterDisconnected], if the previous stream was not null, followed by
  /// [afterConnected], if the new stream is not null.
57
  final Stream<T>? stream;
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

  /// 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);

78 79
  /// Returns an updated version of the [current] summary following an error
  /// with a stack trace.
80 81
  ///
  /// The default implementation returns [current] as is.
82
  S afterError(S current, Object error, StackTrace stackTrace) => current;
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

  /// 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
100
  State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
101 102 103 104
}

/// State for [StreamBuilderBase].
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
105 106
  StreamSubscription<T>? _subscription; // ignore: cancel_subscriptions
  late S _summary;
107 108 109 110

  @override
  void initState() {
    super.initState();
111
    _summary = widget.initial();
112 113 114 115
    _subscribe();
  }

  @override
116
  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
117
    super.didUpdateWidget(oldWidget);
118
    if (oldWidget.stream != widget.stream) {
119 120
      if (_subscription != null) {
        _unsubscribe();
121
        _summary = widget.afterDisconnected(_summary);
122 123 124 125 126 127
      }
      _subscribe();
    }
  }

  @override
128
  Widget build(BuildContext context) => widget.build(context, _summary);
129 130 131 132 133 134 135 136

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

  void _subscribe() {
137
    if (widget.stream != null) {
138
      _subscription = widget.stream!.listen((T data) {
139
        setState(() {
140
          _summary = widget.afterData(_summary, data);
141
        });
142
      }, onError: (Object error, StackTrace stackTrace) {
143
        setState(() {
144
          _summary = widget.afterError(_summary, error, stackTrace);
145 146 147
        });
      }, onDone: () {
        setState(() {
148
          _summary = widget.afterDone(_summary);
149 150
        });
      });
151
      _summary = widget.afterConnected(_summary);
152 153 154 155 156
    }
  }

  void _unsubscribe() {
    if (_subscription != null) {
157
      _subscription!.cancel();
158 159 160 161 162 163 164
      _subscription = null;
    }
  }
}

/// The state of connection to an asynchronous computation.
///
165 166 167 168 169 170 171 172
/// 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.
///
173 174
/// See also:
///
175 176
///  * [AsyncSnapshot], which augments a connection state with information
///    received from the asynchronous computation.
177 178
enum ConnectionState {
  /// Not currently connected to any asynchronous computation.
179 180
  ///
  /// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
181 182 183 184 185 186
  none,

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

  /// Connected to an active asynchronous computation.
187 188 189
  ///
  /// For example, a [Stream] that has returned at least one value, but is not
  /// yet done.
190 191 192 193 194 195 196 197 198 199 200
  active,

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

/// Immutable representation of the most recent interaction with an asynchronous
/// computation.
///
/// See also:
///
201 202 203 204
///  * [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].
205
@immutable
206
class AsyncSnapshot<T> {
207
  /// Creates an [AsyncSnapshot] with the specified [connectionState],
208 209 210
  /// 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)
211
    : assert(connectionState != null),
212 213
      assert(!(data != null && error != null)),
      assert(stackTrace == null || error != null);
214

215
  /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.
216
  const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null);
217

218
  /// Creates an [AsyncSnapshot] in [ConnectionState.waiting] with null data and error.
219
  const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null);
220

221
  /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data].
222
  const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null);
223

224 225 226 227 228 229 230 231 232
  /// 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);
233

234
  /// Current state of connection to the asynchronous computation.
235 236
  final ConnectionState connectionState;

237
  /// The latest data received by the asynchronous computation.
238
  ///
239
  /// If this is non-null, [hasData] will be true.
240
  ///
241
  /// If [error] is not null, this will be null. See [hasError].
242
  ///
243 244 245
  /// 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].
246
  final T? data;
247 248

  /// Returns latest data received, failing if there is no data.
249
  ///
250 251 252
  /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
  /// nor [hasError].
  T get requireData {
253
    if (hasData)
254
      return data!;
255
    if (hasError)
256
      throw error!;
257
    throw StateError('Snapshot has neither data nor error');
258 259
  }

260 261 262 263 264
  /// 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.
265
  final Object? error;
266

267 268 269 270 271 272 273 274 275
  /// 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;

276
  /// Returns a snapshot like this one, but in the specified [state].
277
  ///
278 279 280
  /// 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);
281 282 283 284 285 286 287 288

  /// 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;
289

290 291 292 293
  /// Returns whether this snapshot contains a non-null [error] value.
  ///
  /// This is always true if the asynchronous computation's last result was
  /// failure.
294 295 296
  bool get hasError => error != null;

  @override
297
  String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)';
298 299

  @override
300
  bool operator ==(Object other) {
301 302
    if (identical(this, other))
      return true;
303 304 305
    return other is AsyncSnapshot<T>
        && other.connectionState == connectionState
        && other.data == data
306 307
        && other.error == error
        && other.stackTrace == stackTrace;
308 309 310
  }

  @override
311
  int get hashCode => hashValues(connectionState, data, error);
312 313 314 315 316 317 318
}

/// Signature for strategies that build widgets based on asynchronous
/// interaction.
///
/// See also:
///
319 320 321 322
///  * [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].
323
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
324 325 326 327

/// Widget that builds itself based on the latest snapshot of interaction with
/// a [Stream].
///
328 329
/// {@youtube 560 315 https://www.youtube.com/watch?v=MkKEWHfy99Y}
///
330
/// Widget rebuilding is scheduled by each interaction, using [State.setState],
331
/// but is otherwise decoupled from the timing of the stream. The [builder]
332 333 334 335 336
/// 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
337
/// 0 through 9, the [builder] may be called with any ordered sub-sequence
338 339 340
/// of the following snapshots that includes the last one (the one with
/// ConnectionState.done):
///
341 342 343
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
344
/// * ...
345 346
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
347
///
348 349 350
/// 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.
351 352
///
/// Changing the [StreamBuilder] configuration to another stream during event
353
/// generation introduces snapshot pairs of the form:
354
///
355 356
/// * `AsyncSnapshot<int>.withData(ConnectionState.none, 5)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, 5)`
357
///
358 359
/// The latter will be produced only when the new stream is non-null, and the
/// former only when the old stream is non-null.
360
///
361
/// The stream may produce errors, resulting in snapshots of the form:
362
///
363
/// * `AsyncSnapshot<int>.withError(ConnectionState.active, 'some error', someStackTrace)`
364 365 366 367
///
/// The data and error fields of snapshots produced are only changed when the
/// state is `ConnectionState.active`.
///
368
/// The initial snapshot data can be controlled by specifying [initialData].
369 370
/// 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
371
/// to be processed.
372
///
373
/// {@tool dartpad --template=stateful_widget_material}
374 375 376 377 378 379
///
/// 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.
380 381
///
/// ```dart
382 383 384 385 386 387 388
/// Stream<int> _bids = (() async* {
///   await Future<void>.delayed(Duration(seconds: 1));
///   yield 1;
///   await Future<void>.delayed(Duration(seconds: 1));
/// })();
///
/// Widget build(BuildContext context) {
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
///   return DefaultTextStyle(
///     style: Theme.of(context).textTheme.headline2,
///     textAlign: TextAlign.center,
///     child: Container(
///       alignment: FractionalOffset.center,
///       color: Colors.white,
///       child: StreamBuilder<int>(
///         stream: _bids,
///         builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
///           List<Widget> children;
///           if (snapshot.hasError) {
///             children = <Widget>[
///               Icon(
///                 Icons.error_outline,
///                 color: Colors.red,
///                 size: 60,
///               ),
///               Padding(
///                 padding: const EdgeInsets.only(top: 16),
///                 child: Text('Error: ${snapshot.error}'),
409 410 411 412 413
///               ),
///               Padding(
///                 padding: const EdgeInsets.only(top: 8),
///                 child: Text('Stack trace: ${snapshot.stackTrace}'),
///               ),
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
///             ];
///           } else {
///             switch (snapshot.connectionState) {
///               case ConnectionState.none:
///                 children = <Widget>[
///                   Icon(
///                     Icons.info,
///                     color: Colors.blue,
///                     size: 60,
///                   ),
///                   const Padding(
///                     padding: EdgeInsets.only(top: 16),
///                     child: Text('Select a lot'),
///                   )
///                 ];
///                 break;
///               case ConnectionState.waiting:
///                 children = <Widget>[
///                   SizedBox(
///                     child: const CircularProgressIndicator(),
///                     width: 60,
///                     height: 60,
///                   ),
///                   const Padding(
///                     padding: EdgeInsets.only(top: 16),
///                     child: Text('Awaiting bids...'),
///                   )
///                 ];
///                 break;
///               case ConnectionState.active:
///                 children = <Widget>[
///                   Icon(
///                     Icons.check_circle_outline,
///                     color: Colors.green,
///                     size: 60,
///                   ),
///                   Padding(
///                     padding: const EdgeInsets.only(top: 16),
///                     child: Text('\$${snapshot.data}'),
///                   )
///                 ];
///                 break;
///               case ConnectionState.done:
///                 children = <Widget>[
///                   Icon(
///                     Icons.info,
///                     color: Colors.blue,
///                     size: 60,
///                   ),
///                   Padding(
///                     padding: const EdgeInsets.only(top: 16),
///                     child: Text('\$${snapshot.data} (closed)'),
///                   )
///                 ];
///                 break;
///             }
470 471
///           }
///
472 473 474 475 476 477 478
///           return Column(
///             mainAxisAlignment: MainAxisAlignment.center,
///             crossAxisAlignment: CrossAxisAlignment.center,
///             children: children,
///           );
///         },
///       ),
479 480 481
///     ),
///   );
/// }
482
/// ```
483
/// {@end-tool}
484 485 486 487 488 489 490 491
///
/// 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.
// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1139 is fixed
492
class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
493
  /// Creates a new [StreamBuilder] that builds itself based on the latest
494
  /// snapshot of interaction with the specified [stream] and whose build
495 496
  /// strategy is given by [builder].
  ///
497
  /// The [initialData] is used to create the initial snapshot.
498 499
  ///
  /// The [builder] must not be null.
500
  const StreamBuilder({
501
    Key? key,
502
    this.initialData,
503 504
    Stream<T>? stream,
    required this.builder,
505 506
  }) : assert(builder != null),
       super(key: key, stream: stream);
507

508
  /// The build strategy currently used by this builder.
509 510 511
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.
512 513
  final AsyncWidgetBuilder<T> builder;

514 515
  /// The data that will be used to create the initial snapshot.
  ///
516
  /// Providing this value (presumably obtained synchronously somehow when the
517
  /// [Stream] was created) ensures that the first frame will show useful data.
518 519 520 521
  /// 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.
522
  final T? initialData;
523

524
  @override
525 526
  AsyncSnapshot<T> initial() => initialData == null
      ? AsyncSnapshot<T>.nothing()
527
      : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
528 529 530 531 532 533

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

  @override
  AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
534
    return AsyncSnapshot<T>.withData(ConnectionState.active, data);
535 536 537
  }

  @override
538 539
  AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
    return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
  }

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

/// Widget that builds itself based on the latest snapshot of interaction with
/// a [Future].
///
555
/// The [future] must have been obtained earlier, e.g. during [State.initState],
556
/// [State.didUpdateWidget], or [State.didChangeDependencies]. It must not be
557 558 559 560 561 562 563 564
/// 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.
///
565 566
/// {@youtube 560 315 https://www.youtube.com/watch?v=ek8ZPdWj4Qo}
///
567 568
/// ## Timing
///
569 570
/// Widget rebuilding is scheduled by the completion of the future, using
/// [State.setState], but is otherwise decoupled from the timing of the future.
571
/// The [builder] callback is called at the discretion of the Flutter pipeline, and
572 573 574
/// will thus receive a timing-dependent sub-sequence of the snapshots that
/// represent the interaction with the future.
///
575 576 577 578 579 580 581
/// 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
///
582 583 584
/// 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:
585
///
586 587
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
588
///
589 590
/// If that same future instead completed with an error, the [builder] would be
/// called with either both or only the latter of:
591
///
592 593
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.withError(ConnectionState.done, 'some error', someStackTrace)`
594 595 596 597 598
///
/// 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.
599
///
600 601 602 603 604 605
/// 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:
///
606 607
/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
608 609
///
/// In general, the latter will be produced only when the new future is
610
/// non-null, and the former only when the old future is non-null.
611 612 613 614
///
/// 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.
615
///
616
/// {@tool dartpad --template=stateful_widget_material}
617 618 619 620 621 622
///
/// 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.
623 624
///
/// ```dart
625 626 627 628 629 630
/// Future<String> _calculation = Future<String>.delayed(
///   Duration(seconds: 2),
///   () => 'Data Loaded',
/// );
///
/// Widget build(BuildContext context) {
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
///   return DefaultTextStyle(
///     style: Theme.of(context).textTheme.headline2,
///     textAlign: TextAlign.center,
///     child: FutureBuilder<String>(
///       future: _calculation, // a previously-obtained Future<String> or null
///       builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
///         List<Widget> children;
///         if (snapshot.hasData) {
///           children = <Widget>[
///             Icon(
///               Icons.check_circle_outline,
///               color: Colors.green,
///               size: 60,
///             ),
///             Padding(
///               padding: const EdgeInsets.only(top: 16),
///               child: Text('Result: ${snapshot.data}'),
///             )
///           ];
///         } else if (snapshot.hasError) {
///           children = <Widget>[
///             Icon(
///               Icons.error_outline,
///               color: Colors.red,
///               size: 60,
///             ),
///             Padding(
///               padding: const EdgeInsets.only(top: 16),
///               child: Text('Error: ${snapshot.error}'),
///             )
///           ];
///         } else {
///           children = <Widget>[
///             SizedBox(
///               child: CircularProgressIndicator(),
///               width: 60,
///               height: 60,
///             ),
///             const Padding(
///               padding: EdgeInsets.only(top: 16),
///               child: Text('Awaiting result...'),
///             )
///           ];
///         }
///         return Center(
///           child: Column(
///             mainAxisAlignment: MainAxisAlignment.center,
///             crossAxisAlignment: CrossAxisAlignment.center,
///             children: children,
680
///           ),
681 682 683
///         );
///       },
///     ),
684 685
///   );
/// }
686
/// ```
687
/// {@end-tool}
688
// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/sdk/issues/35520 is fixed
689
class FutureBuilder<T> extends StatefulWidget {
690 691 692
  /// Creates a widget that builds itself based on the latest snapshot of
  /// interaction with a [Future].
  ///
693
  /// The [builder] must not be null.
694
  const FutureBuilder({
695
    Key? key,
696
    this.future,
697
    this.initialData,
698
    required this.builder,
699 700
  }) : assert(builder != null),
       super(key: key);
701 702

  /// The asynchronous computation to which this builder is currently connected,
703
  /// possibly null.
704 705
  ///
  /// If no future has yet completed, including in the case where [future] is
706
  /// null, the data provided to the [builder] will be set to [initialData].
707
  final Future<T>? future;
708

709 710 711 712 713 714
  /// 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:
  ///
715 716 717
  ///  * [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.
718
  ///
719 720 721 722
  ///  * [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.
723 724 725 726 727 728
  ///
  ///  * [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.
729 730 731
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.
732 733
  final AsyncWidgetBuilder<T> builder;

734 735 736
  /// The data that will be used to create the snapshots provided until a
  /// non-null [future] has completed.
  ///
737 738 739
  /// 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
740
  /// [AsyncSnapshot.hasError] will be true.)
741
  final T? initialData;
742

743
  @override
744
  State<FutureBuilder<T>> createState() => _FutureBuilderState<T>();
745 746 747
}

/// State for [FutureBuilder].
748
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
749 750 751
  /// 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.
752 753
  Object? _activeCallbackIdentity;
  late AsyncSnapshot<T> _snapshot;
754 755 756 757

  @override
  void initState() {
    super.initState();
758 759
    _snapshot = widget.initialData == null
        ? AsyncSnapshot<T>.nothing()
760
        : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
761 762 763 764
    _subscribe();
  }

  @override
765
  void didUpdateWidget(FutureBuilder<T> oldWidget) {
766
    super.didUpdateWidget(oldWidget);
767
    if (oldWidget.future != widget.future) {
768 769 770 771 772 773 774 775 776
      if (_activeCallbackIdentity != null) {
        _unsubscribe();
        _snapshot = _snapshot.inState(ConnectionState.none);
      }
      _subscribe();
    }
  }

  @override
777
  Widget build(BuildContext context) => widget.builder(context, _snapshot);
778 779 780 781 782 783 784 785

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

  void _subscribe() {
786
    if (widget.future != null) {
787
      final Object callbackIdentity = Object();
788
      _activeCallbackIdentity = callbackIdentity;
789
      widget.future!.then<void>((T data) {
790 791
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
792
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
793 794
          });
        }
795
      }, onError: (Object error, StackTrace stackTrace) {
796 797
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
798
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
799 800 801 802 803 804 805 806 807 808 809
          });
        }
      });
      _snapshot = _snapshot.inState(ConnectionState.waiting);
    }
  }

  void _unsubscribe() {
    _activeCallbackIdentity = null;
  }
}