diff --git a/packages/flutter/lib/src/painting/_network_image_io.dart b/packages/flutter/lib/src/painting/_network_image_io.dart index 68bed3a9927..f95d267337e 100644 --- a/packages/flutter/lib/src/painting/_network_image_io.dart +++ b/packages/flutter/lib/src/painting/_network_image_io.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'binding.dart'; @@ -171,7 +172,7 @@ class NetworkImage extends image_provider.ImageProvider Object.hash(url, scale, headers); + int get hashCode => Object.hash(url, scale, const MapEquality().hash(headers)); @override String toString() => diff --git a/packages/flutter/lib/src/painting/_network_image_web.dart b/packages/flutter/lib/src/painting/_network_image_web.dart index 6eb02a70bd2..773968a1146 100644 --- a/packages/flutter/lib/src/painting/_network_image_web.dart +++ b/packages/flutter/lib/src/painting/_network_image_web.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:ui' as ui; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import '../web.dart' as web; @@ -250,7 +251,12 @@ class NetworkImage extends image_provider.ImageProvider Object.hash(url, scale, webHtmlElementStrategy, headers); + int get hashCode => Object.hash( + url, + scale, + webHtmlElementStrategy, + const MapEquality().hash(headers), + ); @override String toString() => diff --git a/packages/flutter/test/painting/image_provider_network_image_test.dart b/packages/flutter/test/painting/image_provider_network_image_test.dart index 0aea42f2542..a23c0c66ba7 100644 --- a/packages/flutter/test/painting/image_provider_network_image_test.dart +++ b/packages/flutter/test/painting/image_provider_network_image_test.dart @@ -288,6 +288,51 @@ void main() { debugNetworkImageHttpClientProvider = null; }, skip: isBrowser); // [intended] Browser does not resolve images this way. + test('Network image with same arguments uses cache', () async { + final _FakeHttpClient mockHttpClient = _FakeHttpClient(); + debugNetworkImageHttpClientProvider = () => mockHttpClient; + mockHttpClient.request.response + ..statusCode = HttpStatus.ok + ..contentLength = kTransparentImage.length + ..content = [Uint8List.fromList(kTransparentImage)]; + + final Completer imageAvailable1 = Completer(); + final ImageProvider imageProvider1 = NetworkImage( + nonconst('testing.url'), + headers: nonconst({'key': 'value'}), + ); + expect(imageCache.liveImageCount, 0); + + final ImageStream result1 = imageProvider1.resolve(ImageConfiguration.empty); + result1.addListener( + ImageStreamListener((ImageInfo image, bool synchronousCall) { + imageAvailable1.complete(); + }), + ); + await imageAvailable1.future; + expect(imageCache.liveImageCount, 1); + + // NetworkImage should not make another request. + mockHttpClient.request.response.statusCode = HttpStatus.badRequest; + + final Completer imageAvailable2 = Completer(); + final ImageProvider imageProvider2 = NetworkImage( + nonconst('testing.url'), + headers: nonconst({'key': 'value'}), + ); + + final ImageStream result2 = imageProvider2.resolve(ImageConfiguration.empty); + result2.addListener( + ImageStreamListener((ImageInfo image, bool synchronousCall) { + imageAvailable2.complete(); + }), + ); + await imageAvailable2.future; + expect(imageCache.liveImageCount, 1); + + debugNetworkImageHttpClientProvider = null; + }, skip: isBrowser); // [intended] Browser does not resolve images this way. + test('Network image sets tag', () async { const String url = 'http://test.png'; const int chunkSize = 8; @@ -340,6 +385,9 @@ class _FakeHttpClient extends Fake implements HttpClient { class _FakeHttpClientRequest extends Fake implements HttpClientRequest { final _FakeHttpClientResponse response = _FakeHttpClientResponse(); + @override + final HttpHeaders headers = _FakeHttpHeaders(); + @override Future close() async { return response; @@ -379,3 +427,8 @@ class _FakeHttpClientResponse extends Fake implements HttpClientResponse { return futureValue ?? futureValue as E; // Mirrors the implementation in Stream. } } + +class _FakeHttpHeaders extends Fake implements HttpHeaders { + @override + void add(String name, Object value, {bool preserveHeaderCase = false}) {} +}