Using custom exception class for network loading error (#35335)

Using custom exception class for network loading error
diff --git a/packages/flutter/lib/src/painting/_network_image_io.dart b/packages/flutter/lib/src/painting/_network_image_io.dart
index 5f9129b..2b2c4a7 100644
--- a/packages/flutter/lib/src/painting/_network_image_io.dart
+++ b/packages/flutter/lib/src/painting/_network_image_io.dart
@@ -87,7 +87,7 @@
       });
       final HttpClientResponse response = await request.close();
       if (response.statusCode != HttpStatus.ok)
-        throw Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved');
+        throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
 
       final Uint8List bytes = await consolidateHttpClientResponseBytes(
         response,
diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart
index 4f16b93..a08e4c9 100644
--- a/packages/flutter/lib/src/painting/image_provider.dart
+++ b/packages/flutter/lib/src/painting/image_provider.dart
@@ -780,3 +780,25 @@
     );
   }
 }
+
+/// The exception thrown when the HTTP request to load a network image fails.
+class NetworkImageLoadException implements Exception {
+  /// Creates a [NetworkImageLoadException] with the specified http status
+  /// [code] and the [uri]
+  NetworkImageLoadException({@required this.statusCode, @required this.uri})
+      : assert(uri != null),
+        assert(statusCode != null),
+        _message = 'HTTP request failed, statusCode: $statusCode, $uri';
+
+  /// The HTTP status code from the server.
+  final int statusCode;
+
+  /// A human-readable error message.
+  final String _message;
+
+  /// Resolved URI of the requested image.
+  final Uri uri;
+
+  @override
+  String toString() => _message;
+}
diff --git a/packages/flutter/test/painting/image_provider_test.dart b/packages/flutter/test/painting/image_provider_test.dart
index 656edfc..e485eec 100644
--- a/packages/flutter/test/painting/image_provider_test.dart
+++ b/packages/flutter/test/painting/image_provider_test.dart
@@ -11,6 +11,7 @@
 import 'package:flutter/painting.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:mockito/mockito.dart';
+import 'package:test_api/test_api.dart' show TypeMatcher;
 
 import '../rendering/rendering_tester.dart';
 import 'image_data.dart';
@@ -154,6 +155,32 @@
         debugNetworkImageHttpClientProvider = null;
       });
 
+      test('Expect thrown exception with statusCode', () async {
+        final int errorStatusCode = HttpStatus.notFound;
+        const String requestUrl = 'foo-url';
+
+        final MockHttpClientRequest request = MockHttpClientRequest();
+        final MockHttpClientResponse response = MockHttpClientResponse();
+        when(httpClient.getUrl(any)).thenAnswer((_) => Future<HttpClientRequest>.value(request));
+        when(request.close()).thenAnswer((_) => Future<HttpClientResponse>.value(response));
+        when(response.statusCode).thenReturn(errorStatusCode);
+
+        final Completer<dynamic> caughtError = Completer<dynamic>();
+
+        final ImageProvider imageProvider = NetworkImage(nonconst(requestUrl));
+        final ImageStream result = imageProvider.resolve(ImageConfiguration.empty);
+        result.addListener(ImageStreamListener((ImageInfo info, bool syncCall) {
+        }, onError: (dynamic error, StackTrace stackTrace) {
+          caughtError.complete(error);
+        }));
+
+        final dynamic err = await caughtError.future;
+        expect(err, const TypeMatcher<NetworkImageLoadException>()
+            .having((NetworkImageLoadException e) => e.statusCode, 'statusCode', errorStatusCode)
+            .having((NetworkImageLoadException e) => e.uri, 'uri', Uri.base.resolve(requestUrl))
+        );
+      });
+
       test('Disallows null urls', () {
         expect(() {
           NetworkImage(nonconst(null));