Home > Software engineering >  Flutter: How to handle NetworkImage with Invalid Image Data exception?
Flutter: How to handle NetworkImage with Invalid Image Data exception?

Time:08-24

I am trying to use NetworkImage as the image in a BoxDecoration for a Container. The url passed to the NetworkImage will sometimes hold a bad image type (the url works and is correct but the actual image at the specified url is bad) resulting in an error.

To handle this occurrence I set up a method that uses a try-catch block where it returns the NetworkImage if successful, and a preset AssetImage in the event of an error. The try-catch block is not handling this exception, and throws an error instead of returning the AssetImage specified in the catch.

I've seen that Image.network has an one rror parameter which looks like it would solve the problem, but Image.network is of type "Image" and BoxDecoration requires an "ImageProvider" (NetworkImage, AssetImage), so that does not help in this case.

Which is the best way to handle this error so that I can show an AssetImage in the case of the NetworkImage throwing an error?

Here is the Widget holding the BoxDecoration where I call the method I created to handle fetching the NetworkImage:

class CharacterPreviewCard extends StatelessWidget {
  final CharacterPreview character;
  const CharacterPreviewCard({Key? key, required this.character})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        context.router
            .push(CharacterDetailsRoute(characterId: character.characterId));
      },
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            height: 171,
            width: 171,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(16),
              color: Colors.black,
              image: DecorationImage(
                image: getCharacterAvatar(character.characterAvatarUrl),
                fit: BoxFit.fill,
              ),
            ),
          ),
          const SizedBox(height: smallMargin),
          Padding(
            padding: const EdgeInsets.only(left: smallMargin),
            child: SizedBox(
              width: 165,
              child: Text(
                character.characterName,
                style: Theme.of(context).textTheme.bodyLarge,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ),
        ],
      ),
    );
  }

Here is the method "getCharacterAvatar" which should return either NetworkImage or AssetImage:

ImageProvider<Object> getCharacterAvatar(String url) {
    try {
      final image = NetworkImage(url);
      return image;
    } catch (e) {
      return const AssetImage('assets/images/village-not-found-logo.png');
    }
  }

And here is the error in Debug Console:

The following _Exception was thrown resolving an image codec:
Exception: Invalid image data

When the exception was thrown, this was the stack
#0      _futurize (dart:ui/painting.dart:5718:5)
#1      ImageDescriptor.encoded (dart:ui/painting.dart:5574:12)
#2      instantiateImageCodec (dart:ui/painting.dart:2056:60)
<asynchronous suspension>
Image provider: NetworkImage("https://narutoql.s3.amazonaws.com/Hana.jpg", scale: 1.0)
Image key: NetworkImage("https://narutoql.s3.amazonaws.com/Hana.jpg", scale: 1.0)

CodePudding user response:

Currently, there is no way to catch errors with NetworkImage or Image.network. More on this can be found here: https://github.com/flutter/flutter/issues/20910.

Thanks to Tom3652's comment suggesting the use of CachedNetworkImage I was able to find a solution that works using the Widget CachedNetworkImage with an errorWidget parameter to display an AssetImage when an error was thrown.

I replaced the Container that had the NetworkImage as a parameter for DecorationImage with a custom widget (to minimized code in the file). The custom widget returns the CachedNetworkImage.

Here is the solution that worked for me:

class PreviewCardImage extends StatelessWidget {
  final String url;
  final AssetImage errorImage;
  const PreviewCardImage({
    Key? key,
    required this.url,
    required this.errorImage,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CachedNetworkImage(
      imageUrl: url,
      imageBuilder: (context, imageProvider) => Container(
        height: 171,
        width: 171,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(16),
          color: Colors.black,
          image: DecorationImage(
            image: imageProvider,
            fit: BoxFit.fill,
          ),
        ),
      ),
      placeholder: (context, url) => const CircularProgressIndicator(),
      errorWidget: (context, url, error) => Container(
        height: 171,
        width: 171,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(16),
          color: Colors.black,
          image: DecorationImage(
            image: errorImage,
            fit: BoxFit.fill,
          ),
        ),
      ),
    );
  }
}

CodePudding user response:

I think you should use the Image.network class replacing your NetworkImage.

It contains especially an errorBuilder parameter that allows you to set a custom widget in case of error :

Image.network(
  String src,
  {Key? key, 
  double scale = 1.0,
  ImageFrameBuilder? frameBuilder,
  ImageLoadingBuilder? loadingBuilder,
  ImageErrorWidgetBuilder? errorBuilder,
  ...
  int? cacheWidth,
  int? cacheHeight}
)

If you need the ImageProvider, then your method can look like this :

ImageProvider<Object> getCharacterAvatar(String url) {
     final image = Image.network(url, errorBuilder: (context, object, trace) {
      return Image(image: AssetImage('assets/images/village-not-found-logo.png'));
    },).image;
     return image;
}

Please note the .image at the end of the Image.network().image to return the ImageProvider.

  • Related