74

How to handle Image.network when the url is wrong or the destination leads to 404.

for example try

Image.network('https://image.tmdb.org/t/p/w92')
Kamil Gosciminski
  • 16,547
  • 8
  • 49
  • 72
mohamed zayani
  • 943
  • 1
  • 6
  • 10

15 Answers15

115

I have handled the network image issue related to 404 by using an errorBuilder.

Image.network('Your image url...',
    errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
        return Text('Your error widget...');
    },
),

errorBuilder property

NirajPhutane
  • 1,586
  • 1
  • 12
  • 10
24

UPDATE: Look at the new accurate way which uses an inbuilt method, answered by @Niraj Phutane here which requires no plugins.


old answer (before errorbuilder existed)

I recommend using cached_network_image which gives an option to add a placeholder image and also an error widget in case of a 404 or 403.

CachedNetworkImage(
   imageUrl: "http://via.placeholder.com/350x150",
   placeholder: (context, url) => new CircularProgressIndicator(),
   errorWidget: (context, url, error) => new Icon(Icons.error),
),

Edit: If your app crashes when url is a 404, the faq has an entry regarding this, please file a bug report with a reproducible example to cached_network_image on github.

Phani Rithvij
  • 4,030
  • 3
  • 25
  • 60
15

You can display image from assets when you found 404 or any other error while loading image.

What I have done is:

  FadeInImage(
      image: NetworkImage("imageUrl"),
      placeholder: AssetImage(
          "assets/images/placeholder.jpg"),
      imageErrorBuilder:
          (context, error, stackTrace) {
        return Image.asset(
            'assets/images/error.jpg',
            fit: BoxFit.fitWidth);
      },
      fit: BoxFit.fitWidth,
    )

Check imageErrorBuilder property.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
10

Instead of Network.image use NetworkImageWithRetry

https://pub.dartlang.org/documentation/flutter_image/latest/

Example:

var avatar = Image(
  image: NetworkImageWithRetry('http://example.com/avatars/123.jpg'),
);
Linus Unnebäck
  • 23,234
  • 15
  • 74
  • 89
