Efficient Image Caching in Flutter with Riverpod

/

Overview:-

  • Discover how to optimize image caching in Flutter using Riverpod for improved app performance. 
  • Learn step-by-step techniques for manual caching, reducing network load, and enhancing UI smoothness without external libraries

When you build a Flutter application, images are often a vital part, and as a developer, you have to deal with them on a daily basis, whether you’re building a product gallery, a social media feed, or a daily inspiration screen.

But if you’re not optimizing them, you’ll be downloading these images from the internet every time, and your app will be slow and you’ll use lots of data.

This guide walks you through a practical way to cache images manually and manage them efficiently using Riverpod, Flutter’s powerful state management tool. 

We’ll skip external libraries and show how to achieve smooth, responsive image loading with tools already at your disposal.

Who This Is For

This guide is for Flutter developers dealing with network images. No matter if you are a new developer looking for ways to make a performant app, or an intermediate developer looking to power up a responsive, intelligent, cache-aware image gallery, this article will walk you through a set of tools and techniques for making your app more efficient and enjoyable for users.

Why Cache Images?

Here’s the thing: every time you request an image from a URL, you’re essentially making a call out to the network. Imagine doing that every single time when the same image needs to be displayed. 

Doesn’t make much sense, right? But here’s where image caching saves the day.

When you cache an image, you’re saying to your app, ā€œRemember this one for the next time around.ā€ This means that when users scroll, navigate, and return to a screen, the app doesn’t need to go back to the network to fetch the same image repeatedly. It’s already there, ready and waiting.

Key Benefits of Image Caching:

These are the main reasons why caching is vital

Performance Boost: Cached images load immediately from local storage. This speeds up rendering and provides a smoother & faster experience..

Reduced Network Load: Your app saves bandwidth and makes fewer server requests by not having to download images again.

Smoother UI: With cached images, there’s no delay or flicker during loading. This ensures a seamless, uninterrupted user experience.

Tools We’ll Use

These are the tools that we’ll be using in this guide

  • Flutter – for UI and native image rendering
  • Riverpod – for reactive state management
  • Standard Dart APIs – to cache images manually in memory

Step-by-step process to cache an image in Flutter with Riverpod

Take a look at the simple step-by-step process to cache images in Flutter with Riverpod

Step 1: Define Your Image URL Provider

First, create a simple StateNotifier to manage a list of image URLs:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final imageUrlsProvider = StateNotifierProvider<ImageUrlNotifier, List<String>>(

Ā Ā (ref) => ImageUrlNotifier(),

);

class ImageUrlNotifier extends StateNotifier<List<String>> {

Ā Ā ImageUrlNotifier() : super([]);

Ā Ā void addImage(String url) {

Ā Ā Ā Ā state = [...state, url];

Ā Ā }

Ā Ā void removeImage(String url) {

Ā Ā Ā Ā state = state.where((img) => img != url).toList();

Ā Ā }

}

Step 2: Implement a Basic In-Memory Cache

We’ll manually cache images using a Map<String, Uint8List> to avoid re-fetching the same image.

final imageCacheProvider = Provider<ImageCacheManager>((ref) {

Ā Ā return ImageCacheManager();

});

class ImageCacheManager {

Ā Ā final _cache = <String, Uint8List>{};

Ā Ā Future<Uint8List> getImageBytes(String url) async {

Ā Ā Ā Ā if (_cache.containsKey(url)) return _cache[url]!;

Ā Ā Ā Ā final response = await NetworkAssetBundle(Uri.parse(url)).load(url);

Ā Ā Ā Ā final bytes = response.buffer.asUint8List();

Ā Ā Ā Ā _cache[url] = bytes;

Ā Ā Ā Ā return bytes;

Ā Ā }

Ā Ā void clearCache() {

Ā Ā Ā Ā _cache.clear();

Ā Ā }

Ā Ā void removeImage(String url) {

Ā Ā Ā Ā _cache.remove(url);

Ā Ā }

}

Step 3: Build the Image Gallery Widget

Use Image.memory() instead of Image.network() for cached image data:

import 'package:flutter/material.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';

