Flutter Image Placeholder and ErrorBuilder Patterns
Use flutter image placeholder and errorBuilder to show loading states and fallback images when Image.network fails in Flutter apps.
Flutter's Image.network widget has three loading-state callbacks: loadingBuilder for the in-progress state, errorBuilder for the failed state, and frameBuilder for controlling the transition from loading to loaded. Using these callbacks correctly lets you show a flutter image placeholder during loading and a fallback image on error without third-party dependencies.
The CachedNetworkImage package adds disk caching and a simpler placeholder API on top of Image.network. Both approaches work with URL-based fallback images from fallback.pics, eliminating the need to bundle placeholder asset files in your app.
Widget API
Image.network loadingBuilder and errorBuilder signatures
loadingBuilder receives the BuildContext, a child widget (the image being loaded), and a ImageChunkEvent that reports bytes downloaded. Return a widget from loadingBuilder — typically a progress indicator or a placeholder widget. When the image finishes loading, Flutter calls the builder with a null loadingProgress and renders the loaded image.
errorBuilder receives the BuildContext, the error object, and a stack trace. Return a widget that represents the error state — a fallback image, an icon, or a text label. The errorBuilder fires on any Image.network failure: 404, network timeout, or invalid image data.
frameBuilder wraps the loaded image widget and is called on every animation frame while the image transitions from loading to loaded. Use it to add fade-in effects. Without frameBuilder, the image appears instantly with no transition.
// Image.network with loadingBuilder and errorBuilder
import 'package:flutter/material.dart';
class NetworkImageWithFallback extends StatelessWidget {
final String imageUrl;
final double width;
final double height;
const NetworkImageWithFallback({
super.key,
required this.imageUrl,
this.width = 200,
this.height = 200,
});
String get _fallbackUrl =>
'https://fallback.pics/api/v1/${width.toInt()}x${height.toInt()}/E4E4E7/71717A';
@override
Widget build(BuildContext context) {
return Image.network(
imageUrl,
width: width,
height: height,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: width,
height: height,
color: const Color(0xFFE4E4E7),
child: const Center(child: CircularProgressIndicator()),
);
},
errorBuilder: (context, error, stackTrace) {
return Image.network(
_fallbackUrl,
width: width,
height: height,
fit: BoxFit.cover,
);
},
);
}
} CachedNetworkImage
Simpler placeholder API with CachedNetworkImage
The cached_network_image package provides placeholder and errorWidget properties that accept a WidgetBuilder. It handles disk caching, memory caching, and HTTP cache headers automatically. For most production use cases, CachedNetworkImage is simpler than wiring Image.network callbacks manually.
The placeholder property shows a widget while the image is being fetched. The errorWidget property shows a widget when the fetch fails. Both accept the context and the URL as parameters, which lets you generate a fallback URL from the original URL's dimensions.
// CachedNetworkImage with fallback.pics fallback
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CachedImageWithFallback extends StatelessWidget {
final String imageUrl;
final double width;
final double height;
const CachedImageWithFallback({
super.key,
required this.imageUrl,
this.width = 200,
this.height = 200,
});
@override
Widget build(BuildContext context) {
final fallbackUrl =
'https://fallback.pics/api/v1/${width.toInt()}x${height.toInt()}/7C3AED/FFFFFF';
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: width,
height: height,
color: const Color(0xFFE4E4E7),
),
errorWidget: (context, url, error) => CachedNetworkImage(
imageUrl: fallbackUrl,
width: width,
height: height,
fit: BoxFit.cover,
),
);
}
} Shimmer loading
Animated shimmer placeholder during image load
A shimmer animation communicates loading state better than a static grey box, especially for image-heavy lists like product catalogs or social feeds. The shimmer package provides a Shimmer widget that animates a linear gradient across a placeholder shape.
Combine the shimmer placeholder with loadingBuilder in Image.network or the placeholder property in CachedNetworkImage. Show the shimmer during the loading phase and remove it when loadingProgress is null (loaded) or when errorBuilder fires.
// Shimmer loading placeholder
import 'package:shimmer/shimmer.dart';
Widget _buildPlaceholder(double width, double height) {
return Shimmer.fromColors(
baseColor: const Color(0xFFE4E4E7),
highlightColor: const Color(0xFFF4F4F5),
child: Container(
width: width,
height: height,
color: Colors.white,
),
);
}
// Use inside CachedNetworkImage:
// placeholder: (context, url) => _buildPlaceholder(width, height), List performance
Flutter image loading performance in ListView and GridView
Flutter's ListView.builder creates and destroys widgets as items scroll off-screen. When a widget is destroyed and later recreated, Image.network re-fetches the image from cache or network. CachedNetworkImage uses a disk cache keyed by URL, so the re-fetch is a fast cache hit after the first load.
Without CachedNetworkImage, use the precacheImage function to pre-load images before they scroll into view. Call precacheImage in the initState of your list widget for the first batch of URLs. This reduces the loading state duration for initially visible items.
Error handling depth
Handling errorBuilder fallback failures gracefully
If the fallback URL itself fails (device is offline), the errorBuilder's Image.network will call its own errorBuilder. Avoid infinite nesting of errorBuilders. Instead, use a local asset or a simple Container as the ultimate fallback — the last resort that requires no network.
A two-level fallback strategy works well: level 1 is the remote fallback URL (fallback.pics), level 2 is a local bundled asset that is always available. The local asset only needs to be a small, generic placeholder since it fires only when both the original URL and the remote fallback fail.
Key takeaways
What to standardize before shipping
- Image.network provides loadingBuilder and errorBuilder for loading states and error fallbacks without any third-party package.
- CachedNetworkImage simplifies the placeholder and fallback API and adds disk caching, making it the better choice for production apps.
- Use a shimmer animation via the shimmer package in the loadingBuilder or placeholder for a polished loading state.
- errorBuilder fallbacks can also fail — implement a two-level strategy: remote fallback URL first, local bundled asset as last resort.
- CachedNetworkImage's disk cache means ListView.builder recycling does not cause unnecessary network re-fetches.
Production fallback layer
Use fallback.pics anywhere an image URL is accepted.
Start with one deterministic URL and standardize fallback behavior across your design system.