Skip to content

Commit 9e314ff

Browse files
authored
Make FutureBuilder handle SynchronousFuture correctly, reland SynchronousFuture usage in test assets (#115173)
* Make FutureBuilder handle SynchronousFuture correctly * Reland "Load assets in flutter_test without turning event loop. (#115123)" (#115156)" This reverts commit 3895786.
1 parent 0e9ee36 commit 9e314ff

File tree

6 files changed

+50
-14
lines changed

6 files changed

+50
-14
lines changed

packages/flutter/lib/src/foundation/synchronous_future.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ class SynchronousFuture<T> implements Future<T> {
3838

3939
@override
4040
Future<R> then<R>(FutureOr<R> Function(T value) onValue, { Function? onError }) {
41-
final dynamic result = onValue(_value);
41+
final FutureOr<R> result = onValue(_value);
4242
if (result is Future<R>) {
4343
return result;
4444
}
45-
return SynchronousFuture<R>(result as R);
45+
return SynchronousFuture<R>(result);
4646
}
4747

4848
@override

packages/flutter/lib/src/services/asset_bundle.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,17 +247,24 @@ abstract class CachingAssetBundle extends AssetBundle {
247247
/// An [AssetBundle] that loads resources using platform messages.
248248
class PlatformAssetBundle extends CachingAssetBundle {
249249
@override
250-
Future<ByteData> load(String key) async {
250+
Future<ByteData> load(String key) {
251251
final Uint8List encoded = utf8.encoder.convert(Uri(path: Uri.encodeFull(key)).path);
252-
final ByteData? asset =
253-
await ServicesBinding.instance.defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData());
254-
if (asset == null) {
252+
final Future<ByteData>? future = ServicesBinding.instance.defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData())?.then((ByteData? asset) {
253+
if (asset == null) {
254+
throw FlutterError.fromParts(<DiagnosticsNode>[
255+
_errorSummaryWithKey(key),
256+
ErrorDescription('The asset does not exist or has empty data.'),
257+
]);
258+
}
259+
return asset;
260+
});
261+
if (future == null) {
255262
throw FlutterError.fromParts(<DiagnosticsNode>[
256-
_errorSummaryWithKey(key),
257-
ErrorDescription('The asset does not exist or has empty data.'),
258-
]);
263+
_errorSummaryWithKey(key),
264+
ErrorDescription('The asset does not exist or has empty data.'),
265+
]);
259266
}
260-
return asset;
267+
return future;
261268
}
262269

263270
@override

packages/flutter/lib/src/widgets/async.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,11 @@ class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
645645
}());
646646

647647
});
648-
_snapshot = _snapshot.inState(ConnectionState.waiting);
648+
// An implementation like `SynchronousFuture` may have already called the
649+
// .then closure. Do not overwrite it in that case.
650+
if (_snapshot.connectionState != ConnectionState.done) {
651+
_snapshot = _snapshot.inState(ConnectionState.waiting);
652+
}
649653
}
650654
}
651655

packages/flutter/test/widgets/async_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66

7+
import 'package:flutter/foundation.dart';
78
import 'package:flutter/widgets.dart';
89
import 'package:flutter_test/flutter_test.dart';
910

@@ -87,6 +88,20 @@ void main() {
8788
});
8889
});
8990
group('FutureBuilder', () {
91+
testWidgets('gives expected snapshot with SynchronousFuture', (WidgetTester tester) async {
92+
final SynchronousFuture<String> future = SynchronousFuture<String>('flutter');
93+
await tester.pumpWidget(FutureBuilder<String>(
94+
future: future,
95+
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
96+
expect(snapshot.connectionState, ConnectionState.done);
97+
expect(snapshot.data, 'flutter');
98+
expect(snapshot.error, null);
99+
expect(snapshot.stackTrace, null);
100+
return const Placeholder();
101+
},
102+
));
103+
});
104+
90105
testWidgets('gracefully handles transition from null future', (WidgetTester tester) async {
91106
final GlobalKey key = GlobalKey();
92107
await tester.pumpWidget(FutureBuilder<String>(

packages/flutter_test/lib/src/_binding_io.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
88

9+
import 'package:flutter/foundation.dart';
910
import 'package:flutter/services.dart';
10-
import 'package:flutter/widgets.dart';
1111
import 'package:path/path.dart' as path;
1212
// ignore: deprecated_member_use
1313
import 'package:test_api/test_api.dart' as test_package;
@@ -42,7 +42,7 @@ void mockFlutterAssets() {
4242
/// platform messages.
4343
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {});
4444

45-
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async {
45+
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) {
4646
assert(message != null);
4747
String key = utf8.decode(message!.buffer.asUint8List());
4848
File asset = File(path.join(assetFolderPath, key));
@@ -62,7 +62,7 @@ void mockFlutterAssets() {
6262
}
6363

6464
final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync());
65-
return Future<ByteData>.value(encoded.buffer.asByteData());
65+
return SynchronousFuture<ByteData>(encoded.buffer.asByteData());
6666
});
6767
}
6868

packages/flutter_test/test/bindings_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import 'dart:async';
1111
import 'dart:io';
1212

13+
import 'package:flutter/services.dart';
1314
import 'package:flutter/widgets.dart';
1415
import 'package:flutter_test/flutter_test.dart';
1516

@@ -90,4 +91,13 @@ void main() {
9091
binding.idle();
9192
});
9293
});
94+
95+
testWidgets('Assets in the tester can be loaded without turning event loop', (WidgetTester tester) async {
96+
bool responded = false;
97+
// The particular asset does not matter, as long as it exists.
98+
rootBundle.load('AssetManifest.json').then((ByteData data) {
99+
responded = true;
100+
});
101+
expect(responded, true);
102+
});
93103
}

0 commit comments

Comments
 (0)