Use MultiFrameImageStreamProvider in the various image providers. (#12997)
diff --git a/dev/manual_tests/test/card_collection_test.dart b/dev/manual_tests/test/card_collection_test.dart index 93cb397..428e30e 100644 --- a/dev/manual_tests/test/card_collection_test.dart +++ b/dev/manual_tests/test/card_collection_test.dart
@@ -3,12 +3,15 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show createHttpClient; import 'package:flutter_test/flutter_test.dart'; import '../lib/card_collection.dart' as card_collection; +import 'mock_image_http.dart'; void main() { testWidgets('Card Collection smoke test', (WidgetTester tester) async { + createHttpClient = createMockImageHttpClient; card_collection.main(); // builds the app and schedules a frame but doesn't trigger one await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // triggers a frame
diff --git a/dev/manual_tests/test/color_testing_demo_test.dart b/dev/manual_tests/test/color_testing_demo_test.dart index c00f64c..1d6b2d4 100644 --- a/dev/manual_tests/test/color_testing_demo_test.dart +++ b/dev/manual_tests/test/color_testing_demo_test.dart
@@ -3,12 +3,16 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show createHttpClient; import 'package:flutter_test/flutter_test.dart'; import '../lib/color_testing_demo.dart' as color_testing_demo; +import 'mock_image_http.dart'; void main() { + testWidgets('Color testing demo smoke test', (WidgetTester tester) async { + createHttpClient = createMockImageHttpClient; color_testing_demo.main(); // builds the app and schedules a frame but doesn't trigger one await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 await tester.pump(); // triggers a frame
diff --git a/dev/manual_tests/test/mock_image_http.dart b/dev/manual_tests/test/mock_image_http.dart new file mode 100644 index 0000000..b491bd9 --- /dev/null +++ b/dev/manual_tests/test/mock_image_http.dart
@@ -0,0 +1,15 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart' show ValueGetter; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart' as http; + +import '../../../packages/flutter/test/services/image_data.dart'; + +// Returns a mock HTTP client that responds with an image to all requests. +ValueGetter<http.Client> createMockImageHttpClient = () { + return new http.MockClient((http.BaseRequest request) { + return new Future<http.Response>.value( + new http.Response.bytes(kTransparentImage, 200, request: request) + ); + }); +};
diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart index e92719a..fa70780 100644 --- a/packages/flutter/lib/src/services/image_provider.dart +++ b/packages/flutter/lib/src/services/image_provider.dart
@@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:io' show File; import 'dart:typed_data'; -import 'dart:ui' as ui show Image; +import 'dart:ui' as ui show instantiateImageCodec, Codec; import 'dart:ui' show Size, Locale, TextDirection, hashValues; import 'package:flutter/foundation.dart'; @@ -14,7 +14,6 @@ import 'asset_bundle.dart'; import 'http_client.dart'; import 'image_cache.dart'; -import 'image_decoder.dart'; import 'image_stream.dart'; /// Configuration information passed to the [ImageProvider.resolve] method to @@ -365,8 +364,9 @@ /// image using [loadAsync]. @override ImageStreamCompleter load(AssetBundleImageKey key) { - return new OneFrameImageStreamCompleter( - loadAsync(key), + return new MultiFrameImageStreamCompleter( + codec: _loadAsync(key), + scale: key.scale, informationCollector: (StringBuffer information) { information.writeln('Image provider: $this'); information.write('Image key: $key'); @@ -379,23 +379,11 @@ /// /// This function is used by [load]. @protected - Future<ImageInfo> loadAsync(AssetBundleImageKey key) async { + Future<ui.Codec> _loadAsync(AssetBundleImageKey key) async { final ByteData data = await key.bundle.load(key.name); if (data == null) throw 'Unable to read data'; - final ui.Image image = await decodeImage(data); - if (image == null) - throw 'Unable to decode image data'; - return new ImageInfo(image: image, scale: key.scale); - } - - /// Converts raw image data from a [ByteData] buffer into a decoded - /// [ui.Image] which can be passed to a [Canvas]. - /// - /// By default, this just uses [decodeImageFromList]. This method could be - /// overridden in subclasses (e.g. for testing). - Future<ui.Image> decodeImage(ByteData data) { - return decodeImageFromList(data.buffer.asUint8List()); + return await ui.instantiateImageCodec(data.buffer.asUint8List()); } } @@ -426,8 +414,9 @@ @override ImageStreamCompleter load(NetworkImage key) { - return new OneFrameImageStreamCompleter( - _loadAsync(key), + return new MultiFrameImageStreamCompleter( + codec: _loadAsync(key), + scale: key.scale, informationCollector: (StringBuffer information) { information.writeln('Image provider: $this'); information.write('Image key: $key'); @@ -437,26 +426,19 @@ static final http.Client _httpClient = createHttpClient(); - Future<ImageInfo> _loadAsync(NetworkImage key) async { + Future<ui.Codec> _loadAsync(NetworkImage key) async { assert(key == this); final Uri resolved = Uri.base.resolve(key.url); final http.Response response = await _httpClient.get(resolved); if (response == null || response.statusCode != 200) - return null; + throw new Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved'); final Uint8List bytes = response.bodyBytes; if (bytes.lengthInBytes == 0) - return null; + throw new Exception('NetworkImage is an empty file: $resolved'); - final ui.Image image = await decodeImageFromList(bytes); - if (image == null) - return null; - - return new ImageInfo( - image: image, - scale: key.scale, - ); + return await ui.instantiateImageCodec(bytes); } @override @@ -498,29 +480,23 @@ @override ImageStreamCompleter load(FileImage key) { - return new OneFrameImageStreamCompleter( - _loadAsync(key), + return new MultiFrameImageStreamCompleter( + codec: _loadAsync(key), + scale: key.scale, informationCollector: (StringBuffer information) { information.writeln('Path: ${file?.path}'); } ); } - Future<ImageInfo> _loadAsync(FileImage key) async { + Future<ui.Codec> _loadAsync(FileImage key) async { assert(key == this); final Uint8List bytes = await file.readAsBytes(); if (bytes.lengthInBytes == 0) return null; - final ui.Image image = await decodeImageFromList(bytes); - if (image == null) - return null; - - return new ImageInfo( - image: image, - scale: key.scale, - ); + return await ui.instantiateImageCodec(bytes); } @override @@ -568,20 +544,16 @@ @override ImageStreamCompleter load(MemoryImage key) { - return new OneFrameImageStreamCompleter(_loadAsync(key)); + return new MultiFrameImageStreamCompleter( + codec: _loadAsync(key), + scale: key.scale + ); } - Future<ImageInfo> _loadAsync(MemoryImage key) async { + Future<ui.Codec> _loadAsync(MemoryImage key) { assert(key == this); - final ui.Image image = await decodeImageFromList(bytes); - if (image == null) - return null; - - return new ImageInfo( - image: image, - scale: key.scale, - ); + return ui.instantiateImageCodec(bytes); } @override
diff --git a/packages/flutter/lib/src/services/image_stream.dart b/packages/flutter/lib/src/services/image_stream.dart index a802ae0..91843ba 100644 --- a/packages/flutter/lib/src/services/image_stream.dart +++ b/packages/flutter/lib/src/services/image_stream.dart
@@ -365,6 +365,8 @@ void _handleCodecReady(ui.Codec codec) { _codec = codec; + assert(_codec != null); + _decodeNextFrameAndSchedule(); }
diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart index 02f9ced..301d221 100644 --- a/packages/flutter/test/widgets/image_resolution_test.dart +++ b/packages/flutter/test/widgets/image_resolution_test.dart
@@ -88,24 +88,27 @@ String toString() => '${describeIdentity(this)}()'; } +class FakeImageStreamCompleter extends ImageStreamCompleter { + FakeImageStreamCompleter(Future<ImageInfo> image) { + image.then<Null>(setImage); + } +} + class TestAssetImage extends AssetImage { TestAssetImage(String name) : super(name); @override - Future<ImageInfo> loadAsync(AssetBundleImageKey key) { - ImageInfo result; + ImageStreamCompleter load(AssetBundleImageKey key) { + ImageInfo imageInfo; key.bundle.load(key.name).then<Null>((ByteData data) { - decodeImage(data).then<Null>((ui.Image image) { - result = new ImageInfo(image: image, scale: key.scale); - }); + final TestByteData testData = data; + final ui.Image image = new TestImage(testData.scale); + imageInfo = new ImageInfo(image: image, scale: key.scale); }); - assert(result != null); - return new SynchronousFuture<ImageInfo>(result); - } - - @override - Future<ui.Image> decodeImage(covariant TestByteData data) { - return new SynchronousFuture<ui.Image>(new TestImage(data.scale)); + assert(imageInfo != null); + return new FakeImageStreamCompleter( + new SynchronousFuture<ImageInfo>(imageInfo) + ); } }