mohamed zayani
  • 943
  • 1
  • 6
  • 10
  • 1
    You don't need to use `new` keyword beginning with Dart 2.0 – CopsOnRoad Sep 30 '18 at 07:01
  • Thanks, this was just copy and paste code from flutter repo – mohamed zayani Oct 01 '18 at 08:08
  • 28
    What does `NetworkImageWithRetry` do when you have 404 error? – CopsOnRoad Oct 01 '18 at 13:25
  • By default just nothing. Mainly you won't get an exception and you end up with an empty image. – mohamed zayani Oct 02 '18 at 14:16
  • i am getting error "The method 'NetworkImageWithRetry' isn't defined.." – krupesh Anadkat Jan 12 '19 at 09:45
  • 3
    @krupeshAnadkat Add `flutter_image: ^1.0.0` to your `pubspek.yaml` file – Oleksii Masnyi Jan 21 '19 at 15:11
  • 1
    @krupesh-anadkat also add the import `import 'package:flutter_image/network.dart';` – Rodrigo João Bertotti Feb 21 '19 at 13:47
  • Doesnt work. Still throws an exception when the resource is not found – Jorge Aug 27 '20 at 22:41
  • 1
    @Jorge did you find any solution for crash when image not found 404. I. am facing the same issue, and app is crashing – Jen Jose Sep 19 '20 at 20:22
  • @JenJose Yes, kind of. I use this: static Future loadBytes(String url, {Map headers}) async { final Uri resolved = Uri.base.resolve(url); final Response response = await _httpClient.get(resolved, headers: headers); if (response == null || response.statusCode != 200) throw new Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved'); final Uint8List bytes = response.bodyBytes; if (bytes.lengthInBytes == 0) throw new Exception('NetworkImage is an empty file: $resolved'); return bytes; } Here you can catch – Jorge Sep 19 '20 at 21:57
  • You can catch the Exception. I call this from initState in statefulWidgets and set the image widget this way. The only problem is that I couldnt make it work in combination with precacheImage (which is great because it starts loading before and the app behaves better – Jorge Sep 19 '20 at 21:59
  • Thanks @Jorge , but I really need to cache images, otherwise, on scrolling it loads the images again and it seems to be less user-friendly. – Jen Jose Sep 20 '20 at 10:32
  • 1
    @JenJose For that have you considered AutomaticKeepAliveClientMixin? You add it to your wrapper class and it keeps the state. Or if its a ListView you can configure it to keep the state: But if you really need to use image caching (i.e. precacheImage) I am sure there is a way to combine precacheImage with byte loading. I was thinking about making a question about this exactly. – Jorge Sep 20 '20 at 18:29
9

Can use errorBuilder from Image.network or Image.asset

Image.network(
       path.image,
       width: 40,
       height: 40,
       errorBuilder: (BuildContext context, Object exception,
            StackTrace? stackTrace) {
         return const Text('');
       },
luis tundisi
  • 101
  • 1
  • 2
5

You can use ImageStream to handle errors.

class CustomProductImage extends StatefulWidget {
  final String image;

  const CustomProductImage(this.image);

  @override
  State<CustomProductImage> createState() => _CustomProductImageState();
}

class _CustomProductImageState extends State<CustomProductImage> {
  Widget mainWidget = const CircularProgressIndicator(); // Placeholder widget
  @override
  void initState() {
    super.initState();

    Image image = Image.network(widget.image, width: 50, height: 50);

    final ImageStream stream = image.image.resolve(ImageConfiguration.empty);

    stream.addListener(ImageStreamListener((info, call) {
      if (!completer) {
        completer = true;
        setState(() {
          mainWidget = image;
        });
      }
    }, onError: (dynamic exception, StackTrace? stackTrace) {
      print('Error is $exception , stack is $stackTrace');
      setState(() {
        mainWidget = const Icon(Icons.error); // Error Widget
      });
    }));
  }

  @override
  Widget build(BuildContext context) {
    return mainWidget;
  }
}
BIS Tech
  • 17,000
  • 12
  • 99
  • 148
3

It's weird but you can't easily handle errors correctly with Image.Provider. Use this handy package: https://pub.dev/packages/flutter_advanced_networkimage

Then in you code:

backgroundImage: AdvancedNetworkImage(
                    "YOUR IMAGE URL", 
                    fallbackAssetImage: "YOUR DEAULT ASSERT IMAGE eg:assets/default.png"
                  )
  • This is the only solution here that did not throw an error and hang my applicaiton. However in GitHub this package is marked as archived. Does anyone know why? It also fails to build on Flutter v1.2.x+ – Nicholas Pesa Aug 11 '20 at 22:25
  • 1
    Sadly I ran into this error: https://github.com/dnfield/flutter_svg/issues/368 when trying to use it with Flutter 1.20.2. I still have no clue how to catch ALL errors that happen during loading a network image. :-( – Andre Kreienbring Oct 23 '20 at 15:56
  • this package is discontinued! – ramzieus Feb 18 '23 at 15:19
2

I had the same problem, but, I solved it using the property 'imageErrorBuilder' of the FadeInImage class.

Here is a link about that: https://api.flutter.dev/flutter/widgets/Image/errorBuilder.html

The example was made for Image.network(), but it also work for FadeInImage.

Here's my code:

FadeInImage(
  imageErrorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
    print('Error Handler'); 
    return Container(
      width: 100.0,
      height: 100.0,
      child: Image.asset('assets/img/no_disponible.jpg'),
    );
  },
  placeholder: AssetImage('assets/img/loading.gif'), 
  image: NetworkImage(product.getImg()),
  fit: BoxFit.cover,
  height: 100.0,
  width: 100.0,
), 

The function of imageErrorBuilder will be execute for example, if the direction of the page doesn't exist.

I'm using Flutter 1.20.1

2

I used the basic Image widget and added an error builder for errors and a loadingBuilder for loading status and for the actual image I used NetworkImageWithRetry so that it will try to load the image many times and give you an error at the end if it couldn't load the image.

Image(
      image: NetworkImageWithRetry(
      'http://ImageUrl.png',),
      errorBuilder: (context, exception, stackTrack) => Icon(Icons.error,),
      loadingBuilder: (context, exception, stackTrack) => CircularProgressIndicator(),
    )
Khaled Mahmoud
  • 285
  • 2
  • 7
2

For those who are using firebase storage

The above methods didn't help me at all. When the object is not found in firebase, I got an exception "Null is not a type of List<int>". So I decided to verify if the Unit8List values are valid or not. If it's valid then we are ready to show the image otherwise use any widgets as placeholder.

Future<String?> getImgUrl()async{
//Storage structre: avatars/+919999999999/avatar.jpg
//Permanent url of an image without tokens
//%2F means "/"
//%2B mans "+"

String imgUrl = "https://firebasestorage.googleapis.com/v0/b/yourFIrebaseProjectID/o/avatars%2F%2B919999999999%2Favatar.jpg?alt=media";

    try {
      Uint8List bytes = (await NetworkAssetBundle(Uri.parse(imgUrl)).load(imgUrl)).buffer.asUint8List();
      print("The image exists!");
      return imgUrl;
    } catch (e) {
      print("Error: $e");
      return null;
    }
  }




 Widget futureBulder(){
    return FutureBuilder(
      future: getImgUrl(),
      builder: (BuildContext context, AsyncSnapshot<String?> snapshot) {
bool error = snapshot.data==null;
//I use NetworkImage for demonstration purpose, use it whatever widget u want
        return CircleAvatar(
         backgroundImage: error? null : NetworkImage(snapshot.data!),
        );
      },
    );
  }
Rajesh
  • 3,562
  • 7
  • 24
  • 43
2

I ran into this problem while learning flutter (not web). I found the solution after I tried all the non-working options that are given in this thread. They all print errors to the console if the address is wrong or the resource is not found. To play with my solution, you will need to install two plugins: http and url_launcher (the second plugin is only needed to open a link to this topic in the simulator). Be sure to read the up-to-date instructions for using plugins. How to play, you ask? Change the address in input Image URL field as you like! Please report bugs if you find them.

import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Image 404',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const _problemDescriptionUrl =
      'https://stackoverflow.com/questions/52568872/flutter-how-to-handle-image-network-error-like-404-or-wrong-url';
  String imageUrl =
      'https://repository-images.githubusercontent.com/90528830/018a6400-d879-11e9-92b4-492c61d9bc32';

  late Future<Widget> futureWidget;

  Future<Widget> fetchImage() async {
    if (imageUrl.startsWith('https://') &&
        Uri.tryParse(imageUrl) != null &&
        Uri.tryParse(imageUrl)!.hasAbsolutePath) {
      final response = await http.get(Uri.parse(imageUrl));
      if (response.statusCode == 200 &&
          (response.headers.keys.contains('content-type') &&
              !response.headers['content-type']!.contains('text/html'))) {
        // If the server did return a 200 OK response,
        // then parse the data.
        return Image.memory(response.bodyBytes, fit: BoxFit.cover);
      } else {
        // If the server did not return a 200 OK response,
        // then
        return const Text('Failed to load Image');
      }
    }
    return const Text('Failed to load Image');
  }

  @override
  void initState() {
    futureWidget = fetchImage();
    super.initState();
  }

  void _launchUrl() async {
    if (!await launchUrl(Uri.tryParse(_problemDescriptionUrl)!)) {
      throw 'Could not launch $_problemDescriptionUrl';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image.network Error 404 Solution'),
      ),
      body: Column(
        children: [
          TextButton(
              onPressed: _launchUrl,
              child: const Text('The problem described here')),
          Container(
            padding: const EdgeInsets.all(8),
            alignment: Alignment.center,
            width: double.infinity,
            height: 200,
            child: FutureBuilder<Widget>(
              future: futureWidget,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return snapshot.data!;
                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }
                // By default, show a loading spinner.
                return const CircularProgressIndicator();
              },
            ),
          ),
          Form(
            child: TextFormField(
              decoration: const InputDecoration(labelText: 'Image URL'),
              initialValue: imageUrl,
              maxLines: 3,
              keyboardType: TextInputType.url,
              textInputAction: TextInputAction.done,
              onChanged: (val) {
                setState(() {
                  imageUrl = val;
                  futureWidget = fetchImage();
                });
              },
            ),
          )
        ],
      ),
    );
  }
}
1

