Advanced Image Handling Guide
The Flutter Riverpod Clean Architecture template includes a robust image handling system for optimized loading, caching, processing, and display of images. This guide explains how to use the various image components and utilities.
Core Components
AdvancedImage Widget
The AdvancedImage
widget provides a sophisticated image loading experience with features like:
- Automatic caching of images
- Placeholder support during loading
- Blur-up image preview thumbnails
- Fade-in animations
- Error handling with customizable error widgets
- Configurable image quality and resizing
AdvancedImage(
imageUrl: 'https://example.com/image.jpg',
width: 300,
height: 200,
fit: BoxFit.cover,
placeholder: ShimmerPlaceholder(),
errorWidget: Icon(Icons.broken_image),
useThumbnailPreview: true,
fadeInDuration: Duration(milliseconds: 300),
);
Image Processor
The ImageProcessor
interface provides methods for image manipulation:
- Resizing images
- Compressing images
- Cropping images
- Applying blur effects
- Converting to grayscale
- Getting image dimensions
- Converting image formats
- Generating thumbnails
final processor = ref.watch(imageProcessorProvider);
// Resize an image
final resizedImage = await processor.resize(
imageData: imageBytes,
width: 800,
height: 600,
maintainAspectRatio: true,
);
// Generate a thumbnail
final thumbnail = await processor.generateThumbnail(
imageData: imageBytes,
maxDimension: 200,
quality: 80,
);
SVG Renderer
The SvgRenderer
handles SVG rendering with caching support:
// Display an SVG image from assets
SvgImage.asset(
'assets/images/icon.svg',
width: 48,
height: 48,
color: Colors.blue,
);
// Display an SVG image from network
SvgImage.network(
'https://example.com/icon.svg',
width: 48,
height: 48,
color: Theme.of(context).primaryColor,
);
Image Transformer
The ImageTransformer
applies visual effects to images:
// Define an effect configuration
final grayscaleEffect = ImageEffectConfig(
effectType: ImageEffectType.grayscale,
intensity: 0.8,
);
// Apply the effect to an image
ImageTransformer(
effect: grayscaleEffect,
child: Image.asset('assets/images/photo.jpg'),
);
// Or use with a provider for dynamic effects
final effectProvider = Provider<ImageEffectConfig>((ref) {
return ImageEffectConfig(
effectType: ImageEffectType.sepia,
intensity: 0.7,
);
});
TransformedImage(
child: Image.network('https://example.com/photo.jpg'),
effectProvider: effectProvider,
);
Shimmer Placeholders
Beautiful loading placeholders with shimmer effects:
// Simple shimmer placeholder
ShimmerPlaceholder(
width: 200,
height: 200,
borderRadius: BorderRadius.circular(8),
);
// Image card skeleton with title and description
ImageCardSkeleton(
width: 200,
height: 300,
showTitle: true,
showDescription: true,
);
// Grid of placeholders for a gallery
ImagePlaceholderGrid(
itemCount: 6,
crossAxisCount: 2,
);
Caching System
Images are cached at two levels:
- Memory Cache: For ultra-fast access to recently used images
- Disk Cache: For persistent storage of images across app launches
The cache configuration can be customized:
// Configure the image memory cache
final customImageCacheProvider = Provider<CacheManager<ui.Image>>((ref) {
return CacheManager<ui.Image>(maxItems: 200);
});
// Use the custom cache
ref.read(advancedImageConfigProvider.notifier).update((state) =>
state.copyWith(memoryCacheProvider: customImageCacheProvider)
);
Best Practices
Image Optimization
- Use appropriate image dimensions: Avoid loading images larger than needed
- Enable auto-resizing: Set
enableAutoResize
to true inAdvancedImageConfig
- Use thumbnails: Generate and display thumbnails for lists and grids
Performance
- Enable caching: Ensure
enableCaching
is true inAdvancedImageConfig
- Use blur-up previews: Enable
useThumbnailPreview
for better perceived performance - Lazy load images: Only load images when they become visible
Accessibility
- Provide semantic labels: Add descriptive text for screen readers
- Handle errors gracefully: Always provide meaningful error widgets
- Show loading indicators: Use shimmer placeholders during loading
Examples
Basic Image Gallery
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: images.length,
itemBuilder: (context, index) {
return AdvancedImage(
imageUrl: images[index].url,
placeholder: ShimmerPlaceholder(),
errorWidget: Icon(Icons.broken_image),
);
},
);
Profile Picture with Effects
TransformedImage(
effectProvider: userProfileEffectProvider,
child: AdvancedImage(
imageUrl: user.avatarUrl,
width: 120,
height: 120,
fit: BoxFit.cover,
placeholder: ShimmerPlaceholder(shape: BoxShape.circle),
),
);
Product Image with SVG Badge
Stack(
children: [
AdvancedImage(
imageUrl: product.imageUrl,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
),
if (product.isNew)
Positioned(
top: 8,
right: 8,
child: SvgImage.asset(
'assets/images/new_badge.svg',
width: 40,
height: 40,
),
),
],
);
Advanced Use Cases
Custom Image Processor
You can create your own implementation of the ImageProcessor
interface:
class FirebaseImageProcessor implements ImageProcessor {
// Implementation using Firebase ML Kit or other libraries
// ...
}
final customImageProcessorProvider = Provider<ImageProcessor>((ref) {
return FirebaseImageProcessor();
});
Custom Effects
Create custom image effects by extending the ImageEffectType
enum and updating the ImageTransformer
widget:
extension CustomImageEffects on ImageEffectType {
static const duotone = ImageEffectType.duotone;
}
// Then in your ImageTransformer implementation:
case CustomImageEffects.duotone:
return ColorFiltered(
colorFilter: ColorFilter.matrix(_getDuotoneMatrix(
effect.intensity,
effect.overlayColor ?? Colors.blue,
)),
child: child,
);
Prefetching Images
Improve the user experience by prefetching images before they're needed:
Future<void> prefetchImagesForGallery(List<String> imageUrls) async {
final processor = ref.read(imageProcessorProvider);
final cache = ref.read(imageMemoryCacheProvider);
for (final url in imageUrls) {
// Check if already cached
final key = ref.read(imageKeyProvider(url));
if (!cache.containsKey(key)) {
// Fetch and cache in background
unawaited(_fetchAndCacheImage(url, processor, cache, key));
}
}
}
Troubleshooting
Common Issues
- Images not loading: Check network connectivity and verify URLs
- Poor performance: Reduce image dimensions or enable auto-resizing
- High memory usage: Decrease the memory cache size or enable aggressive caching
Testing Image Loading
Test image loading in various network conditions:
// Simulate slow network
Future<void> testSlowImageLoading() async {
final tester = await WidgetTester.create();
await tester.pumpWidget(
ProviderScope(
overrides: [
imageProcessorProvider.overrideWithValue(
SlowNetworkImageProcessor(),
),
],
child: MyApp(),
),
);
// Verify loading behavior
expect(find.byType(ShimmerPlaceholder), findsWidgets);
await tester.pump(Duration(seconds: 5));
expect(find.byType(ShimmerPlaceholder), findsNothing);
}