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
5import 'dart:async' show StreamSubscription;
6
7import 'package:flutter/foundation.dart';
8
9import '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.
41abstract 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].
96class _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.
169enum 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
198class 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].
317typedef 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.
383class 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}
515class 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].
579class _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