there what i was looking for, but its temporary... its ignore 404 error exception and returning null.

FlutterError.onError = (FlutterErrorDetails details) {
      if (details.library == 'image resource service') {
        return;
      }
      FirebaseCrashlytics.instance.recordFlutterFatalError(details);
    };

for more information errorBuilder throws exception for invalid image data #107416

Fedor
  • 17,146
  • 13
  • 40
  • 131
0

I have implemented this StatefulWidget for the same case, hope this helps!!!

    class NetworkImageHandlerViewer extends StatefulWidget {
      final String imageURL;
      bool errorFoundInImageLoad = false;
      NetworkImageHandlerViewer({
        Key key,
        @required this.imageURL,
      }) : super(key: key);

      @override
      _NetworkImageHandlerViewerState createState() =>
          _NetworkImageHandlerViewerState();
    }

    class _NetworkImageHandlerViewerState extends State<NetworkImageHandlerViewer> {
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            Container(
              height: 200,
              // height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              // color: Colors.black,
              child: (widget.errorFoundInImageLoad)
                  ? Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          "Unable To Fetch Image",
                        ),
                        IconButton(
                          iconSize: 40,
                          onPressed: () {
                            setState(() {
                              if (mounted) {
                                print("reloading image again");
                                setState(() {
                                  widget.errorFoundInImageLoad = false;
                                });
                              }
                            });
                          },
                          icon: Icon(Icons.refresh),
                        ),
                        Text(
                          "Tap Refresh Icon To Reload!!!",
                        ),
                      ],
                    )
                  : Image.network(
                      // widget.question.fileInfo[0].remoteURL,
                      widget.imageURL,
                      //
                      loadingBuilder: (context, child, loadingProgress) =>
                          (loadingProgress == null)
                              ? child
                              : Center(
                                  child: CircularProgressIndicator(),
                                ),
                      errorBuilder: (context, error, stackTrace) {
                        Future.delayed(
                          Duration(milliseconds: 0),
                          () {
                            if (mounted) {
                              setState(() {
                                widget.errorFoundInImageLoad = true;
                              });
                            }
                          },
                        );
                        return SizedBox.shrink();
                      },
                    ),
            ),
            SizedBox(height: 25),
          ],
        );
      }
    }
