flutter-caching-data
Original:🇺🇸 English
Translated
Implements caching strategies for Flutter apps to improve performance and offline support. Use when retaining app data locally to reduce network requests or speed up startup.
151installs
Sourceflutter/skills
Added on
NPX Install
npx skill4agent add flutter/skills flutter-caching-dataTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Implementing Flutter Caching and Offline-First Architectures
Contents
- Selecting a Caching Strategy
- Implementing Offline-First Data Synchronization
- Managing File System and SQLite Persistence
- Optimizing UI, Scroll, and Image Caching
- Caching the FlutterEngine (Android)
- Workflows
Selecting a Caching Strategy
Apply the appropriate caching mechanism based on the data lifecycle and size requirements.
- If storing small, non-critical UI states or preferences: Use .
shared_preferences - If storing large, structured datasets: Use on-device databases (SQLite via , Drift, Hive CE, or Isar).
sqflite - If storing binary data or large media: Use file system caching via .
path_provider - If retaining user session state (navigation, scroll positions): Implement Flutter's built-in state restoration to sync the Element tree with the engine.
- If optimizing Android initialization: Pre-warm and cache the .
FlutterEngine
Implementing Offline-First Data Synchronization
Design repositories as the single source of truth, combining local databases and remote API clients.
Read Operations (Stream Approach)
Yield local data immediately for fast UI rendering, then fetch remote data, update the local cache, and yield the fresh data.
dart
Stream<UserProfile> getUserProfile() async* {
// 1. Yield local cache first
final localProfile = await _databaseService.fetchUserProfile();
if (localProfile != null) yield localProfile;
// 2. Fetch remote, update cache, yield fresh data
try {
final remoteProfile = await _apiClientService.getUserProfile();
await _databaseService.updateUserProfile(remoteProfile);
yield remoteProfile;
} catch (e) {
// Handle network failure; UI already has local data
}
}Write Operations
Determine the write strategy based on data criticality:
- If strict server synchronization is required (Online-only): Attempt the API call first. Only update the local database if the API call succeeds.
- If offline availability is prioritized (Offline-first): Write to the local database immediately. Attempt the API call. If the API call fails, flag the local record for background synchronization.
Background Synchronization
Add a boolean flag to your data models. Run a periodic background task (e.g., via or a ) to push unsynchronized local changes to the server.
synchronizedworkmanagerTimerManaging File System and SQLite Persistence
File System Caching
Use to locate the correct directory.
path_provider- Use for persistent data.
getApplicationDocumentsDirectory() - Use for cache data the OS can clear.
getTemporaryDirectory()
dart
Future<File> get _localFile async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/cache.txt');
}SQLite Persistence
Use for relational data caching. Always use to prevent SQL injection.
sqflitewhereArgsdart
Future<void> updateCachedRecord(Record record) async {
final db = await database;
await db.update(
'records',
record.toMap(),
where: 'id = ?',
whereArgs: [record.id], // NEVER use string interpolation here
);
}Optimizing UI, Scroll, and Image Caching
Image Caching
Image I/O and decompression are expensive.
- Use the package to handle file-system caching of remote images.
cached_network_image - Custom ImageProviders: If implementing a custom , override
ImageProviderandcreateStream()instead of the deprecatedresolveStreamForKey()method.resolve() - Cache Sizing: The no longer automatically expands for large images. If loading images larger than the default cache size, manually increase
ImageCache.maxByteSizeor subclassImageCache.maxByteSizeto implement custom eviction logic.ImageCache
Scroll Caching
When configuring caching for scrollable widgets (, , ), use the property with a object. Do not use the deprecated and properties.
ListViewGridViewViewportscrollCacheExtentScrollCacheExtentcacheExtentcacheExtentStyledart
// Correct implementation
ListView(
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
children: // ...
)
Viewport(
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
slivers: // ...
)Widget Caching
- Avoid overriding on
operator ==objects. It causes O(N²) behavior during rebuilds.Widget - Exception: You may override only on leaf widgets (no children) where comparing properties is significantly faster than rebuilding, and the properties rarely change.
operator == - Prefer using constructors to allow the framework to short-circuit rebuilds automatically.
const
Caching the FlutterEngine (Android)
To eliminate the non-trivial warm-up time of a when adding Flutter to an existing Android app, pre-warm and cache the engine.
FlutterEngine- Instantiate and pre-warm the engine in the class.
Application - Store it in the .
FlutterEngineCache - Retrieve it using in the
withCachedEngineorFlutterActivity.FlutterFragment
kotlin
// 1. Pre-warm in Application class
val flutterEngine = FlutterEngine(this)
flutterEngine.navigationChannel.setInitialRoute("/cached_route")
flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint.createDefault())
// 2. Cache the engine
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
// 3. Use in Activity/Fragment
startActivity(
FlutterActivity.withCachedEngine("my_engine_id").build(this)
)Note: You cannot set an initial route via the Activity/Fragment builder when using a cached engine. Set the initial route on the engine's navigation channel before executing the Dart entrypoint.
Workflows
Workflow: Implementing an Offline-First Repository
Follow these steps to implement a robust offline-first data layer.
- Task Progress:
- Define the data model with a boolean flag (default
synchronized).false - Implement the local (SQLite/Hive) with CRUD operations.
DatabaseService - Implement the remote for network requests.
ApiClientService - Create the class combining both services.
Repository - Implement the read method returning a (yield local, fetch remote, update local, yield remote).
Stream<T> - Implement the write method (write local, attempt remote, update flag).
synchronized - Implement a background sync function to process records where .
synchronized == false - Run validator -> review errors -> fix (Test offline behavior by disabling network).
- Define the data model with a
Workflow: Pre-warming the Android FlutterEngine
Follow these steps to cache the FlutterEngine for seamless Android integration.
- Task Progress:
- Locate the Android class (create one if it doesn't exist and register in
Application).AndroidManifest.xml - Instantiate a new .
FlutterEngine - (Optional) Set the initial route via .
navigationChannel.setInitialRoute() - Execute the Dart entrypoint via .
dartExecutor.executeDartEntrypoint() - Store the engine in .
FlutterEngineCache.getInstance().put() - Update the target or
FlutterActivityto useFlutterFragment..withCachedEngine("id") - Run validator -> review errors -> fix (Verify no blank screen appears during transition).
- Locate the Android