class ImageGallery extends ConsumerWidget {

Ā Ā const ImageGallery({Key? key}) : super(key: key);

Ā Ā @override

Ā Ā Widget build(BuildContext context, WidgetRef ref) {

Ā Ā Ā Ā final imageUrls = ref.watch(imageUrlsProvider);

Ā Ā Ā Ā final cacheManager = ref.watch(imageCacheProvider);

Ā Ā Ā Ā return ListView.builder(

Ā Ā Ā Ā Ā Ā itemCount: imageUrls.length,

Ā Ā Ā Ā Ā Ā itemBuilder: (context, index) {

Ā Ā Ā Ā Ā Ā Ā Ā final url = imageUrls[index];

Ā Ā Ā Ā Ā Ā Ā Ā return FutureBuilder<Uint8List>(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā future: cacheManager.getImageBytes(url),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā builder: (context, snapshot) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā if (snapshot.connectionState == ConnectionState.waiting) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return const Padding(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā padding: EdgeInsets.all(16),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā child: Center(child: CircularProgressIndicator()),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā if (snapshot.hasError || !snapshot.hasData) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return const Icon(Icons.error);

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return Padding(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā padding: const EdgeInsets.all(8.0),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā child: Image.memory(snapshot.data!),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā },

Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā },

Ā Ā Ā Ā );

Ā Ā }

}

Step 4: Add Images Dynamically

Add a new image URL to the list using a simple button:

ElevatedButton(

Ā Ā onPressed: () {

Ā Ā Ā Ā ref.read(imageUrlsProvider.notifier).addImage(

Ā Ā Ā Ā Ā Ā 'https://example.com/newimage.jpg',

Ā Ā Ā Ā );

Ā Ā },

Ā Ā child: const Text("Add Image"),

)

Step 5: Manual Cache Control

You can expose cache management buttons (optional):

ElevatedButton(

Ā Ā onPressed: () {

Ā Ā Ā Ā ref.read(imageCacheProvider).clearCache();Ā 

///(or we can remove a single Image using ref.read(imageCacheProvider).removeImage('https://example.com/image.jpg');)

Ā Ā },

Ā Ā child: const Text("Clear All Cached Images"),

),

Advantages of Using Riverpod

These are the advantages of using Riverpod for caching images

Scoped Memory Usage: Riverpod manages memory efficiently and prevents unnecessary memory usage. It keeps state alive only as long as necessary.

Separation of Concerns: Riverpod makes sure that state management is isolated from UI logic, which results in a clean and maintainable app.

Testability: Riverpod’s architecture makes it easy to mock providers, which means you can easily unit test your code without relying on an overly complex UI.

Reactivity: Riverpod uses a reactive model, so your app only updates when it has to, boosting performance.

Final Result

With this approach:

  • Images are downloaded once and stored in memory
  • They are rendered instantly once cached
  • You can dynamically add/remove images
  • No external packages are needed

Conclusion

While Flutter packages like cached_network_image offer automatic solutions, building your own image caching logic gives you more control and understanding of how caching works.

Using Riverpod for reactive state and in-memory storage for image bytes, this guide showed you how to cache images without plugins, dynamically manage image state, and keep your UI fast and clean.

Want persistent disk caching? You could extend this with path_provider and file storage. But for most use cases, in-memory caching gives a significant performance win.

Overview:-

  • Discover how to optimize image caching in Flutter using Riverpod for improved app performance. 
  • Learn step-by-step techniques for manual caching, reducing network load, and enhancing UI smoothness without external libraries

When you build a Flutter application, images are often a vital part, and as a developer, you have to deal with them on a daily basis, whether you’re building a product gallery, a social media feed, or a daily inspiration screen.

But if you’re not optimizing them, you’ll be downloading these images from the internet every time, and your app will be slow and you’ll use lots of data.

This guide walks you through a practical way to cache images manually and manage them efficiently using Riverpod, Flutter’s powerful state management tool. 

We’ll skip external libraries and show how to achieve smooth, responsive image loading with tools already at your disposal.

Who This Is For

This guide is for Flutter developers dealing with network images. No matter if you are a new developer looking for ways to make a performant app, or an intermediate developer looking to power up a responsive, intelligent, cache-aware image gallery, this article will walk you through a set of tools and techniques for making your app more efficient and enjoyable for users.

Why Cache Images?

Here’s the thing: every time you request an image from a URL, you’re essentially making a call out to the network. Imagine doing that every single time when the same image needs to be displayed. 

Doesn’t make much sense, right? But here’s where image caching saves the day.

When you cache an image, you’re saying to your app, ā€œRemember this one for the next time around.ā€ This means that when users scroll, navigate, and return to a screen, the app doesn’t need to go back to the network to fetch the same image repeatedly. It’s already there, ready and waiting.

Key Benefits of Image Caching:

These are the main reasons why caching is vital

Performance Boost: Cached images load immediately from local storage. This speeds up rendering and provides a smoother & faster experience..

Reduced Network Load: Your app saves bandwidth and makes fewer server requests by not having to download images again.

Smoother UI: With cached images, there’s no delay or flicker during loading. This ensures a seamless, uninterrupted user experience.

Tools We’ll Use

These are the tools that we’ll be using in this guide

  • Flutter – for UI and native image rendering
  • Riverpod – for reactive state management
  • Standard Dart APIs – to cache images manually in memory

Step-by-step process to cache an image in Flutter with Riverpod

Take a look at the simple step-by-step process to cache images in Flutter with Riverpod

Step 1: Define Your Image URL Provider

First, create a simple StateNotifier to manage a list of image URLs:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final imageUrlsProvider = StateNotifierProvider<ImageUrlNotifier, List<String>>(

Ā Ā (ref) => ImageUrlNotifier(),

);

class ImageUrlNotifier extends StateNotifier<List<String>> {

Ā Ā ImageUrlNotifier() : super([]);

Ā Ā void addImage(String url) {

Ā Ā Ā Ā state = [...state, url];

Ā Ā }

Ā Ā void removeImage(String url) {

Ā Ā Ā Ā state = state.where((img) => img != url).toList();

Ā Ā }

}

Step 2: Implement a Basic In-Memory Cache

We’ll manually cache images using a Map<String, Uint8List> to avoid re-fetching the same image.

final imageCacheProvider = Provider<ImageCacheManager>((ref) {

Ā Ā return ImageCacheManager();

});

class ImageCacheManager {

Ā Ā final _cache = <String, Uint8List>{};

Ā Ā Future<Uint8List> getImageBytes(String url) async {

Ā Ā Ā Ā if (_cache.containsKey(url)) return _cache[url]!;

Ā Ā Ā Ā final response = await NetworkAssetBundle(Uri.parse(url)).load(url);

Ā Ā Ā Ā final bytes = response.buffer.asUint8List();

Ā Ā Ā Ā _cache[url] = bytes;

Ā Ā Ā Ā return bytes;

Ā Ā }

Ā Ā void clearCache() {

Ā Ā Ā Ā _cache.clear();

Ā Ā }

Ā Ā void removeImage(String url) {

Ā Ā Ā Ā _cache.remove(url);

Ā Ā }

}

Step 3: Build the Image Gallery Widget

Use Image.memory() instead of Image.network() for cached image data:

import 'package:flutter/material.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';

class ImageGallery extends ConsumerWidget {

Ā Ā const ImageGallery({Key? key}) : super(key: key);

Ā Ā @override

Ā Ā Widget build(BuildContext context, WidgetRef ref) {

Ā Ā Ā Ā final imageUrls = ref.watch(imageUrlsProvider);

Ā Ā Ā Ā final cacheManager = ref.watch(imageCacheProvider);

Ā Ā Ā Ā return ListView.builder(

Ā Ā Ā Ā Ā Ā itemCount: imageUrls.length,

Ā Ā Ā Ā Ā Ā itemBuilder: (context, index) {

Ā Ā Ā Ā Ā Ā Ā Ā final url = imageUrls[index];

Ā Ā Ā Ā Ā Ā Ā Ā return FutureBuilder<Uint8List>(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā future: cacheManager.getImageBytes(url),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā builder: (context, snapshot) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā if (snapshot.connectionState == ConnectionState.waiting) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return const Padding(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā padding: EdgeInsets.all(16),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā child: Center(child: CircularProgressIndicator()),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā if (snapshot.hasError || !snapshot.hasData) {

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return const Icon(Icons.error);

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā return Padding(

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā padding: const EdgeInsets.all(8.0),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā child: Image.memory(snapshot.data!),

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā },

Ā Ā Ā Ā Ā Ā Ā Ā );

Ā Ā Ā Ā Ā Ā },

Ā Ā Ā Ā );

Ā Ā }

}

Step 4: Add Images Dynamically

Add a new image URL to the list using a simple button:

ElevatedButton(

Ā Ā onPressed: () {

Ā Ā Ā Ā ref.read(imageUrlsProvider.notifier).addImage(

Ā Ā Ā Ā Ā Ā 'https://example.com/newimage.jpg',

Ā Ā Ā Ā );

Ā Ā },

Ā Ā child: const Text("Add Image"),

)

Step 5: Manual Cache Control

You can expose cache management buttons (optional):

ElevatedButton(

Ā Ā onPressed: () {

Ā Ā Ā Ā ref.read(imageCacheProvider).clearCache();Ā 

///(or we can remove a single Image using ref.read(imageCacheProvider).removeImage('https://example.com/image.jpg');)

Ā Ā },

Ā Ā child: const Text("Clear All Cached Images"),

),

Advantages of Using Riverpod

These are the advantages of using Riverpod for caching images

Scoped Memory Usage: Riverpod manages memory efficiently and prevents unnecessary memory usage. It keeps state alive only as long as necessary.

Separation of Concerns: Riverpod makes sure that state management is isolated from UI logic, which results in a clean and maintainable app.

Testability: Riverpod’s architecture makes it easy to mock providers, which means you can easily unit test your code without relying on an overly complex UI.

Reactivity: Riverpod uses a reactive model, so your app only updates when it has to, boosting performance.

Final Result

With this approach:

  • Images are downloaded once and stored in memory
  • They are rendered instantly once cached
  • You can dynamically add/remove images
  • No external packages are needed

Conclusion

While Flutter packages like cached_network_image offer automatic solutions, building your own image caching logic gives you more control and understanding of how caching works.

Using Riverpod for reactive state and in-memory storage for image bytes, this guide showed you how to cache images without plugins, dynamically manage image state, and keep your UI fast and clean.

Want persistent disk caching? You could extend this with path_provider and file storage. But for most use cases, in-memory caching gives a significant performance win.

logo

Soft Suave - Live Chat online

close

Are you sure you want to end the session?

šŸ’¬ Hi there! Need help?
chat 1