flutter-caching-data by flutter/skills
npx skills add https://github.com/flutter/skills --skill flutter-caching-data根据数据生命周期和大小要求应用适当的缓存机制。
shared_preferences。sqflite、Drift、Hive CE 或 Isar 使用 SQLite)。path_provider 使用文件系统缓存。FlutterEngine。将数据仓库设计为单一数据源,结合本地数据库和远程 API 客户端。
立即产出本地数据以实现快速 UI 渲染,然后获取远程数据,更新本地缓存,并产出新数据。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Stream<UserProfile> getUserProfile() async* {
// 1. 首先产出本地缓存
final localProfile = await _databaseService.fetchUserProfile();
if (localProfile != null) yield localProfile;
// 2. 获取远程数据,更新缓存,产出新数据
try {
final remoteProfile = await _apiClientService.getUserProfile();
await _databaseService.updateUserProfile(remoteProfile);
yield remoteProfile;
} catch (e) {
// 处理网络故障;UI 已有本地数据
}
}
根据数据的关键性确定写入策略:
在您的数据模型中添加一个 synchronized 布尔标志。运行一个周期性的后台任务(例如通过 workmanager 或 Timer),将未同步的本地更改推送到服务器。
使用 path_provider 来定位正确的目录。
使用 getApplicationDocumentsDirectory() 获取持久化数据目录。
使用 getTemporaryDirectory() 获取操作系统可以清理的缓存数据目录。
Future<File> get _localFile async { final directory = await getApplicationDocumentsDirectory(); return File('${directory.path}/cache.txt'); }
使用 sqflite 进行关系型数据缓存。始终使用 whereArgs 来防止 SQL 注入。
Future<void> updateCachedRecord(Record record) async {
final db = await database;
await db.update(
'records',
record.toMap(),
where: 'id = ?',
whereArgs: [record.id], // 切勿在此处使用字符串插值
);
}
图片 I/O 和解压缩开销很大。
cached_network_image 包来处理远程图片的文件系统缓存。ImageProvider,请重写 createStream() 和 resolveStreamForKey() 方法,而不是已弃用的 resolve() 方法。ImageCache.maxByteSize 不再为大型图片自动扩展。如果加载的图片大于默认缓存大小,请手动增加 ImageCache.maxByteSize 或子类化 ImageCache 以实现自定义的淘汰逻辑。为可滚动小部件(ListView、GridView、Viewport)配置缓存时,请使用 scrollCacheExtent 属性配合 ScrollCacheExtent 对象。不要使用已弃用的 cacheExtent 和 cacheExtentStyle 属性。
// 正确实现
ListView(
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
children: // ...
)
Viewport(
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
slivers: // ...
)
Widget 对象上重写 operator ==。这会导致重建期间出现 O(N²) 行为。operator ==,前提是比较属性的速度明显快于重建,并且属性很少更改。const 构造函数,以允许框架自动短路重建。为了在将 Flutter 添加到现有 Android 应用时消除 FlutterEngine 不可忽视的预热时间,请预热并缓存引擎。
Application 类中实例化并预热引擎。FlutterEngineCache 中。FlutterActivity 或 FlutterFragment 中使用 withCachedEngine 检索它。// 1. 在 Application 类中预热
val flutterEngine = FlutterEngine(this)
flutterEngine.navigationChannel.setInitialRoute("/cached_route")
flutterEngine.dartExecutor.executeDartEntrypoint(DartEntrypoint.createDefault())
// 2. 缓存引擎
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
// 3. 在 Activity/Fragment 中使用
startActivity(
FlutterActivity.withCachedEngine("my_engine_id").build(this)
)
注意:使用缓存引擎时,无法通过 Activity/Fragment 构建器设置初始路由。在执行 Dart 入口点之前,在引擎的导航通道上设置初始路由。
按照以下步骤实现健壮的离线优先数据层。
synchronized 布尔标志(默认 false)的数据模型。DatabaseService(SQLite/Hive)。ApiClientService。Repository 类。Stream<T> 的读取方法(产出本地数据,获取远程数据,更新本地数据,产出远程数据)。synchronized 标志)。synchronized == false 的记录。按照以下步骤缓存 FlutterEngine 以实现无缝的 Android 集成。
Application 类(如果不存在则创建一个,并在 AndroidManifest.xml 中注册)。FlutterEngine。navigationChannel.setInitialRoute() 设置初始路由。dartExecutor.executeDartEntrypoint() 执行 Dart 入口点。FlutterEngineCache.getInstance().put() 中。FlutterActivity 或 FlutterFragment 以使用 .withCachedEngine("id")。每周安装量
1.8K
代码仓库
GitHub 星标数
784
首次出现
11 天前
安全审计
安装于
codex1.8K
gemini-cli1.8K
opencode1.8K
github-copilot1.8K
kimi-cli1.8K
cursor1.8K
Apply the appropriate caching mechanism based on the data lifecycle and size requirements.
shared_preferences.sqflite, Drift, Hive CE, or Isar).path_provider.FlutterEngine.Design repositories as the single source of truth, combining local databases and remote API clients.
Yield local data immediately for fast UI rendering, then fetch remote data, update the local cache, and yield the fresh data.
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
}
}
Determine the write strategy based on data criticality:
Add a synchronized boolean flag to your data models. Run a periodic background task (e.g., via workmanager or a Timer) to push unsynchronized local changes to the server.
Use path_provider to locate the correct directory.
Use getApplicationDocumentsDirectory() for persistent data.
Use getTemporaryDirectory() for cache data the OS can clear.
Future<File> get _localFile async { final directory = await getApplicationDocumentsDirectory(); return File('${directory.path}/cache.txt'); }
Use sqflite for relational data caching. Always use whereArgs to prevent SQL injection.
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
);
}
Image I/O and decompression are expensive.
cached_network_image package to handle file-system caching of remote images.ImageProvider, override createStream() and resolveStreamForKey() instead of the deprecated resolve() method.ImageCache.maxByteSize no longer automatically expands for large images. If loading images larger than the default cache size, manually increase ImageCache.maxByteSize or subclass ImageCache to implement custom eviction logic.When configuring caching for scrollable widgets (ListView, GridView, Viewport), use the scrollCacheExtent property with a ScrollCacheExtent object. Do not use the deprecated cacheExtent and cacheExtentStyle properties.
// Correct implementation
ListView(
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
children: // ...
)
Viewport(
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
slivers: // ...
)
operator == on Widget objects. It causes O(N²) behavior during rebuilds.operator == only on leaf widgets (no children) where comparing properties is significantly faster than rebuilding, and the properties rarely change.const constructors to allow the framework to short-circuit rebuilds automatically.To eliminate the non-trivial warm-up time of a FlutterEngine when adding Flutter to an existing Android app, pre-warm and cache the engine.
Application class.FlutterEngineCache.withCachedEngine in the FlutterActivity or FlutterFragment.// 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.
Follow these steps to implement a robust offline-first data layer.
synchronized boolean flag (default false).DatabaseService (SQLite/Hive) with CRUD operations.ApiClientService for network requests.Repository class combining both services.Stream<T> (yield local, fetch remote, update local, yield remote).synchronized flag).synchronized == false.Follow these steps to cache the FlutterEngine for seamless Android integration.
Application class (create one if it doesn't exist and register in AndroidManifest.xml).FlutterEngine.navigationChannel.setInitialRoute().dartExecutor.executeDartEntrypoint().FlutterEngineCache.getInstance().put().FlutterActivity or FlutterFragment to use .withCachedEngine("id").Weekly Installs
1.8K
Repository
GitHub Stars
784
First Seen
11 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex1.8K
gemini-cli1.8K
opencode1.8K
github-copilot1.8K
kimi-cli1.8K
cursor1.8K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装