AsyncSnapshot.data to throw if error or no data (#34626)
This updates `AsyncSnapshot.data` to act as `AsyncSnapshot.requireData` used to -- and it removes `AsyncSnapshot.requireData`. Correspondingly, this adds a `StreamBuilder.withoutInitialData()` constructor, makes the `initialData` argument to the default `StreamBuilder()` constructor required, and deprecates the `initialData` argument to the `FutureBuilder()` constructor. See the breaking change announcement for more info. https://github.com/flutter/flutter/issues/34545 https://groups.google.com/forum/#!topic/flutter-announce/H6Od0QdsdrI
This commit is contained in:
parent
1aa4628fa2
commit
b61fcfd25d
@ -59,35 +59,41 @@ abstract class StreamBuilderBase<T, S> extends StatefulWidget {
|
|||||||
///
|
///
|
||||||
/// Sub-classes must override this method to provide the initial value for
|
/// Sub-classes must override this method to provide the initial value for
|
||||||
/// the fold computation.
|
/// the fold computation.
|
||||||
|
@protected
|
||||||
S initial();
|
S initial();
|
||||||
|
|
||||||
/// Returns an updated version of the [current] summary reflecting that we
|
/// Returns an updated version of the [current] summary reflecting that we
|
||||||
/// are now connected to a stream.
|
/// are now connected to a stream.
|
||||||
///
|
///
|
||||||
/// The default implementation returns [current] as is.
|
/// The default implementation returns [current] as is.
|
||||||
|
@protected
|
||||||
S afterConnected(S current) => current;
|
S afterConnected(S current) => current;
|
||||||
|
|
||||||
/// Returns an updated version of the [current] summary following a data event.
|
/// Returns an updated version of the [current] summary following a data event.
|
||||||
///
|
///
|
||||||
/// Sub-classes must override this method to specify how the current summary
|
/// Sub-classes must override this method to specify how the current summary
|
||||||
/// is combined with the new data item in the fold computation.
|
/// is combined with the new data item in the fold computation.
|
||||||
|
@protected
|
||||||
S afterData(S current, T data);
|
S afterData(S current, T data);
|
||||||
|
|
||||||
/// Returns an updated version of the [current] summary following an error.
|
/// Returns an updated version of the [current] summary following an error.
|
||||||
///
|
///
|
||||||
/// The default implementation returns [current] as is.
|
/// The default implementation returns [current] as is.
|
||||||
|
@protected
|
||||||
S afterError(S current, Object error) => current;
|
S afterError(S current, Object error) => current;
|
||||||
|
|
||||||
/// Returns an updated version of the [current] summary following stream
|
/// Returns an updated version of the [current] summary following stream
|
||||||
/// termination.
|
/// termination.
|
||||||
///
|
///
|
||||||
/// The default implementation returns [current] as is.
|
/// The default implementation returns [current] as is.
|
||||||
|
@protected
|
||||||
S afterDone(S current) => current;
|
S afterDone(S current) => current;
|
||||||
|
|
||||||
/// Returns an updated version of the [current] summary reflecting that we
|
/// Returns an updated version of the [current] summary reflecting that we
|
||||||
/// are no longer connected to a stream.
|
/// are no longer connected to a stream.
|
||||||
///
|
///
|
||||||
/// The default implementation returns [current] as is.
|
/// The default implementation returns [current] as is.
|
||||||
|
@protected
|
||||||
S afterDisconnected(S current) => current;
|
S afterDisconnected(S current) => current;
|
||||||
|
|
||||||
/// Returns a Widget based on the [currentSummary].
|
/// Returns a Widget based on the [currentSummary].
|
||||||
@ -185,6 +191,8 @@ enum ConnectionState {
|
|||||||
/// Immutable representation of the most recent interaction with an asynchronous
|
/// Immutable representation of the most recent interaction with an asynchronous
|
||||||
/// computation.
|
/// computation.
|
||||||
///
|
///
|
||||||
|
/// `T` is the type of computation data.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [StreamBuilder], which builds itself based on a snapshot from interacting
|
/// * [StreamBuilder], which builds itself based on a snapshot from interacting
|
||||||
@ -193,46 +201,68 @@ enum ConnectionState {
|
|||||||
/// with a [Future].
|
/// with a [Future].
|
||||||
@immutable
|
@immutable
|
||||||
class AsyncSnapshot<T> {
|
class AsyncSnapshot<T> {
|
||||||
/// Creates an [AsyncSnapshot] with the specified [connectionState],
|
/// Creates an [AsyncSnapshot] with the specified [connectionState] and
|
||||||
/// and optionally either [data] or [error] (but not both).
|
/// [hasData], and optionally either [data] or [error] (but not both).
|
||||||
const AsyncSnapshot._(this.connectionState, this.data, this.error)
|
///
|
||||||
|
/// It is legal for both [hasData] to be true and [data] to be null.
|
||||||
|
const AsyncSnapshot._(this.connectionState, this.hasData, this._data, this.error)
|
||||||
: assert(connectionState != null),
|
: assert(connectionState != null),
|
||||||
assert(!(data != null && error != null));
|
assert(hasData != null),
|
||||||
|
assert(hasData || _data == null),
|
||||||
|
assert(!(hasData && error != null));
|
||||||
|
|
||||||
/// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.
|
/// Creates an [AsyncSnapshot] in the specified [state] and with neither
|
||||||
const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null);
|
/// [data] nor [error].
|
||||||
|
const AsyncSnapshot.withoutData(ConnectionState state) : this._(state, false, null, null);
|
||||||
|
|
||||||
/// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data].
|
/// Creates an [AsyncSnapshot] in the specified [state] and with the
|
||||||
const AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, data, null);
|
/// 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 [error].
|
/// Creates an [AsyncSnapshot] in the specified `state` and with the
|
||||||
const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, null, error);
|
/// specified [error].
|
||||||
|
const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, false, null, error);
|
||||||
|
|
||||||
/// Current state of connection to the asynchronous computation.
|
/// 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.
|
||||||
final ConnectionState connectionState;
|
final ConnectionState connectionState;
|
||||||
|
|
||||||
/// The latest data received by the asynchronous computation.
|
/// Whether this snapshot contains [data].
|
||||||
///
|
///
|
||||||
/// If this is non-null, [hasData] will be true.
|
/// 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 [error] is not null, this will be null. See [hasError].
|
/// If this property is false, then attempting to access the [data] property
|
||||||
///
|
/// will throw an exception.
|
||||||
/// If the asynchronous computation has never returned a value, this may be
|
final bool hasData;
|
||||||
/// set to an initial data value specified by the relevant widget. See
|
|
||||||
/// [FutureBuilder.initialData] and [StreamBuilder.initialData].
|
|
||||||
final T data;
|
|
||||||
|
|
||||||
/// Returns latest data received, failing if there is no data.
|
/// The latest data received by the asynchronous computation, failing if
|
||||||
|
/// there is no data.
|
||||||
///
|
///
|
||||||
/// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
|
/// If [hasData] is true, accessing this property will not throw an error.
|
||||||
/// nor [hasError].
|
///
|
||||||
T get requireData {
|
/// 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 {
|
||||||
if (hasData)
|
if (hasData)
|
||||||
return data;
|
return _data;
|
||||||
if (hasError)
|
if (hasError) {
|
||||||
|
// TODO(tvolkert): preserve the stack trace (https://github.com/dart-lang/sdk/issues/30741)
|
||||||
throw error;
|
throw error;
|
||||||
|
}
|
||||||
throw StateError('Snapshot has neither data nor error');
|
throw StateError('Snapshot has neither data nor error');
|
||||||
}
|
}
|
||||||
|
final T _data;
|
||||||
|
|
||||||
/// The latest error object received by the asynchronous computation.
|
/// The latest error object received by the asynchronous computation.
|
||||||
///
|
///
|
||||||
@ -243,41 +273,46 @@ class AsyncSnapshot<T> {
|
|||||||
|
|
||||||
/// Returns a snapshot like this one, but in the specified [state].
|
/// Returns a snapshot like this one, but in the specified [state].
|
||||||
///
|
///
|
||||||
/// The [data] and [error] fields persist unmodified, even if the new state is
|
/// The [hasData], [data], [hasError], and [error] fields persist unmodified,
|
||||||
/// [ConnectionState.none].
|
/// even if the new state is [ConnectionState.none].
|
||||||
AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error);
|
AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, hasData, _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.
|
/// Returns whether this snapshot contains a non-null [error] value.
|
||||||
///
|
///
|
||||||
/// This is always true if the asynchronous computation's last result was
|
/// This is always true if the asynchronous computation's last result was
|
||||||
/// failure.
|
/// failure.
|
||||||
|
///
|
||||||
|
/// When this is true, [hasData] will always be false.
|
||||||
bool get hasError => error != null;
|
bool get hasError => error != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$runtimeType($connectionState, $data, $error)';
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) {
|
bool operator ==(dynamic other) {
|
||||||
if (identical(this, other))
|
if (identical(this, other))
|
||||||
return true;
|
return true;
|
||||||
if (other is! AsyncSnapshot<T>)
|
if (runtimeType != other.runtimeType)
|
||||||
return false;
|
return false;
|
||||||
final AsyncSnapshot<T> typedOther = other;
|
final AsyncSnapshot<T> typedOther = other;
|
||||||
return connectionState == typedOther.connectionState
|
return connectionState == typedOther.connectionState
|
||||||
&& data == typedOther.data
|
&& hasData == typedOther.hasData
|
||||||
|
&& _data == typedOther._data
|
||||||
&& error == typedOther.error;
|
&& error == typedOther.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => hashValues(connectionState, data, error);
|
int get hashCode => hashValues(connectionState, hasData, _data, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature for strategies that build widgets based on asynchronous
|
/// Signature for strategies that build widgets based on asynchronous
|
||||||
@ -307,12 +342,12 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
|
|||||||
/// of the following snapshots that includes the last one (the one with
|
/// of the following snapshots that includes the last one (the one with
|
||||||
/// ConnectionState.done):
|
/// ConnectionState.done):
|
||||||
///
|
///
|
||||||
/// * `new AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
|
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
|
||||||
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
|
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
|
||||||
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
|
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
|
||||||
/// * ...
|
/// * ...
|
||||||
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
|
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
|
||||||
/// * `new AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
|
/// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
|
||||||
///
|
///
|
||||||
/// The actual sequence of invocations of the [builder] depends on the relative
|
/// 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
|
/// timing of events produced by the stream and the build rate of the Flutter
|
||||||
@ -329,7 +364,7 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
|
|||||||
///
|
///
|
||||||
/// The stream may produce errors, resulting in snapshots of the form:
|
/// The stream may produce errors, resulting in snapshots of the form:
|
||||||
///
|
///
|
||||||
/// * `new AsyncSnapshot<int>.withError(ConnectionState.active, 'some error')`
|
/// * `AsyncSnapshot<int>.withError(ConnectionState.active, 'some error')`
|
||||||
///
|
///
|
||||||
/// The data and error fields of snapshots produced are only changed when the
|
/// The data and error fields of snapshots produced are only changed when the
|
||||||
/// state is `ConnectionState.active`.
|
/// state is `ConnectionState.active`.
|
||||||
@ -337,7 +372,20 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
|
|||||||
/// The initial snapshot data can be controlled by specifying [initialData].
|
/// The initial snapshot data can be controlled by specifying [initialData].
|
||||||
/// This should be used to ensure that the first frame has the expected value,
|
/// 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
|
/// as the builder will always be called before the stream listener has a chance
|
||||||
/// to be processed.
|
/// 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)`
|
||||||
///
|
///
|
||||||
/// {@tool sample}
|
/// {@tool sample}
|
||||||
///
|
///
|
||||||
@ -348,6 +396,7 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// StreamBuilder<int>(
|
/// StreamBuilder<int>(
|
||||||
/// stream: _lot?.bids, // a Stream<int> or null
|
/// stream: _lot?.bids, // a Stream<int> or null
|
||||||
|
/// initialData: 100, // initial seed value
|
||||||
/// builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
|
/// builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
|
||||||
/// if (snapshot.hasError)
|
/// if (snapshot.hasError)
|
||||||
/// return Text('Error: ${snapshot.error}');
|
/// return Text('Error: ${snapshot.error}');
|
||||||
@ -372,42 +421,95 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
|
|||||||
// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1139 is fixed
|
// 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>> {
|
class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
|
||||||
/// Creates a new [StreamBuilder] that builds itself based on the latest
|
/// 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].
|
/// strategy is given by [builder].
|
||||||
///
|
///
|
||||||
/// The [initialData] is used to create the initial snapshot.
|
/// 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 [builder] must not be null.
|
/// The [builder] must not be null.
|
||||||
const StreamBuilder({
|
const StreamBuilder({
|
||||||
Key key,
|
Key key,
|
||||||
this.initialData,
|
@required T initialData,
|
||||||
Stream<T> stream,
|
Stream<T> stream,
|
||||||
@required this.builder,
|
@required this.builder,
|
||||||
}) : assert(builder != null),
|
}) : 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);
|
super(key: key, stream: stream);
|
||||||
|
|
||||||
/// The build strategy currently used by this builder.
|
/// The build strategy currently used by this builder.
|
||||||
final AsyncWidgetBuilder<T> 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.
|
/// The data that will be used to create the initial snapshot.
|
||||||
///
|
///
|
||||||
/// Providing this value (presumably obtained synchronously somehow when the
|
/// Providing this value (presumably obtained synchronously somehow when the
|
||||||
/// [Stream] was created) ensures that the first frame will show useful data.
|
/// [Stream] was created) ensures that the first frame will show useful data.
|
||||||
/// Otherwise, the first frame will be built with the value null, regardless
|
/// Otherwise, the first frame will be built with a snapshot that contains no
|
||||||
/// of whether a value is available on the stream: since streams are
|
/// data, regardless of whether a value is available on the stream: since
|
||||||
/// asynchronous, no events from the stream can be obtained before the initial
|
/// streams are asynchronous, no events from the stream can be obtained
|
||||||
/// build.
|
/// before the initial build.
|
||||||
final T initialData;
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AsyncSnapshot<T> initial() => AsyncSnapshot<T>.withData(ConnectionState.none, initialData);
|
AsyncSnapshot<T> initial() {
|
||||||
|
return hasInitialData
|
||||||
|
? AsyncSnapshot<T>.withData(ConnectionState.none, initialData)
|
||||||
|
: AsyncSnapshot<T>.withoutData(ConnectionState.none);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
|
AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
|
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
|
||||||
return AsyncSnapshot<T>.withData(ConnectionState.active, data);
|
return _TypeLiteral.isVoidType(T)
|
||||||
|
? AsyncSnapshot<T>.withoutData(ConnectionState.active)
|
||||||
|
: AsyncSnapshot<T>.withData(ConnectionState.active, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -455,23 +557,17 @@ class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
|
|||||||
///
|
///
|
||||||
/// ## Builder contract
|
/// ## Builder contract
|
||||||
///
|
///
|
||||||
/// For a future that completes successfully with data, assuming [initialData]
|
/// For a future that completes successfully with data, the [builder] will be
|
||||||
/// is null, the [builder] will be called with either both or only the latter of
|
/// called with either both or only the latter of the following snapshots:
|
||||||
/// the following snapshots:
|
|
||||||
///
|
///
|
||||||
/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
|
/// * `AsyncSnapshot<String>.withoutData(ConnectionState.waiting)`
|
||||||
/// * `new AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
|
/// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
|
||||||
///
|
///
|
||||||
/// If that same future instead completed with an error, the [builder] would be
|
/// If that same future instead completed with an error, the [builder] would be
|
||||||
/// called with either both or only the latter of:
|
/// called with either both or only the latter of:
|
||||||
///
|
///
|
||||||
/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
|
/// * `AsyncSnapshot<String>.withoutData(ConnectionState.waiting)`
|
||||||
/// * `new AsyncSnapshot<String>.withError(ConnectionState.done, 'some error')`
|
/// * `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
|
/// 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
|
/// state field transitions from `waiting` to `done`, and they will be retained
|
||||||
@ -479,8 +575,8 @@ class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
|
|||||||
/// old future has already completed successfully with data as above, changing
|
/// old future has already completed successfully with data as above, changing
|
||||||
/// configuration to a new future results in snapshot pairs of the form:
|
/// configuration to a new future results in snapshot pairs of the form:
|
||||||
///
|
///
|
||||||
/// * `new AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
|
/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
|
||||||
/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
|
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
|
||||||
///
|
///
|
||||||
/// In general, the latter will be produced only when the new future is
|
/// 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.
|
/// non-null, and the former only when the old future is non-null.
|
||||||
@ -489,6 +585,12 @@ class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
|
|||||||
/// `future?.asStream()`, except that snapshots with `ConnectionState.active`
|
/// `future?.asStream()`, except that snapshots with `ConnectionState.active`
|
||||||
/// may appear for the latter, depending on how the stream is implemented.
|
/// 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}
|
/// {@tool sample}
|
||||||
///
|
///
|
||||||
/// This sample shows a [FutureBuilder] configuring a text label to show the
|
/// This sample shows a [FutureBuilder] configuring a text label to show the
|
||||||
@ -497,7 +599,13 @@ class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
|
|||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// FutureBuilder<String>(
|
/// FutureBuilder<String>(
|
||||||
/// future: _calculation, // a previously-obtained Future<String> or null
|
/// // 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,
|
||||||
|
///
|
||||||
/// builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
/// builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
|
||||||
/// switch (snapshot.connectionState) {
|
/// switch (snapshot.connectionState) {
|
||||||
/// case ConnectionState.none:
|
/// case ConnectionState.none:
|
||||||
@ -520,11 +628,33 @@ class FutureBuilder<T> extends StatefulWidget {
|
|||||||
/// Creates a widget that builds itself based on the latest snapshot of
|
/// Creates a widget that builds itself based on the latest snapshot of
|
||||||
/// interaction with a [Future].
|
/// interaction with a [Future].
|
||||||
///
|
///
|
||||||
/// The [builder] must not be null.
|
/// 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.
|
||||||
const FutureBuilder({
|
const FutureBuilder({
|
||||||
Key key,
|
Key key,
|
||||||
this.future,
|
this.future,
|
||||||
this.initialData,
|
@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
|
||||||
@required this.builder,
|
@required this.builder,
|
||||||
}) : assert(builder != null),
|
}) : assert(builder != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
@ -533,7 +663,10 @@ class FutureBuilder<T> extends StatefulWidget {
|
|||||||
/// possibly null.
|
/// possibly null.
|
||||||
///
|
///
|
||||||
/// If no future has yet completed, including in the case where [future] is
|
/// If no future has yet completed, including in the case where [future] is
|
||||||
/// null, the data provided to the [builder] will be set to [initialData].
|
// 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.
|
||||||
final Future<T> future;
|
final Future<T> future;
|
||||||
|
|
||||||
/// The build strategy currently used by this builder.
|
/// The build strategy currently used by this builder.
|
||||||
@ -542,29 +675,56 @@ class FutureBuilder<T> extends StatefulWidget {
|
|||||||
/// [AsyncSnapshot.connectionState] property will be one of the following
|
/// [AsyncSnapshot.connectionState] property will be one of the following
|
||||||
/// values:
|
/// values:
|
||||||
///
|
///
|
||||||
/// * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will
|
/// * [ConnectionState.none]: [future] is null.
|
||||||
/// be set to [initialData], unless a future has previously completed, in
|
|
||||||
/// which case the previous result persists.
|
|
||||||
///
|
///
|
||||||
/// * [ConnectionState.waiting]: [future] is not null, but has not yet
|
/// If this widget was created with initial data (deprecated), then the
|
||||||
/// completed. The [AsyncSnapshot.data] will be set to [initialData],
|
/// [AsyncSnapshot.data] will be set to [initialData], unless a future has
|
||||||
/// unless a future has previously completed, in which case the previous
|
/// previously completed, in which case the previous result persists.
|
||||||
/// 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.done]: [future] is not null, and has completed. If the
|
/// * [ConnectionState.done]: [future] is not null, and has completed. If the
|
||||||
/// future completed successfully, the [AsyncSnapshot.data] will be set to
|
/// future completed successfully, the [AsyncSnapshot.data] will be set to
|
||||||
/// the value to which the future completed. If it completed with an error,
|
/// the value to which the future completed. If it completed with an error,
|
||||||
/// [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
|
/// [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
|
||||||
/// set to the error object.
|
/// 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;
|
final AsyncWidgetBuilder<T> builder;
|
||||||
|
|
||||||
/// The data that will be used to create the snapshots provided until a
|
/// The data that will be used to create the snapshots provided until a
|
||||||
/// non-null [future] has completed.
|
/// non-null [future] has completed.
|
||||||
///
|
///
|
||||||
/// If the future completes with an error, the data in the [AsyncSnapshot]
|
/// If the future completes with an error, the [AsyncSnapshot] provided to
|
||||||
/// provided to the [builder] will become null, regardless of [initialData].
|
/// the [builder] will contain no data, regardless of [initialData]. (The
|
||||||
/// (The error itself will be available in [AsyncSnapshot.error], and
|
/// error itself will be available in [AsyncSnapshot.error], and
|
||||||
/// [AsyncSnapshot.hasError] will be true.)
|
/// [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;
|
final T initialData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -582,7 +742,11 @@ class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
|
// 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);
|
||||||
_subscribe();
|
_subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,7 +778,9 @@ class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
|
|||||||
widget.future.then<void>((T data) {
|
widget.future.then<void>((T data) {
|
||||||
if (_activeCallbackIdentity == callbackIdentity) {
|
if (_activeCallbackIdentity == callbackIdentity) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
|
_snapshot = _TypeLiteral.isVoidType(T)
|
||||||
|
? AsyncSnapshot<T>.withoutData(ConnectionState.done)
|
||||||
|
: AsyncSnapshot<T>.withData(ConnectionState.done, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, onError: (Object error) {
|
}, onError: (Object error) {
|
||||||
@ -632,3 +798,33 @@ class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
|
|||||||
_activeCallbackIdentity = null;
|
_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;
|
||||||
|
}
|
||||||
|
@ -12,23 +12,33 @@ void main() {
|
|||||||
return Text(snapshot.toString(), textDirection: TextDirection.ltr);
|
return Text(snapshot.toString(), textDirection: TextDirection.ltr);
|
||||||
}
|
}
|
||||||
group('AsyncSnapshot', () {
|
group('AsyncSnapshot', () {
|
||||||
test('requiring data succeeds if data is present', () {
|
test('data succeeds if data is present', () {
|
||||||
expect(
|
const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withData(ConnectionState.done, 'hello');
|
||||||
const AsyncSnapshot<String>.withData(ConnectionState.done, 'hello').requireData,
|
expect(snapshot.hasData, isTrue);
|
||||||
'hello',
|
expect(snapshot.data, 'hello');
|
||||||
);
|
expect(snapshot.hasError, isFalse);
|
||||||
|
expect(snapshot.error, isNull);
|
||||||
});
|
});
|
||||||
test('requiring data fails if there is an error', () {
|
test('data throws if there is an error', () {
|
||||||
expect(
|
const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withError(ConnectionState.done, 'error');
|
||||||
() => const AsyncSnapshot<String>.withError(ConnectionState.done, 'error').requireData,
|
expect(snapshot.hasData, isFalse);
|
||||||
throwsA(equals('error')),
|
expect(() => snapshot.data, throwsA(equals('error')));
|
||||||
);
|
expect(snapshot.hasError, isTrue);
|
||||||
|
expect(snapshot.error, 'error');
|
||||||
});
|
});
|
||||||
test('requiring data fails if snapshot has neither data nor error', () {
|
test('data throws if created without data', () {
|
||||||
expect(
|
const AsyncSnapshot<String> snapshot = AsyncSnapshot<String>.withoutData(ConnectionState.none);
|
||||||
() => const AsyncSnapshot<String>.nothing().requireData,
|
expect(snapshot.hasData, isFalse);
|
||||||
throwsStateError,
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group('Async smoke tests', () {
|
group('Async smoke tests', () {
|
||||||
@ -40,7 +50,7 @@ void main() {
|
|||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
});
|
});
|
||||||
testWidgets('StreamBuilder', (WidgetTester tester) async {
|
testWidgets('StreamBuilder', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
stream: Stream<String>.fromIterable(<String>['hello', 'world']),
|
stream: Stream<String>.fromIterable(<String>['hello', 'world']),
|
||||||
builder: snapshotText,
|
builder: snapshotText,
|
||||||
));
|
));
|
||||||
@ -59,12 +69,12 @@ void main() {
|
|||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: null, builder: snapshotText,
|
key: key, future: null, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
|
||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: completer.future, builder: snapshotText,
|
key: key, future: completer.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('gracefully handles transition to null future', (WidgetTester tester) async {
|
testWidgets('gracefully handles transition to null future', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
@ -72,14 +82,14 @@ void main() {
|
|||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: completer.future, builder: snapshotText,
|
key: key, future: completer.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: null, builder: snapshotText,
|
key: key, future: null, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
|
||||||
completer.complete('hello');
|
completer.complete('hello');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('gracefully handles transition to other future', (WidgetTester tester) async {
|
testWidgets('gracefully handles transition to other future', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
@ -88,125 +98,132 @@ void main() {
|
|||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: completerA.future, builder: snapshotText,
|
key: key, future: completerA.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key, future: completerB.future, builder: snapshotText,
|
key: key, future: completerB.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
completerB.complete('B');
|
completerB.complete('B');
|
||||||
completerA.complete('A');
|
completerA.complete('A');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, B, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: B)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async {
|
testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async {
|
||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
future: completer.future, builder: snapshotText,
|
future: completer.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
completer.complete('hello');
|
completer.complete('hello');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: hello)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async {
|
testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async {
|
||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
future: completer.future, builder: snapshotText,
|
future: completer.future, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
completer.completeError('bad');
|
completer.completeError('bad');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, error: bad)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
|
testWidgets('produces snapshot with null data for null-completing data Future', (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>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(FutureBuilder<String>(
|
await tester.pumpWidget(FutureBuilder<String>(
|
||||||
key: key,
|
future: completer.future, builder: snapshotText,
|
||||||
future: completer.future,
|
|
||||||
builder: snapshotText,
|
|
||||||
initialData: 'Ignored',
|
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group('StreamBuilder', () {
|
group('StreamBuilder', () {
|
||||||
testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
|
testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: null, builder: snapshotText,
|
key: key, stream: null, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
|
||||||
final StreamController<String> controller = StreamController<String>();
|
final StreamController<String> controller = StreamController<String>();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: controller.stream, builder: snapshotText,
|
key: key, stream: controller.stream, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
|
testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
final StreamController<String> controller = StreamController<String>();
|
final StreamController<String> controller = StreamController<String>();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: controller.stream, builder: snapshotText,
|
key: key, stream: controller.stream, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: null, builder: snapshotText,
|
key: key, stream: null, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
|
testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
final StreamController<String> controllerA = StreamController<String>();
|
final StreamController<String> controllerA = StreamController<String>();
|
||||||
final StreamController<String> controllerB = StreamController<String>();
|
final StreamController<String> controllerB = StreamController<String>();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: controllerA.stream, builder: snapshotText,
|
key: key, stream: controllerA.stream, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: controllerB.stream, builder: snapshotText,
|
key: key, stream: controllerB.stream, builder: snapshotText,
|
||||||
));
|
));
|
||||||
controllerB.add('B');
|
controllerB.add('B');
|
||||||
controllerA.add('A');
|
controllerA.add('A');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, B, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, data: B)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async {
|
testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
final StreamController<String> controller = StreamController<String>();
|
final StreamController<String> controller = StreamController<String>();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>.withoutInitialData(
|
||||||
key: key, stream: controller.stream, builder: snapshotText,
|
key: key, stream: controller.stream, builder: snapshotText,
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsOneWidget);
|
||||||
controller.add('1');
|
controller.add('1');
|
||||||
controller.add('2');
|
controller.add('2');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, 2, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, data: 2)'), findsOneWidget);
|
||||||
controller.add('3');
|
controller.add('3');
|
||||||
controller.addError('bad');
|
controller.addError('bad');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, null, bad)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.active, error: bad)'), findsOneWidget);
|
||||||
controller.add('4');
|
controller.add('4');
|
||||||
controller.close();
|
controller.close();
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, 4, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: 4)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
|
testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
|
||||||
final StreamController<String> controller = StreamController<String>();
|
final StreamController<String> controller = StreamController<String>();
|
||||||
@ -215,7 +232,7 @@ void main() {
|
|||||||
builder: snapshotText,
|
builder: snapshotText,
|
||||||
initialData: 'I',
|
initialData: 'I',
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, data: I)'), findsOneWidget);
|
||||||
});
|
});
|
||||||
testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
|
testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
@ -225,7 +242,7 @@ void main() {
|
|||||||
builder: snapshotText,
|
builder: snapshotText,
|
||||||
initialData: 'I',
|
initialData: 'I',
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, data: I)'), findsOneWidget);
|
||||||
final StreamController<String> controller = StreamController<String>();
|
final StreamController<String> controller = StreamController<String>();
|
||||||
await tester.pumpWidget(StreamBuilder<String>(
|
await tester.pumpWidget(StreamBuilder<String>(
|
||||||
key: key,
|
key: key,
|
||||||
@ -233,7 +250,69 @@ void main() {
|
|||||||
builder: snapshotText,
|
builder: snapshotText,
|
||||||
initialData: 'Ignored',
|
initialData: 'Ignored',
|
||||||
));
|
));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () {
|
group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () {
|
||||||
@ -241,48 +320,30 @@ void main() {
|
|||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(Column(children: <Widget>[
|
await tester.pumpWidget(Column(children: <Widget>[
|
||||||
FutureBuilder<String>(future: completer.future, builder: snapshotText),
|
FutureBuilder<String>(future: completer.future, builder: snapshotText),
|
||||||
StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
|
StreamBuilder<String>.withoutInitialData(stream: completer.future.asStream(), builder: snapshotText),
|
||||||
]));
|
]));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsNWidgets(2));
|
||||||
completer.complete('hello');
|
completer.complete('hello');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsNWidgets(2));
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, data: hello)'), findsNWidgets(2));
|
||||||
});
|
});
|
||||||
testWidgets('when completing with error', (WidgetTester tester) async {
|
testWidgets('when completing with error', (WidgetTester tester) async {
|
||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
await tester.pumpWidget(Column(children: <Widget>[
|
await tester.pumpWidget(Column(children: <Widget>[
|
||||||
FutureBuilder<String>(future: completer.future, builder: snapshotText),
|
FutureBuilder<String>(future: completer.future, builder: snapshotText),
|
||||||
StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
|
StreamBuilder<String>.withoutInitialData(stream: completer.future.asStream(), builder: snapshotText),
|
||||||
]));
|
]));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting)'), findsNWidgets(2));
|
||||||
completer.completeError('bad');
|
completer.completeError('bad');
|
||||||
await eventFiring(tester);
|
await eventFiring(tester);
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsNWidgets(2));
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, error: bad)'), findsNWidgets(2));
|
||||||
});
|
});
|
||||||
testWidgets('when Future is null', (WidgetTester tester) async {
|
testWidgets('when Future is null', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(Column(children: <Widget>[
|
await tester.pumpWidget(Column(children: <Widget>[
|
||||||
FutureBuilder<String>(future: null, builder: snapshotText),
|
FutureBuilder<String>(future: null, builder: snapshotText),
|
||||||
StreamBuilder<String>(stream: null, builder: snapshotText),
|
StreamBuilder<String>.withoutInitialData(stream: null, builder: snapshotText),
|
||||||
]));
|
]));
|
||||||
expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsNWidgets(2));
|
expect(find.text('AsyncSnapshot<String>(ConnectionState.none)'), 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', () {
|
group('StreamBuilderBase', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user