Open-source project
Baseflow/flutter_cache_manager avatar
Baseflow/flutter_cache_manager

flutter_cache_manager: an HTTP-aware cache for Flutter apps

Generic cache manager for flutter

814 stars512 forksDartMIT

At a glance

What is it?
The flutter_cache_manager package downloads and caches files in the application cache directory and leans on the cache-control header to decide when a cached file is still fresh. The readme is unusually explicit about the awkward parts, such as the fact that a file in the cache is returned first and checked afterwards, and that two managers sharing a key will fight over the same files.
Who is it for?
Adopt flutter_cache_manager if your Flutter application fetches remote files or images repeatedly and you want the caching rules to come from the server's own cache-control header rather than from a policy you invent. Do not adopt it if you need a cache that survives a reinstall or an operating system clean-up, because the default storage is the app's temporary directory and the operating system is free to delete it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The server decides how long a file stays

The package is described as a CacheManager to download and cache files in the cache directory of the app, with various settings on how long to keep a file, and the key line is that it uses the cache-control HTTP header to efficiently retrieve files.

That is the whole idea, and it is a good one. Rather than a library deciding that an image is valid for a day, the freshness comes from the response that carried the file in the first place, which means a static asset server, a CDN and an application server can each state their own policy and the cache follows.

The readme spells out the semantics. A valid URL response should contain a cache-control header, which says in summary for how long the image can be expected to be up to date. The same header family includes an entity tag, which can be used to check, after that time, whether the file did change or is still actually valid.

There is a subtlety in the order of operations that is worth stating plainly, because it surprises people. When a file is in the cache it is always directly returned when calling the single-file or stream method. Only after that is the information checked to see whether the file is still valid, and if it is outdated according to the cache-control headers, the manager tries to update the file and store the new one. With the streaming method, that updated file is also returned in the stream.

So the first call after a file goes stale can return the old file, and the update happens around it. That is a deliberate design, and it is the kind of thing to know about before you write a test that asserts a fresh file on the first request.

The project comes from Baseflow, the package is MIT licensed, and the homepage is baseflow.com.

The API is a set of verbs, and the difference between them is the point

The usage section lists a small set of methods, and choosing the wrong one is the most likely mistake a new user makes.

The easiest way to get a single file is to call the single-file method on the default manager:

dart
var file = await DefaultCacheManager().getSingleFile(url);

The stream variant returns a stream whose first result is the cached file and, later, optionally the downloaded file. Turning the progress flag on adds another behaviour to that stream: it also emits download progress when the file is not found in the cache, which is the case where a progress indicator is actually useful.

Then there are the three methods that do exactly what their names say. Downloading directly from the web bypasses the cache. Retrieving from the cache only returns a file when it is in the cache and returns no file when it is not. And putting a file lets you place a file into the cache without downloading it, which is the one to reach for when the bytes came from somewhere else.

The removal methods are the obvious pair, one to remove a single file and one to empty the cache entirely.

The naming convention is unfashionable and effective. Every method states its source, the network or the cache or neither, so the choice is visible in the call site rather than in a document about caching strategies.

The image mixin is what most applications actually use

There is a mixin, called ImageCacheManager, and if you apply it to a cache manager you get a method for free, and the default manager already has it applied.

The method takes a URL and the usual options, plus a maximum height and a maximum width:

dart
Stream<FileResponse> getImageFile(String url, {
    String key,
    Map<String, String> headers,
    bool withProgress,
    int maxHeight,  // This is extra
    int maxWidth,   // This is extra as well
})

The behaviour is worth reading carefully, because it has a cost. The image from the URL is resized within the given specifications, and the resized image is stored in the cache. It always tries to keep the existing aspect ratio. And the original image is also cached, and used to resize the image if you call the method later with different height and width parameters.

That last sentence is the design decision. Caching the original means a second call with a larger size does not have to download again, it re-resizes from what is already on disk. The trade is disk space, since two copies of every image are kept, and for an application showing the same image at a thumbnail size and a full size, that is usually a good trade.

The readme also points to a sibling package for the case where the files live in Firebase Storage, so the storage backend is not hard wired to the network.

Where the files live, and why the key has to be unique

The frequently asked questions section answers the questions that cause real support issues, and the first one is about storage.

By default the cached files are stored in the temporary directory of the app, which means the operating system can delete them at any time. That single sentence sets the expectations for the whole package. This is a cache, not a store, and on Android the temporary directory is in fact cleaned on an app update, which the readme mentions in the question about removal.

The bookkeeping is separate from the files. Information about the files is stored in a database, using the sqflite package on Android, iOS and macOS, or in a plain JSON file on other platforms. The name of that database is the key of the cache manager, which is exactly why the key has to be unique.

