flutter-caching-data
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseImplementing Flutter Caching and Offline-First Architectures
实现Flutter缓存与离线优先架构
Contents
目录
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
根据数据生命周期和大小需求,选用合适的缓存机制。
- 若存储小型、非关键的UI状态或偏好设置: 使用。
shared_preferences - 若存储大型、结构化数据集: 使用本地数据库(通过、Drift、Hive CE或Isar操作SQLite)。
sqflite - 若存储二进制数据或大型媒体文件: 通过使用文件系统缓存。
path_provider - 若保留用户会话状态(导航、滚动位置): 实现Flutter内置的状态恢复功能,将Element树与引擎同步。
- 若优化Android初始化速度: 预加载并缓存。
FlutterEngine
Implementing Offline-First Data Synchronization
实现离线优先的数据同步
Design repositories as the single source of truth, combining local databases and remote API clients.
将仓库设计为单一数据源,结合本地数据库与远程API客户端。
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
}
}立即返回本地数据以实现快速UI渲染,随后获取远程数据、更新本地缓存并返回最新数据。
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.
根据数据的重要性选择写入策略:
- 若要求严格与服务器同步(仅在线): 先尝试API调用,仅当调用成功时才更新本地数据库。
- 若优先保障离线可用性(离线优先): 立即写入本地数据库,再尝试API调用。若调用失败,标记本地记录以便后台同步。
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.
synchronizedworkmanagerTimer在数据模型中添加布尔标记。通过定期后台任务(如或)将未同步的本地变更推送到服务器。
synchronizedworkmanagerTimerManaging File System and SQLite Persistence
管理文件系统与SQLite持久化
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');
}使用定位正确的目录。
path_provider- 使用存储持久化数据。
getApplicationDocumentsDirectory() - 使用存储可被系统清理的缓存数据。
getTemporaryDirectory()
dart
Future<File> get _localFile async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/cache.txt');
}SQLite Persistence
SQLite持久化
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
);
}使用进行关系型数据缓存。务必使用以防止SQL注入。
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
优化UI、滚动与图片缓存
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
图片I/O与解压缩开销较大。
- 使用包处理远程图片的文件系统缓存。
cached_network_image - 自定义ImageProviders: 若实现自定义,请重写
ImageProvider和createStream()方法,而非已废弃的resolveStreamForKey()方法。resolve() - 缓存大小: 不再自动为大图片扩容。若加载的图片超过默认缓存大小,需手动增大
ImageCache.maxByteSize,或继承ImageCache.maxByteSize实现自定义淘汰逻辑。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: // ...
)为可滚动组件(、、)配置缓存时,使用属性搭配对象。请勿使用已废弃的和属性。
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
- 避免在对象上重写
Widget,这会导致重建时出现O(N²)的性能问题。operator == - 例外情况: 仅可在无子组件的叶子Widget上重写,且仅当比较属性的速度远快于重建、且属性极少变更时才适用。
operator == - 优先使用构造函数,让框架自动跳过不必要的重建。
const
Caching the FlutterEngine (Android)
缓存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.
在现有Android应用中集成Flutter时,为消除的显著预热时间,可预加载并缓存引擎。
FlutterEngine- 在类中实例化并预加载引擎。
Application - 将其存储到中。
FlutterEngineCache - 在或
FlutterActivity中通过FlutterFragment获取引擎。withCachedEngine
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)
)注意:使用缓存引擎时,无法通过Activity/Fragment的构建器设置初始路由。需在执行Dart入口点前,通过引擎的navigationChannel设置初始路由。
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
遵循以下步骤构建健壮的离线优先数据层。
- 任务进度:
- 定义包含布尔标记(默认
synchronized)的数据模型。false - 实现本地(基于SQLite/Hive),包含CRUD操作。
DatabaseService - 实现用于网络请求的远程。
ApiClientService - 创建结合两种服务的类。
Repository - 实现返回的读取方法(先返回本地数据,再获取远程数据、更新本地、返回远程数据)。
Stream<T> - 实现写入方法(先写入本地,再尝试远程调用,更新标记)。
synchronized - 实现后台同步函数,处理的记录。
synchronized == false - 运行验证器 -> 检查错误 -> 修复(通过禁用网络测试离线行为)。
- 定义包含
Workflow: Pre-warming the Android FlutterEngine
工作流程:预加载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
遵循以下步骤缓存FlutterEngine,实现流畅的Android集成。
- 任务进度:
- 找到Android的类(若不存在则创建并在
Application中注册)。AndroidManifest.xml - 实例化新的。
FlutterEngine - (可选)通过设置初始路由。
navigationChannel.setInitialRoute() - 通过执行Dart入口点。
dartExecutor.executeDartEntrypoint() - 将引擎存储到中。
FlutterEngineCache.getInstance().put() - 修改目标或
FlutterActivity,使其使用FlutterFragment。.withCachedEngine("id") - 运行验证器 -> 检查错误 -> 修复(验证切换过程中是否出现空白屏幕)。
- 找到Android的