Mrudul Addipalli
  • 574
  • 6
  • 10
0

https://pub.dev/packages/extended_image

the plugin makes it easy to handle errors in Network image load

import 'package:flutter/material.dart';
import 'package:extended_image/extended_image.dart';

class ImageHolder extends StatefulWidget {
  const ImageHolder(
      {Key? key,
      required this.url,
      required this.imageHeight,
      required this.imageWidth})
      : super(key: key);

  final String url;
  final double imageHeight;
  final double imageWidth;

  @override
  _ImageHolderState createState() => _ImageHolderState();
}

class _ImageHolderState extends State<ImageHolder>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  @override
  void initState() {
    _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 3),
        lowerBound: 0.0,
        upperBound: 1.0);
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ExtendedImage.network(
      widget.url,
      fit: BoxFit.contain,
      cache: true,
      loadStateChanged: (ExtendedImageState state) {
        switch (state.extendedImageLoadState) {
          case LoadState.loading:
            _controller.reset();
            return Image.asset(
              'assets/images/loading.gif',
              fit: BoxFit.fill,
            );
          case LoadState.completed:
            _controller.forward();
            return FadeTransition(
              opacity: _controller,
              child: ExtendedRawImage(
                image: state.extendedImageInfo?.image,
                width: widget.imageWidth,
                height: widget.imageHeight,
              ),
            );
          case LoadState.failed:
            _controller.reset();
            state.imageProvider.evict();
            return Image.asset(
              'assets/images/failed.jpg',
            );
        }
      },
    );
  }
}
Javeed Ishaq
  • 6,024
  • 6
  • 41
  • 60
0
CachedNetworkImage(
  placeholder: (context, url) =>
               Center(child: new CircularProgressIndicator()),
  errorWidget: (context, url, error) => 
               Icon(Icons.error),
  imageUrl: image,
  fit: BoxFit.fill,
              )
Reshnu chandran
  • 338
  • 2
  • 6
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. You can find more information on how to write good answers in the help center: https://stackoverflow.com/help/how-to-answer . Good luck – nima Nov 01 '21 at 19:09