The readme is blunt about the consequence. It is very important not to create more than one cache manager instance with the same key, as these bite each other. That is the failure mode to design around. The customisation example creates the manager as a singleton, and the readme notes you could instead use a provider to provide a manager at the top level of your application. Either way, one manager per key.

So the mental model is: a key names a cache, the key names its database, and two managers with the same key are two writers on one database and one directory.

Two knobs decide expiry, and cleaning happens continuously

The question about when cached files are removed has an answer that mixes library behaviour with operating system behaviour, and the readme is clear that both are in play. The files can be removed by the cache manager or by the operating system, and by default they are stored in a cache folder which is sometimes cleaned, for example on Android with an app update.

Within the library, two variables decide. The maximum number of cache objects, and the stale period. The cache knows when files were last used, and when cleaning the cache, which happens continuously, it deletes files when there are too many, ordered by last use, and when files have simply not been used for longer than the stale period.

The ordering by last use is the detail that makes the policy sensible. A least recently used eviction on a cache directory is the behaviour people expect from an operating system cache, and it means the number of objects acts as a budget on disk rather than as a deadline.

The stale period, on the other hand, is the fallback for files that are never touched again. A file that was downloaded once and never requested again will sit in the cache until the stale period expires, and the readme's customisation example sets that period to seven days while capping the object count at twenty.

Both are settings on the configuration object rather than on the manager itself, which is what makes the customisation section work.

What changed in version 2, and the state of the project

The readme opens with a breaking changes notice for version 2, and it is a good summary of how the library was reshaped.

There is no longer a need to extend a base class. You can call the constructor directly, and the base class is now only an interface, with the concrete manager being the implementation you use. The constructor now expects a configuration object with the settings you are used to, though some of them are slightly different.

The example of a change is where the files live. The system where you want to store your files is not just a dictionary any more, but a file system abstraction, and the readme says that way you have more freedom on where to store your files.

dart
class CustomCacheManager {
  static const key = 'customCacheKey';
  static CacheManager instance = CacheManager(
    Config(
      key,
      stalePeriod: const Duration(days: 7),
      maxNrOfCacheObjects: 20,
      repo: JsonCacheInfoRepository(databaseName: key),
      fileSystem: IOFileSystem(key),
      fileService: HttpFileService(),
    ),
  );
}

Three pieces make up the configuration in that example. The repository is where the metadata lives, here a JSON file repository named after the key. The file system is where the bytes live, and the file service is how bytes are fetched, here over HTTP. Swapping the file service for the Firebase variant is the same substitution, and the readme points at a separate package for it.

On the state of the project, the release list shows a single version, 3.4.0 from August 2024, while the last push was on 2026-10-01. So the code is being maintained and the published version is not moving quickly, which is worth checking against the version your own application resolves before you upgrade.

Editorial conclusion

Adopt flutter_cache_manager if your Flutter application fetches remote files or images repeatedly and you want the caching rules to come from the server's own cache-control header rather than from a policy you invent. Do not adopt it if you need a cache that survives a reinstall or an operating system clean-up, because the default storage is the app's temporary directory and the operating system is free to delete it. Before you integrate, decide how many managers you will create, since two managers with the same key share files and will overwrite each other, and if you serve your own files, make sure the responses carry a cache-control header, because without one the manager has nothing to expire on.

Frequently asked questions

What does the flutter_cache_manager package do?

It downloads and caches files in the cache directory of the app, with settings for how long to keep a file, and it uses the cache-control HTTP header to decide when a cached file is no longer fresh. A valid response should carry that header, and an entity tag in the same family lets the manager check whether the file actually changed.

What is the difference between the flutter_cache_manager methods?

Getting a single file returns it from the cache when it is there and updates it afterwards if it is stale. The stream variant returns the cached file first and later the downloaded one, and with the progress flag it also emits download progress when the file was not cached. Downloading goes straight to the network, cache-only retrieval returns nothing when the file is absent, and there are methods to put a file into the cache, remove one, or empty the cache.

Where does flutter_cache_manager store cached files?

By default in the temporary directory of the app, which means the operating system can delete them at any time, and on Android that directory is cleaned during an app update. Information about the files is kept separately in a database, using sqflite on Android, iOS and macOS and a plain JSON file elsewhere, and that database is named after the cache manager's key.

Why must only one CacheManager exist per key?

Because the key names both the database and the cache directory, so two managers with the same key share them and, in the readme's words, bite each other. The customisation example therefore creates the manager as a singleton, or a provider can supply one at the top level of the application.

How does flutter_cache_manager decide to remove a file?

Two settings control it, the maximum number of cache objects and the stale period. Cleaning runs continuously: files are deleted when there are too many, ordered by last use, and also when they have not been used for longer than the stale period. The operating system can remove them as well.

Official sources

  1. Baseflow/flutter_cache_manager on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/baseflow-flutter-cache-manager.svg)](https://hysenlabs.com/projects/baseflow-flutter-cache-manager)

Was this useful?