Displaying a bundled local image versus one loaded from a remote URL uses the same Image widget in Flutter, but with different constructors — and the remote version needs to account for loading and error states that a local asset never has to worry about.
Registering assets in pubspec.yaml
flutter:
assets:
- assets/images/logo.png
- assets/images/
An image needs to be listed (or its containing folder listed) in pubspec.yaml before Flutter will bundle it into the app — a common early mistake is adding the file to the project folder but forgetting this registration step, resulting in the image simply failing to load with no obvious reason why.
Displaying a local (bundled) image
Image.asset('assets/images/logo.png')
// or, more explicitly
Image(image: AssetImage('assets/images/logo.png'))
Displaying a remote image
Image.network('https://example.com/photo.jpg')
Handling loading and error states for a remote image
Image.network(
'https://example.com/photo.jpg',
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
);
},
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.broken_image);
},
)
A local asset image is bundled into the app itself and essentially can't fail to load (short of a build misconfiguration) — a network image genuinely can fail (no connection, a 404, a slow connection worth showing progress for), which is exactly why only the network version needs these loading and error handling callbacks.
Caching network images across the app
flutter pub add cached_network_image
CachedNetworkImage(
imageUrl: 'https://example.com/photo.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.broken_image),
)
Flutter's built-in Image.network re-downloads the same image on every rebuild unless the underlying image cache happens to still hold it — the cached_network_image package adds a genuine disk cache, meaningfully reducing repeated network requests for images shown repeatedly (a list of avatars scrolling in and out of view, for instance).
Sizing and fitting an image within its container
Image.network(
'https://example.com/photo.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
)
BoxFit.cover scales the image to fill the given dimensions, cropping any overflow — BoxFit.contain instead scales to fit entirely within the dimensions without cropping, potentially leaving empty space — choosing between them depends on whether cropping or empty space is the more acceptable trade-off for a specific layout.