Ensuring the efficient performance of any app is paramount, particularly when it comes to handling images. At times, it's more efficient to display data from a previous network request instead of making the user wait for a new request to complete. We reduce the number of requests (cost reduction) and make the user see the data faster. Users are often impatient, and long waits for data may irritate them or falsely conclude that our application is simply broken/jammed or is performing mysterious operations to obtain sensitive user data. This method of storing locally and reusing application data for future use is called caching.
Table of Contents
- Caching terminology
- Benefits of caching images
- Basic usage of Image.network
- Handling cache expiration in custom cache manager
- Customizing placeholder images and handling errors
- Managing cache directory
- Bonus: preloading images
- Optimizing cache performance
- Enhancing Flutter app performance with network image caching
Caching terminology
One of the critical aspects of optimizing Flutter apps is effectively managing the caching of network images. Let’s start with the caching definition provided by the AWS:
In computing, a cache is a high-speed data storage layer which stores a subset of data, typically transient in nature, so that future requests for that data are served up faster than is possible by accessing the data’s primary storage location. Caching allows you to efficiently reuse previously retrieved or computed data. (source: https://aws.amazon.com/caching/ )
Caching comes with terminology and concepts essential to understanding Flutter’s capabilities. For example, a cache hit: “An app is said to have had a cache hit when the cache already contained their desired information, and loading it from the real source of truth was unnecessary.” Cache miss, on the other hand, happens when “an app is said to have had a cache miss when the cache was empty, and the desired data is loaded from the real source of truth, and then saved to the cache for future reads.” (source: https://docs.flutter.dev/get-started/fwe/local-caching#common-caching-terminology)
When an app relies on cached data, there's a risk that the cache may become outdated if the underlying data changes, leading to the app displaying obsolete information. This issue, known as a stale cache, is a common pitfall in caching strategies. Unfortunately, verifying the cache's freshness usually takes as much time as reloading the entire data, thereby undermining the benefits of caching. In the case of images, this problem may occur less frequently because images can remain unchanged for a long time unless the app's primary purpose is to "do something with images."
Benefits of caching images
As we know, data can take various forms and types. Undeniably, if an app is to attract potential users, it must stand out with unique assets. As mobile application developers, we often encounter features that require displaying images. It is not enough to simply display a beautiful picture. Various circumstances need to be considered, such as situations where we simply cannot load an image due to connection issues and need to display something in its place, or, lastly, when we are waiting for data to be fetched. You have probably already figured that caching techniques also matter in the case of images.
Caching network images in Flutter is essential for improving your app's performance and user experience. The primary benefit is the automatic image caching. Once an image is loaded for the first time, it is stored locally, allowing for quick loading on subsequent requests without needing to re-download from the internet, significantly reducing data consumption.
The application runs more smoothly and quickly thanks to image caching, especially when the user navigates through the app and repeatedly displays images. The solution offers built-in error handling mechanisms, such as displaying placeholder images or error widgets when image loading issues occur.
Delivered solutions are easy to integrate with existing Flutter applications, enabling quick and efficient addition of image caching functionality. Thanks to caching, memory and resource usage are reduced, which is particularly important in mobile applications with limited resources. Flutter offers a variety of mechanisms and packages designed to streamline the efficient caching of network images. In the following sections, I will explain these mechanisms, provide clear examples, and suggest effective optimization strategies.
Basic usage of Image.network
The Image.network constructor in Flutter does involve some caching. By default, network images loaded using this constructor are cached to improve performance and reduce redundant network requests. All network images are cached regardless of HTTP headers. However, the caching mechanism for Image.network is not configurable and lacks the flexibility.
![the image.network constructor view in flutter](https://a.storyblok.com/f/202591/667x787/c252defe06/the-image-network-constructor-in-flutter.png/m/4608x6744/filters:format(webp):quality(90))
The Image.network constructor in Flutter utilizes both the ImageCache and ImageProvider under the hood. Here’s how it works:
ImageProvider: The Image.network constructor creates an instance of NetworkImage, which is a subclass of ImageProvider. It is responsible for fetching the image from the network and providing it to the Flutter framework.
ImageCache: The ImageProvider (in this case, NetworkImage) works with ImageCache to buffer the images. ImageCache is a utility class in Flutter that stores references to images to avoid redundant loading and rendering while improving performance and reducing network usage.
According to Flutter documentation:
If cacheWidth or cacheHeight are provided, they indicate to the engine that the image should be decoded at the specified size. The image will be rendered to the constraints of the layout or width and height, regardless of these parameters. These parameters are primarily intended to reduce the memory usage of ImageCache. (source: https://api.flutter.dev/flutter/widgets/Image/Image.network.html)
For network images on the Web platform, cacheWidth, and cacheHeight are disregarded because the browser handles image decoding on the web, where custom decode sizes are not supported.
Here you can see the example:
![network images on the web platform](https://a.storyblok.com/f/202591/702x473/a064611e1c/network-images-on-the-web-platform.png/m/4608x3147/filters:format(webp):quality(90))
Handling cache expiration in custom cache manager
For more advanced control over caching, including custom cache managers, expiration policies, and placeholders, consider using the cached_network_image package, which provides more flexibility and functionality.
CachedNetworkImage package
The cached_network_image package in Flutter provides an enhanced way to load and cache network images, offering more features and flexibility compared to the default Image.network constructor. Here is an in-depth look at how cached_network_image functions behind the scenes.
![cached network image functioning](https://a.storyblok.com/f/202591/903x827/5d00a71a11/cached_network_image-functioning.png/m/4608x4608/filters:format(webp):quality(90))
How it works?
Network Request and Image Loading:
The CachedNetworkImage widget uses a CachedNetworkImageProvider to fetch images from the network.
When an image is requested, the CachedNetworkImageProvider checks if the picture is already stored in the cache.
If the image is not in the cache, it initiates a network request to download it.
Caching Mechanism:
The package uses the flutter_cache_manager to handle caching.
flutter_cache_manager provides a CacheManager class that stores the downloaded images in the device's file system.
The cache manager can be configured to control the cache duration, maximum cache size, and other parameters.
Cache Key:
Each image URL has a unique cache key. This key helps identify and retrieve the image from the cache.
By default, the URL itself is used as the cache key.
Custom Cache Manager (more below):
Developers can provide a custom cache manager to override the default caching behavior.
This is useful for setting custom cache policies, such as the cache duration, or implementing a custom eviction strategy.
CachedNetworkImage can be used directly or through ImageProvider. However, both CachedNetworkImage and CachedNetworkImageProvider currently offer minimal support for the web and lack caching capabilities in that environment. For other platforms, cached network images use flutter_cache_manager to store and retrieve files. A CacheManager is employed to download and cache these files in the app's cache directory, ensuring efficient loading and offline availability.
Customizing placeholder images and handling errors
In both cases (Image. network and CachedNetworkImage), handling the loading state or any error is extremely simple. We cannot assume only the "happy path." It's worth being prepared for every eventuality and handling all states. Incorporating widgets to manage the loading and error states effectively suffices; additional complexities are unnecessary.
CachedNetworkImage offers robust support for placeholder and error widgets. These widgets serve as visual indicators while the image is being fetched or if fetching encounters any issues. They contribute significantly to maintaining a seamless user experience by providing informative feedback during loading or error occurrences.
![loading error image](https://a.storyblok.com/f/202591/833x427/9c8cbd5417/loading-errors.png/m/4608x2295/filters:format(webp):quality(90))
Managing cache directory
It is crucial not to create more than one CacheManager instance with the same key, as they will interfere with each other. By default, cached files are stored in the app's temporary directory, meaning the OS can delete these files at any time. Information about the files is stored in a database using sqflite on Android, iOS, and macOS, or in a plain JSON file on other platforms. The database file name corresponds to the cacheManager's key, which is why it must be unique. Files can be removed by either the cache manager or by the operating system. Typically, files are stored in a cache folder that may be cleaned periodically, for example, during an app update on Android.
The CacheManager uses two key variables to determine when to delete a file: maxNrOfCacheObjects and stalePeriod. The cache tracks the last usage of files. During cache cleaning, which occurs continuously, the manager deletes files that exceed the maximum number of cache objects, prioritizing those used least recently, and files that haven't been used beyond the stale period.
Example:
![cache cleaning in CacheManager](https://a.storyblok.com/f/202591/1048x977/d028a60ae3/cache-cleaning-in-cachemanager.png/m/4608x4608/filters:format(webp):quality(90))
Configuring cache settings
Efficiently managing network image caching in a Flutter app involves several critical configuration parameters. Here’s a breakdown of the key terms and settings that will help you optimize performance and user experience:
Cache Key (key): A unique key to identify the cache. It is used for the folder to store files and for the database file name. This is also useful if you need to separate different caches within your app.
Stale Period: (stalePeriod) The duration after which cached items are considered stale and may be refreshed from the network.
Max Number of Cache Objects (maxNrOfCachedObjects): The maximum number of items to keep in the cache, which helps manage the cache size by limiting the number of cached objects. If there are more files that haven't been used for a longer time, they will be removed.
Cache Info Repository (repo): Custom implementation of a repository to store cache metadata. The default is a JSON file repository, but on Android, iOS, and macOS, it defaults to CacheObjectProvider, a sqflite implementation. On the web, it defaults to NonStoringObjectProvider. In the case of other platforms, it defaults to JsonCacheInfoRepository.
File Service (fileService): Custom file service for fetching files. The default is HttpFileService but this can be adapted to handle different types of file requests.
File System (fileSystem): The file system defines where the cached files are stored
Compared to the previous solutions, this one provides more configuration options. Additionally, the fadeIn and fadeOut parameters allow for smooth animation during image loading, further improving the visual appeal of your app.
Bonus: preloading images
The precacheImage function in Flutter is a powerful method for loading images ahead of time and storing them in the cache. It is particularly useful because the initial load of an asset (especially one stored locally) can sometimes cause a brief flicker. By utilizing precacheImage, subsequent requests for the same image can be fulfilled directly from the cache, leading to faster load times and smoother user interactions. It ensures a user’s seamless visual experience while navigating your app.
![prealoading images in flutter](https://a.storyblok.com/f/202591/975x525/247acb429f/prealoading-images-in-flutter.png/m/4608x2799/filters:format(webp):quality(90))
Optimizing cache performance
As shown in the article, Flutter provides sophisticated techniques for image caching, providing developers with robust tools to enhance app performance and user experience. While we've covered the essential aspects of caching, there are additional optimization practices that can further improve efficiency:
Reduce Image Size: Use appropriately sized images to reduce the amount of data that needs to be downloaded and cached.
Image Compression: Compress images to balance quality and file size.
Network Policies: Implement policies such as loading images only over Wi-Fi or ensuring the app handles slow network conditions gracefully.
Use of Placeholders: Display low-resolution placeholders quickly while the main image loads.
Enhancing Flutter app performance with network image caching
Caching network images in Flutter is crucial for optimizing app performance and improving user experience. By storing data locally and reusing it, developers can reduce the number of network requests and accelerate data retrieval, meeting users' expectations for fast and responsive applications.
With Flutter's robust caching capabilities and customizable configurations, developers can implement effective strategies to seamlessly integrate image caching into their applications, ensuring smooth user interactions and overall efficiency.
![Michał Kochmański avatar](https://a.storyblok.com/f/202591/512x512/fa3bbabd9b/michal-kochmanski.jpeg/m/1152x1152/filters:format(webp):quality(90))