Revert "AsyncSnapshot.data to throw if error or no data (#34626)" (#36618)
This reverts commit b61fcfd25d0b32ec3791d09493d0235bb9a2b43f.
diff --git a/packages/flutter/lib/src/widgets/async.dart b/packages/flutter/lib/src/widgets/async.dart
index 18844f5..7491908 100644
--- a/packages/flutter/lib/src/widgets/async.dart
+++ b/packages/flutter/lib/src/widgets/async.dart
@@ -59,41 +59,35 @@
///
/// Sub-classes must override this method to provide the initial value for
/// the fold computation.
- @protected
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.
- @protected
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.
- @protected
S afterData(S current, T data);
/// Returns an updated version of the [current] summary following an error.
///
/// The default implementation returns [current] as is.
- @protected
S afterError(S current, Object error) => current;
/// Returns an updated version of the [current] summary following stream
/// termination.
///
/// The default implementation returns [current] as is.
- @protected
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.
- @protected
S afterDisconnected(S current) => current;
/// Returns a Widget based on the [currentSummary].
@@ -191,8 +185,6 @@
/// Immutable representation of the most recent interaction with an asynchronous
/// computation.
///
-/// `T` is the type of computation data.
-///
/// See also:
///
/// * [StreamBuilder], which builds itself based on a snapshot from interacting
@@ -201,68 +193,46 @@
/// with a [Future].
@immutable
class AsyncSnapshot<T> {
- /// Creates an [AsyncSnapshot] with the specified [connectionState] and
- /// [hasData], and optionally either [data] or [error] (but not both).
- ///
- /// It is legal for both [hasData] to be true and [data] to be null.
- const AsyncSnapshot._(this.connectionState, this.hasData, this._data, this.error)
+ /// Creates an [AsyncSnapshot] with the specified [connectionState],
+ /// and optionally either [data] or [error] (but not both).
+ const AsyncSnapshot._(this.connectionState, this.data, this.error)
: assert(connectionState != null),
- assert(hasData != null),
- assert(hasData || _data == null),
- assert(!(hasData && error != null));
+ assert(!(data != null && error != null));
- /// Creates an [AsyncSnapshot] in the specified [state] and with neither
- /// [data] nor [error].
- const AsyncSnapshot.withoutData(ConnectionState state) : this._(state, false, null, null);
+ /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.
+ const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null);
- /// Creates an [AsyncSnapshot] in the specified [state] and with the
- /// specified [data] (possibly null).
- const AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, true, data, null);
+ /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data].
+ const AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, data, null);
- /// Creates an [AsyncSnapshot] in the specified `state` and with the
- /// specified [error].
- const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, false, null, error);
+ /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [error].
+ const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, null, error);
- /// The current state of the connection to the asynchronous computation.
- ///
- /// This property exists independently of the [data] and [error] properties.
- /// In other words, a snapshot can exist with any combination of
- /// (`connectionState`/`data`) or (`connectionState`/`error`) tuples.
- ///
- /// This is guaranteed to be non-null.
+ /// Current state of connection to the asynchronous computation.
final ConnectionState connectionState;
- /// Whether this snapshot contains [data].
+ /// The latest data received by the asynchronous computation.
///
- /// This can be false even when the asynchronous computation has completed
- /// successfully ([connectionState] is [ConnectionState.done]), if the
- /// computation did not return a value. For example, a [Future<void>] will
- /// complete with no data even if it completes successfully.
+ /// If this is non-null, [hasData] will be true.
///
- /// If this property is false, then attempting to access the [data] property
- /// will throw an exception.
- final bool hasData;
+ /// If [error] is not null, this will be null. See [hasError].
+ ///
+ /// 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].
+ final T data;
- /// The latest data received by the asynchronous computation, failing if
- /// there is no data.
+ /// Returns latest data received, failing if there is no data.
///
- /// If [hasData] is true, accessing this property will not throw an error.
- ///
- /// If [error] is not null, attempting to access this property will throw
- /// [error]. See [hasError].
- ///
- /// If neither [hasData] nor [hasError] is true, then accessing this
- /// property will throw a [StateError].
- T get data {
+ /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
+ /// nor [hasError].
+ T get requireData {
if (hasData)
- return _data;
- if (hasError) {
- // TODO(tvolkert): preserve the stack trace (https://github.com/dart-lang/sdk/issues/30741)
+ return data;
+ if (hasError)
throw error;
- }
throw StateError('Snapshot has neither data nor error');
}
- final T _data;
/// The latest error object received by the asynchronous computation.
///
@@ -273,46 +243,41 @@
/// Returns a snapshot like this one, but in the specified [state].
///
- /// The [hasData], [data], [hasError], and [error] fields persist unmodified,
- /// even if the new state is [ConnectionState.none].
- AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, hasData, _data, error);
+ /// The [data] and [error] fields persist unmodified, even if the new state is
+ /// [ConnectionState.none].
+ AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error);
+
+ /// 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;
/// Returns whether this snapshot contains a non-null [error] value.
///
/// This is always true if the asynchronous computation's last result was
/// failure.
- ///
- /// When this is true, [hasData] will always be false.
bool get hasError => error != null;
@override
- String toString() {
- final StringBuffer buffer = StringBuffer()..write('$runtimeType')
- ..write('(')
- ..write('$connectionState');
- if (hasData)
- buffer.write(', data: $_data');
- else if (hasError)
- buffer.write(', error: $error');
- buffer.write(')');
- return buffer.toString();
- }
+ String toString() => '$runtimeType($connectionState, $data, $error)';
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
- if (runtimeType != other.runtimeType)
+ if (other is! AsyncSnapshot<T>)
return false;
final AsyncSnapshot<T> typedOther = other;
return connectionState == typedOther.connectionState
- && hasData == typedOther.hasData
- && _data == typedOther._data
+ && data == typedOther.data
&& error == typedOther.error;
}
@override
- int get hashCode => hashValues(connectionState, hasData, _data, error);
+ int get hashCode => hashValues(connectionState, data, error);
}
/// Signature for strategies that build widgets based on asynchronous
@@ -342,12 +307,12 @@
/// of the following snapshots that includes the last one (the one with
/// ConnectionState.done):
///
-/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
-/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
-/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
+/// * `new AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
+/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
+/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
/// * ...
-/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
-/// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
+/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
+/// * `new AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
///
/// 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
@@ -364,7 +329,7 @@
///
/// The stream may produce errors, resulting in snapshots of the form:
///
-/// * `AsyncSnapshot<int>.withError(ConnectionState.active, 'some error')`
+/// * `new AsyncSnapshot<int>.withError(ConnectionState.active, 'some error')`
///
/// The data and error fields of snapshots produced are only changed when the
/// state is `ConnectionState.active`.
@@ -372,20 +337,7 @@
/// The initial snapshot data can be controlled by specifying [initialData].
/// 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
-/// to be processed. In cases where callers wish to have no initial data, the
-/// [new StreamBuilder.withoutInitialData] constructor may be used. Doing so
-/// may cause the first frame to have a snapshot that contains no data.
-///
-/// ## Void StreamBuilders
-///
-/// The `StreamBuilder<void>` type will produce snapshots that contain no data.
-/// An example stream of snapshots would be the following:
-///
-/// * `AsyncSnapshot<void>.withoutData(ConnectionState.waiting)`
-/// * `AsyncSnapshot<void>.withoutData(ConnectionState.active)`
-/// * ...
-/// * `AsyncSnapshot<void>.withoutData(ConnectionState.active)`
-/// * `AsyncSnapshot<void>.withoutData(ConnectionState.done)`
+/// to be processed.
///
/// {@tool sample}
///
@@ -396,7 +348,6 @@
/// ```dart
/// StreamBuilder<int>(
/// stream: _lot?.bids, // a Stream<int> or null
-/// initialData: 100, // initial seed value
/// builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
/// if (snapshot.hasError)
/// return Text('Error: ${snapshot.error}');
@@ -421,95 +372,42 @@
// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1139 is fixed
class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
/// Creates a new [StreamBuilder] that builds itself based on the latest
- /// snapshot of interaction with the specified `stream` and whose build
+ /// snapshot of interaction with the specified [stream] and whose build
/// strategy is given by [builder].
///
- /// The [initialData] argument is used to create the initial snapshot. For
- /// cases where there is no initial snapshot or the initial snapshot is not
- /// yet available, callers may construct a [StreamBuilder] without an initial
- /// snapshot using [new StreamBuilder.withoutInitialData].
+ /// The [initialData] is used to create the initial snapshot.
///
/// The [builder] must not be null.
const StreamBuilder({
Key key,
- @required T initialData,
+ this.initialData,
Stream<T> stream,
@required this.builder,
}) : assert(builder != null),
- hasInitialData = true,
- _initialData = initialData,
- super(key: key, stream: stream);
-
- /// Creates a new [StreamBuilder] that builds itself based on the latest
- /// snapshot of interaction with the specified `stream` and whose build
- /// strategy is given by [builder].
- ///
- /// The initial snapshot will contain no data.
- ///
- /// The [builder] must not be null.
- const StreamBuilder.withoutInitialData({
- Key key,
- Stream<T> stream,
- @required this.builder,
- }) : assert(builder != null),
- hasInitialData = false,
- _initialData = null,
super(key: key, stream: stream);
/// The build strategy currently used by this builder.
final AsyncWidgetBuilder<T> builder;
- /// Whether this builder's initial snapshot contains data.
- ///
- /// If this is false, then attempting to access [initialData] will throw an
- /// error.
- ///
- /// See also:
- ///
- /// * [AsyncSnapshot.hasData], the corresponding property that will be set
- /// in the initial snapshot.
- final bool hasInitialData;
-
/// The data that will be used to create the initial snapshot.
///
/// Providing this value (presumably obtained synchronously somehow when the
/// [Stream] was created) ensures that the first frame will show useful data.
- /// Otherwise, the first frame will be built with a snapshot that contains no
- /// data, 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.
- ///
- /// Some builders intentionally have no data when first built. For those
- /// cases, callers can use the [new StreamBuilder.withoutInitialData]
- /// constructor. When a builder was constructed in this way, attempting to
- /// access the [initialData] property will throw a [StateError].
- T get initialData {
- if (!hasInitialData) {
- throw StateError(
- 'StreamBuilder was created without initial data, yet the initialData '
- 'property was accessed. If you wish your StreamBuilder to have initial '
- 'data, create it using the default constructor.',
- );
- }
- return _initialData;
- }
- final T _initialData;
+ /// 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.
+ final T initialData;
@override
- AsyncSnapshot<T> initial() {
- return hasInitialData
- ? AsyncSnapshot<T>.withData(ConnectionState.none, initialData)
- : AsyncSnapshot<T>.withoutData(ConnectionState.none);
- }
+ AsyncSnapshot<T> initial() => AsyncSnapshot<T>.withData(ConnectionState.none, initialData);
@override
AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
@override
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
- return _TypeLiteral.isVoidType(T)
- ? AsyncSnapshot<T>.withoutData(ConnectionState.active)
- : AsyncSnapshot<T>.withData(ConnectionState.active, data);
+ return AsyncSnapshot<T>.withData(ConnectionState.active, data);
}
@override
@@ -557,17 +455,23 @@
///
/// ## Builder contract
///
-/// For a future that completes successfully with data, the [builder] will be
-/// called with either both or only the latter of the following snapshots:
+/// 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:
///
-/// * `AsyncSnapshot<String>.withoutData(ConnectionState.waiting)`
-/// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
+/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
+/// * `new AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
///
/// If that same future instead completed with an error, the [builder] would be
/// called with either both or only the latter of:
///
-/// * `AsyncSnapshot<String>.withoutData(ConnectionState.waiting)`
-/// * `AsyncSnapshot<String>.withError(ConnectionState.done, 'some error')`
+/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
+/// * `new AsyncSnapshot<String>.withError(ConnectionState.done, 'some error')`
+///
+/// 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.
///
/// 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
@@ -575,8 +479,8 @@
/// old future has already completed successfully with data as above, changing
/// configuration to a new future results in snapshot pairs of the form:
///
-/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
-/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
+/// * `new AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
+/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
///
/// In general, the latter will be produced only when the new future is
/// non-null, and the former only when the old future is non-null.
@@ -585,12 +489,6 @@
/// `future?.asStream()`, except that snapshots with `ConnectionState.active`
/// may appear for the latter, depending on how the stream is implemented.
///
-/// ## Void futures
-///
-/// The `FutureBuilder<void>` type will produce snapshots that contain no data:
-///
-/// * `AsyncSnapshot<String>.withoutData(ConnectionState.done)`
-///
/// {@tool sample}
///
/// This sample shows a [FutureBuilder] configuring a text label to show the
@@ -599,13 +497,7 @@
///
/// ```dart
/// FutureBuilder<String>(
-/// // A previously-obtained `Future<String>` or null.
-/// //
-/// // This MUST NOT be created during the call to the `build()` method that
-/// // creates the `FutureBuilder`. Doing so will cause a new future to be
-/// // instantiated every time `build()` is called (potentially every frame).
-/// future: _calculation,
-///
+/// future: _calculation, // a previously-obtained Future<String> or null
/// builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
/// switch (snapshot.connectionState) {
/// case ConnectionState.none:
@@ -628,33 +520,11 @@
/// Creates a widget that builds itself based on the latest snapshot of
/// interaction with a [Future].
///
- /// The [future] argument must have been obtained earlier, e.g. during
- /// [State.initState], [State.didUpdateConfig], or
- /// [State.didChangeDependencies]. It must not be 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.
- ///
- // ignore: deprecated_member_use_from_same_package
- /// The [initialData] argument specifies the data that will be used to create
- /// the snapshots provided to [builder] until a non-null [future] has
- /// completed. This argument is deprecated and will be removed in a future
- /// stable release because snapshots that are provided to the [builder]
- /// contain an [AsyncSnapshot.connectionState] property that indicates the
- /// state of the [future]. The builder can use that connection state to
- /// provide an "initial value" when the future has not yet completed.
- ///
- /// The [builder] argument must not be null.
+ /// The [builder] must not be null.
const FutureBuilder({
Key key,
this.future,
- @Deprecated(
- 'Instead of providing initialData to FutureBuilder, consider checking '
- 'for ConnectionState.none or ConnectionState.waiting in your build() '
- 'method to know whether the future has completed or not.',
- )
- this.initialData, // ignore: deprecated_member_use_from_same_package
+ this.initialData,
@required this.builder,
}) : assert(builder != null),
super(key: key);
@@ -663,10 +533,7 @@
/// possibly null.
///
/// If no future has yet completed, including in the case where [future] is
- // ignore: deprecated_member_use_from_same_package
- /// null, the snapshot provided to the [builder] will contain [initialData]
- /// if this widget was created with initial data or will contain no data if
- /// this widget was created without initial data.
+ /// null, the data provided to the [builder] will be set to [initialData].
final Future<T> future;
/// The build strategy currently used by this builder.
@@ -675,56 +542,29 @@
/// [AsyncSnapshot.connectionState] property will be one of the following
/// values:
///
- /// * [ConnectionState.none]: [future] is null.
+ /// * [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.
///
- /// If this widget was created with initial data (deprecated), then the
- /// [AsyncSnapshot.data] will be set to [initialData], unless a future has
- /// previously completed, in which case the previous result persists.
- ///
- /// If this widget was created without initial data, then the
- /// [AsyncSnapshot.data] will be unset, and attempts to access the data
- /// will result in an exception.
- ///
- /// * [ConnectionState.waiting]: [future] is not null but has not yet
- /// completed.
- ///
- /// If this widget was created with initial data (deprecated), then the
- /// [AsyncSnapshot.data] will be set to [initialData], unless a future has
- /// previously completed, in which case the previous result persists.
- ///
- /// If this widget was created without initial data, then the
- /// [AsyncSnapshot.data] will be unset, and attempts to access the data
- /// will result in an exception.
+ /// * [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.
///
/// * [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.
- ///
- /// In the case of [future] being a [Future<void>], the snapshot will not
- /// contain data even if the future completed successfully.
final AsyncWidgetBuilder<T> builder;
/// The data that will be used to create the snapshots provided until a
/// non-null [future] has completed.
///
- /// If the future completes with an error, the [AsyncSnapshot] provided to
- /// the [builder] will contain no data, regardless of [initialData]. (The
- /// error itself will be available in [AsyncSnapshot.error], and
+ /// 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
/// [AsyncSnapshot.hasError] will be true.)
- ///
- /// This field is deprecated and will be removed in a future stable release
- /// because snapshots that are provided to the [builder] contain an
- /// [AsyncSnapshot.connectionState] property that indicates the state of the
- /// [future]. The builder can use that connection state to provide an
- /// "initial value" when the future has not yet completed.
- @Deprecated(
- 'Instead of using FutureBuilder.initialData, consider checking '
- 'for ConnectionState.none or ConnectionState.waiting in your build() '
- 'ConnectionState.none or ConnectionState.waiting in your build() '
- 'method to know whether the future has completed or not.',
- )
final T initialData;
@override
@@ -742,11 +582,7 @@
@override
void initState() {
super.initState();
- // ignore: deprecated_member_use_from_same_package
- _snapshot = widget.initialData == null
- ? AsyncSnapshot<T>.withoutData(ConnectionState.none)
- // ignore: deprecated_member_use_from_same_package
- : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
+ _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
_subscribe();
}
@@ -778,9 +614,7 @@
widget.future.then<void>((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
- _snapshot = _TypeLiteral.isVoidType(T)
- ? AsyncSnapshot<T>.withoutData(ConnectionState.done)
- : AsyncSnapshot<T>.withData(ConnectionState.done, data);
+ _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error) {
@@ -798,33 +632,3 @@
_activeCallbackIdentity = null;
}
}
-
-/// Class that allows callers to reference instances of [Type] that would
-/// otherwise not be valid expressions.
-///
-/// Generic types and the `void` type are not usable as Dart expressions, so
-/// the following statements are not legal and all yield compile-time errors:
-///
-/// ```dart
-/// if (type == List<int>) print('msg');
-/// if (type == void) print('msg');
-/// Type type = List<int>;
-/// ```
-///
-/// This class allows callers to get handles on such types, like so:
-///
-/// ```dart
-/// if (type == const _TypeLiteral<List<int>>().type) print('msg');
-/// if (type == const _TypeLiteral<void>().type) print('msg');
-/// Type type = const _TypeLiteral<List<int>>().type;
-/// ```
-class _TypeLiteral<T> {
- /// Creates a new [_TypeLiteral].
- const _TypeLiteral();
-
- /// Returns whether the specified type represents a "void" type.
- static bool isVoidType(Type type) => type == const _TypeLiteral<void>().type;
-
- /// The [Type] (`T`) represented by this [_TypeLiteral].
- Type get type => T;
-}
diff --git a/packages/flutter/test/widgets/async_test.dart b/packages/flutter/test/widgets/async_test.dart
index 6e559af..d8af97a 100644
--- a/packages/flutter/test/widgets/async_test.dart
+++ b/packages/flutter/test/widgets/async_test.dart
@@ -12,33 +12,23 @@
return Text(snapshot.toString(), textDirection: TextDirection.ltr);
}
group('AsyncSnapshot', () {
- test('data succeeds if data is present', () {
- const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withData(ConnectionState.done, 'hello');
- expect(snapshot.hasData, isTrue);
- expect(snapshot.data, 'hello');
- expect(snapshot.hasError, isFalse);
- expect(snapshot.error, isNull);
+ test('requiring data succeeds if data is present', () {
+ expect(
+ const AsyncSnapshot<String>.withData(ConnectionState.done, 'hello').requireData,
+ 'hello',
+ );
});
- test('data throws if there is an error', () {
- const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withError(ConnectionState.done, 'error');
- expect(snapshot.hasData, isFalse);
- expect(() => snapshot.data, throwsA(equals('error')));
- expect(snapshot.hasError, isTrue);
- expect(snapshot.error, 'error');
+ test('requiring data fails if there is an error', () {
+ expect(
+ () => const AsyncSnapshot<String>.withError(ConnectionState.done, 'error').requireData,
+ throwsA(equals('error')),
+ );
});
- test('data throws if created without data', () {
- const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withoutData(ConnectionState.none);
- expect(snapshot.hasData, isFalse);
- expect(() => snapshot.data, throwsStateError);
- expect(snapshot.hasError, isFalse);
- expect(snapshot.error, isNull);
- });
- test('data can be null', () {
- const AsyncSnapshot<int> snapshot = AsyncSnapshot<int>.withData(ConnectionState.none, null);
- expect(snapshot.hasData, isTrue);
- expect(snapshot.data, isNull);
- expect(snapshot.hasError, isFalse);
- expect(snapshot.error, isNull);
+ test('requiring data fails if snapshot has neither data nor error', () {
+ expect(
+ () => const AsyncSnapshot<String>.nothing().requireData,
+ throwsStateError,
+ );
});
});
group('Async smoke tests', () {
@@ -50,7 +40,7 @@
await eventFiring(tester);
});
testWidgets('StreamBuilder', (WidgetTester tester) async {
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
stream: Stream<String>.fromIterable(<String>['hello', 'world']),
builder: snapshotText,
));
@@ -69,12 +59,12 @@
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: null, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: completer.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
});
testWidgets('gracefully handles transition to null future', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
@@ -82,14 +72,14 @@
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: completer.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: null, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
completer.complete('hello');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
});
testWidgets('gracefully handles transition to other future', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
@@ -98,132 +88,125 @@
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: completerA.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
await tester.pumpWidget(FutureBuilder<String>(
key: key, future: completerB.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
completerB.complete('B');
completerA.complete('A');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: B)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, B, null)'), findsOneWidget);
});
testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async {
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(FutureBuilder<String>(
future: completer.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
completer.complete('hello');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: hello)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsOneWidget);
});
testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async {
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(FutureBuilder<String>(
future: completer.future, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
completer.completeError('bad');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, error: bad)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsOneWidget);
});
- testWidgets('produces snapshot with null data for null-completing data Future', (WidgetTester tester) async {
+ testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
+ final GlobalKey key = GlobalKey();
+ await tester.pumpWidget(FutureBuilder<String>(
+ key: key,
+ future: null,
+ builder: snapshotText,
+ initialData: 'I',
+ ));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
+ });
+ testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
+ final GlobalKey key = GlobalKey();
+ await tester.pumpWidget(FutureBuilder<String>(
+ key: key,
+ future: null,
+ builder: snapshotText,
+ initialData: 'I',
+ ));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(FutureBuilder<String>(
- future: completer.future, builder: snapshotText,
+ key: key,
+ future: completer.future,
+ builder: snapshotText,
+ initialData: 'Ignored',
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
- completer.complete(null);
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: null)'), findsOneWidget);
- });
- testWidgets('produces snapshot with null data for Future<Null>', (WidgetTester tester) async {
- final Completer<Null> completer = Completer<Null>(); // ignore: prefer_void_to_null
- await tester.pumpWidget(FutureBuilder<Null>( // ignore: prefer_void_to_null
- future: completer.future, builder: snapshotText,
- ));
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.waiting)'), findsOneWidget);
- completer.complete();
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.done, data: null)'), findsOneWidget);
- });
- testWidgets('produces snapshot with no data for Future<void>', (WidgetTester tester) async {
- final Completer<void> completer = Completer<void>();
- await tester.pumpWidget(
- FutureBuilder<void>(
- future: completer.future,
- builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
- return Text(snapshot.toString(), textDirection: TextDirection.ltr);
- },
- ),
- );
- expect(find.text('AsyncSnapshot<void>(ConnectionState.waiting)'), findsOneWidget);
- completer.complete();
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<void>(ConnectionState.done)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
});
});
group('StreamBuilder', () {
testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: null, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
final StreamController<String> controller = StreamController<String>();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: controller.stream, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
});
testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final StreamController<String> controller = StreamController<String>();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: controller.stream, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: null, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
});
testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final StreamController<String> controllerA = StreamController<String>();
final StreamController<String> controllerB = StreamController<String>();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: controllerA.stream, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: controllerB.stream, builder: snapshotText,
));
controllerB.add('B');
controllerA.add('A');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.active, data: B)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.active, B, null)'), findsOneWidget);
});
testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final StreamController<String> controller = StreamController<String>();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
+ await tester.pumpWidget(StreamBuilder<String>(
key: key, stream: controller.stream, builder: snapshotText,
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
controller.add('1');
controller.add('2');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.active, data: 2)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.active, 2, null)'), findsOneWidget);
controller.add('3');
controller.addError('bad');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.active, error: bad)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.active, null, bad)'), findsOneWidget);
controller.add('4');
controller.close();
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: 4)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, 4, null)'), findsOneWidget);
});
testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
final StreamController<String> controller = StreamController<String>();
@@ -232,7 +215,7 @@
builder: snapshotText,
initialData: 'I',
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, data: I)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
});
testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
@@ -242,7 +225,7 @@
builder: snapshotText,
initialData: 'I',
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none, data: I)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
final StreamController<String> controller = StreamController<String>();
await tester.pumpWidget(StreamBuilder<String>(
key: key,
@@ -250,69 +233,7 @@
builder: snapshotText,
initialData: 'Ignored',
));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, data: I)'), findsOneWidget);
- });
- testWidgets('produces snapshots with null data for null-producing stream', (WidgetTester tester) async {
- final GlobalKey key = GlobalKey();
- final StreamController<String> controller = StreamController<String>();
- await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
- key: key,
- stream: controller.stream,
- builder: snapshotText,
- ));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
- controller.add(null);
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.active, data: null)'), findsOneWidget);
- controller.addError('bad');
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.active, error: bad)'), findsOneWidget);
- controller.add(null);
- controller.close();
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: null)'), findsOneWidget);
- });
- testWidgets('produces snapshots with null data for Stream<Null>', (WidgetTester tester) async {
- final GlobalKey key = GlobalKey();
- final StreamController<Null> controller = StreamController<Null>(); // ignore: prefer_void_to_null
- await tester.pumpWidget(StreamBuilder<Null>.withoutInitialData( // ignore: prefer_void_to_null
- key: key,
- stream: controller.stream,
- builder: snapshotText,
- ));
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.waiting)'), findsOneWidget);
- controller.add(null);
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.active, data: null)'), findsOneWidget);
- controller.addError('bad');
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.active, error: bad)'), findsOneWidget);
- controller.add(null);
- controller.close();
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<Null>(ConnectionState.done, data: null)'), findsOneWidget);
- });
- testWidgets('produces snapshots with no data for Stream<void>', (WidgetTester tester) async {
- final GlobalKey key = GlobalKey();
- final StreamController<void> controller = StreamController<void>();
- await tester.pumpWidget(StreamBuilder<void>.withoutInitialData(
- key: key,
- stream: controller.stream,
- builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
- return Text(snapshot.toString(), textDirection: TextDirection.ltr);
- },
- ));
- expect(find.text('AsyncSnapshot<void>(ConnectionState.waiting)'), findsOneWidget);
- controller.add(null);
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<void>(ConnectionState.active)'), findsOneWidget);
- controller.addError('bad');
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<void>(ConnectionState.active, error: bad)'), findsOneWidget);
- controller.add(null);
- controller.close();
- await eventFiring(tester);
- expect(find.text('AsyncSnapshot<void>(ConnectionState.done)'), findsOneWidget);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
});
});
group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () {
@@ -320,30 +241,48 @@
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(Column(children: <Widget>[
FutureBuilder<String>(future: completer.future, builder: snapshotText),
- StreamBuilder<String>.withoutInitialData(stream: completer.future.asStream(), builder: snapshotText),
+ StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
]));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsNWidgets(2));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
completer.complete('hello');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: hello)'), findsNWidgets(2));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsNWidgets(2));
});
testWidgets('when completing with error', (WidgetTester tester) async {
final Completer<String> completer = Completer<String>();
await tester.pumpWidget(Column(children: <Widget>[
FutureBuilder<String>(future: completer.future, builder: snapshotText),
- StreamBuilder<String>.withoutInitialData(stream: completer.future.asStream(), builder: snapshotText),
+ StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
]));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsNWidgets(2));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
completer.completeError('bad');
await eventFiring(tester);
- expect(find.text('AsyncSnapshot<String>(ConnectionState.done, error: bad)'), findsNWidgets(2));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsNWidgets(2));
});
testWidgets('when Future is null', (WidgetTester tester) async {
await tester.pumpWidget(Column(children: <Widget>[
FutureBuilder<String>(future: null, builder: snapshotText),
- StreamBuilder<String>.withoutInitialData(stream: null, builder: snapshotText),
+ StreamBuilder<String>(stream: null, builder: snapshotText),
]));
- expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsNWidgets(2));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsNWidgets(2));
+ });
+ testWidgets('when initialData is used with null Future and Stream', (WidgetTester tester) async {
+ await tester.pumpWidget(Column(children: <Widget>[
+ FutureBuilder<String>(future: null, builder: snapshotText, initialData: 'I'),
+ StreamBuilder<String>(stream: null, builder: snapshotText, initialData: 'I'),
+ ]));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsNWidgets(2));
+ });
+ testWidgets('when using initialData and completing with data', (WidgetTester tester) async {
+ final Completer<String> completer = Completer<String>();
+ await tester.pumpWidget(Column(children: <Widget>[
+ FutureBuilder<String>(future: completer.future, builder: snapshotText, initialData: 'I'),
+ StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText, initialData: 'I'),
+ ]));
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsNWidgets(2));
+ completer.complete('hello');
+ await eventFiring(tester);
+ expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsNWidgets(2));
});
});
group('StreamBuilderBase', () {