Flutter持久化缓存插件persistant_cached的使用
Flutter 持久化缓存插件 persistent_cached
的使用
本README描述了该插件。如果你将此插件发布到pub.dev
,则此README的内容将出现在你的插件首页。
项目概览
- 测试状态:
- 星标数量:
- 包版本:
- 许可证:
- 风格:
注意事项
原始仓库: https://pub.dev/packages/cached
原始仓库的问题在于持久化存储缓存无法正常工作。
Cached
这是一个简单的Dart包,内置代码生成器。它简化并加速了Dart类的缓存机制创建。
最近最少使用 (LRU) 缓存算法
这是一种使用最近最少使用 (LRU) 算法的有限键值映射,其中最近使用的项被“保持活动”,而较旧的、较少使用的项会被移除以腾出空间给新的项。
适用于你希望仅保留常用项或缓存某些API调用的情况。
目录
动机
通常情况下,你需要在内存中缓存一些东西以备后用。常见的案例是缓存一些API调用及其响应。
通常,这会在某个数据层中完成,比如说在RemoteRepository
中。
以下是典型的仓库代码:
class RemoteRepository implements Repository {
final SomeApiDataSource _dataSource;
final SomeResponseType? cachedResponse;
const RemoteRepository(this._dataSource);
@override
Future<SomeResponseType> getSthData() async {
if (cachedResponse != null) {
return cachedResponse!;
}
cachedResponse = await _dataSource.getData();
return cachedResponse!;
}
}
我们可以使用库来简化上述代码:
[@WithCache](/user/WithCache)()
abstract class RemoteRepository implements Repository, _$RemoteRepository {
factory RemoteRepository({required SomeApiDataSource dataSource,}) = _RemoteRepository;
[@Cached](/user/Cached)()
Future<SomeResponseType> getSthData() {
return dataSource.getData();
}
}
设置
安装包
运行以下命令:
flutter pub add --dev cached
flutter pub add cached_annotation
或者手动添加依赖到pubspec.yaml
文件:
dependencies:
cached_annotation:
dev_dependencies:
cached:
现在,你可以编写自己的缓存类啦🎉!
运行生成器
要运行代码生成器,请执行以下命令:
dart run build_runner build
对于Flutter项目,可以运行:
flutter pub run build_runner build
请注意,像大多数代码生成器一样,[Cached] 需要你同时导入注解(如[cached_annotation])并在文件顶部使用part
关键字。
例如,一个想要使用[Cached]的文件会从以下内容开始:
import 'package:cached_annotation/cached_annotation.dart';
part 'some_file.cached.dart';
基础知识
WithCache
[@WithCache](/user/WithCache)
是用于Cached
包的注解。
标记一个类为[@WithCache](/user/WithCache)
将使该类需要由Cached
代码生成器处理。
它还可以接受一个额外的布尔参数useStaticCache
。如果该参数设置为true
,生成器将生成具有静态缓存的缓存类。这意味着每个实例都可以访问相同的缓存。默认值设置为false
。
[@WithCache](/user/WithCache)(useStaticCache: true)
abstract class Gen implements _$Gen {
factory Gen() = _Gen;
...
}
Cached
[@Cached](/user/Cached)
是一个方法/getter装饰器,表示该方法/getter需要由Cached
代码生成器处理。
有四个可能的附加参数:
ttl
- 生存时间。单位为秒。设置缓存存活的时间。默认值为null,表示无限生存时间。syncWrite
- 影响仅异步方法(返回Future的方法)。如果设置为true
,第一次方法调用将被缓存,并且后续调用将从第一次调用的结果中获取。默认值设置为false
;limit
- 限制不同方法调用参数组合的缓存结果数量。默认值为null,表示无限制。where
- 在缓存值之前触发的函数。如果返回true
:值将被缓存;如果返回false
:值将被忽略。可用于指示某些结果不应被缓存。persistentStorage
- 定义是否使用外部持久化存储(如共享偏好设置)。如果设置为true
,则必须在main.dart
文件中设置PersistentStorageHolder.storage
。有关更多信息,请参阅本README的持久化存储部分。
示例
[@Cached](/user/Cached)(
ttl: 60,
syncWrite: true,
limit: 100,
)
Future<int> getInt(String param) {
return Future.value(1);
}
带有getter的示例
@cached
Future<int> get getter {
return Future.value(1);
}
where
如前所述,where
接受一个顶级函数来检查是否应缓存值。它还支持异步调用,因此可以根据HTTP响应解析自由创建条件缓存。
同步示例
[@Cached](/user/Cached)(
ttl: 60,
syncWrite: true,
limit: 100,
where: _shouldCache
)
int getInt(String param) {
return 1;
}
bool _shouldCache(int candidate) {
return candidate > 0;
}
异步示例
[@Cached](/user/Cached)(
where: _asyncShouldCache,
)
Future<http.Response> getDataWithCached() {
return http.get(Uri.parse(_url));
}
Future<bool> _asyncShouldCache(http.Response response) async {
final json = jsonDecode(response.body) as Map<String, dynamic>;
print('Up to you: check conditionally and decide if should cache: $json');
print('For now: always cache');
return true;
}
IgnoreCache
该注解必须位于方法字段之上,并且必须是布尔值,如果为true
,则缓存将被忽略。
示例用法:
@cached
Future<int> getInt(String param, {@ignoreCache bool ignoreCache = false}) {
return Future.value(1);
}
或者可以与useCacheOnError
一起使用,在注解中设置为true
时,则当发生错误时返回最后缓存的值。
@cached
Future<int> getInt(String param, {[@IgnoreCache](/user/IgnoreCache)(useCacheOnError: true) bool ignoreCache = false}) {
return Future.value(1);
}
可能的原因导致生成器报错:
- 如果方法有多处
@ignoreCache
注解
Ignore
该注解必须位于方法字段之上,
带有[@ignore](/user/ignore)
注解的参数在生成缓存键时将被忽略。
示例用法:
@cached
Future<int> getInt([@ignore](/user/ignore) String param) {
return Future.value(1);
}
CacheKey
该注解必须位于方法字段之上,并且必须包含一个常量函数,该函数将根据提供的字段值返回缓存键。
示例用法:
@cached
Future<int> getInt(@CacheKey(exampleCacheFunction) int test) async {
await Future.delayed(Duration(milliseconds: 20));
return test;
}
String exampleCacheFunction(dynamic value) {
return value.toString();
}
你也可以使用@iterableCacheKey
,它将从Iterable<T>
值生成缓存键。
示例用法:
@cached
Future<List<int>> getInt(@iterableCacheKey List<int> test) async {
await Future.delayed(Duration(milliseconds: 20));
return test;
}
ClearCached
方法装饰器,表示该方法需要由Cached
代码生成器处理。
带有此注解的方法可以用于清除带有[@Cached](/user/Cached)
注解的方法的结果。
此注解的构造器可以接受一个可能的参数。它是我们想要清除缓存的方法名称。
假设存在一个现有的缓存方法:
[@Cached](/user/Cached)()
Future<SomeResponseType> getUserData() {
return userDataSource.getData();
}
为了生成清除缓存的方法,我们可以写:
@clearCached
void clearGetUserData();
或者
[@ClearCached](/user/ClearCached)('getUserData')
void clearUserData();
ClearCached
参数或方法名必须对应于缓存方法名。我们也可以创建一个返回bool
的方法,并编写自己的逻辑来检查是否应该清除缓存。
[@ClearCached](/user/ClearCached)('getUserData')
Future<bool> clearUserData() {
return userDataSource.isLoggedOut();
};
如果用户已登出,用户缓存将被清除。
可能的原因导致生成器报错:
- 如果带有
@cached
注解的方法不存在 - 如果配对的方法不存在
- 如果方法不返回
bool
、Future<bool>
或不是void
、Future<void>
ClearAllCached
这与ClearCached
完全相同,只是你不传递任何参数,也不在方法名前添加清除语句,你只需在方法上添加[@clearAllCached](/user/clearAllCached)
注解,此注解将清除类中所有带有[@WithCache](/user/WithCache)
注解的方法的缓存值。
这是一个简单的示例:
[@clearAllCached](/user/clearAllCached)
void clearAllData();
或者我们也可以创建一个返回bool
的方法,并编写自己的逻辑来检查是否清除所有方法的缓存值。
[@clearAllCached](/user/clearAllCached)
Future<bool> clearAllData() {
return userDataSource.isLoggedOut();
}
如果用户已登出,将清除所有方法的缓存值。
可能的原因导致生成器报错:
- 如果我们有太多
clearAllCached
注解,只能有一个 - 如果方法不返回
bool
、Future<bool>
或不是void
StreamedCache
使用[@StreamedCache](/user/StreamedCache)
注解来获取来自缓存方法的缓存更新流。
记得至少提供缓存类方法的名称作为methodName
参数。
简单用法示例:
@cached
int cachedMethod() {
return 1;
}
[@StreamedCache](/user/StreamedCache)(methodName: "cachedMethod", emitLastValue: true)
Stream<int> cachedStream();
带有[@StreamedCache](/user/StreamedCache)
注解的方法应该具有与methodName
参数提供的方法相同的参数(除了[@ignore](/user/ignore)
或@ignoreCache
)。
否则,将抛出InvalidGenerationSourceError
。
示例:
@cached
Future<String> cachedMethod(int x, [@ignore](/user/ignore) String y) async {
await Future.delayed(Duration(milliseconds: 100));
return x.toString();
}
[@StreamedCache](/user/StreamedCache)(methodName: "cachedMethod", emitLastValue: false)
Stream<String> cachedStream(int x);
CachePeek
方法装饰器,表示该方法需要由Cached
代码生成器处理。
带有此注解的方法可以用于窥视带有[@Cached](/user/Cached)
注解的方法的结果。
此注解的构造器可以接受一个可能的参数。它是我们想要窥视缓存的方法名称。
假设存在一个现有的缓存方法:
[@Cached](/user/Cached)()
Future<SomeResponseType> getUserData() {
return userDataSource.getData();
}
为了生成窥视缓存的方法,我们可以写:
[@CachePeek](/user/CachePeek)("getUserData")
SomeResponseType? peekUserDataCache();
CachePeek
methodName参数必须对应于缓存方法名。
可能的原因导致生成器报错:
- 如果多个方法都针对
Cached
方法的缓存 - 如果方法返回类型不正确
- 如果方法的参数与目标函数不同(排除
Ignore
、IgnoreCache
) - 如果方法不是抽象的
DeletesCache
[@DeletesCache](/user/DeletesCache)
注解是一个方法装饰器,标记方法需要由代码生成器处理。带有此注解的方法在成功完成后会清除指定方法的缓存。
如果方法抛出异常,缓存不会被删除并且异常会被重新抛出。
如果有一个缓存方法:
[@Cached](/user/Cached)()
Future<SomeResponseType> getSthData() {
return dataSource.getData();
}
那么可以编写一个影响此方法缓存的方法:
[@DeletesCache](/user/DeletesCache)(['getSthData'])
Future<SomeResponseType> performOperation() {
...
return data;
}
所有在[@DeletesCache](/user/DeletesCache)
注解中指定的方法名称必须对应于有效的缓存方法名。如果performOperation
方法成功完成而不出现错误,则getSthData
的缓存将被清除。
可能的原因导致生成器报错:
- 如果带有
@cached
注解的方法不存在 - 如果没有指定目标方法名
- 如果指定的目标方法无效
- 如果带注解的方法是抽象的
持久化存储
Cached
库通过在[@Cached](/user/Cached)()
注解中传递persistentStorage: true
参数支持使用任何外部存储(如Shared Preferences,Hive):
[@Cached](/user/Cached)(persistentStorage: true)
Future<double> getDouble() async {
return await _source.nextDouble() ;
}
你只需要提供一个合适的接口,扩展CachedStorage
抽象类,例如:
...
import 'package:cached_annotation/cached_annotation.dart';
class MyStorageImpl extends CachedStorage {
final _storage = MyExternalStorage();
@override
Future<Map<String, dynamic>> read(String key) async {
return await _storage.read(key);
}
@override
Future<void> write(String key, Map<String, dynamic> data) async {
await _storage.write(key, data);
}
@override
Future<void> delete(String key) async {
await _storage.delete(key);
}
@override
Future<void> deleteAll() async {
await _storage.deleteAll();
}
}
现在,你必须在main
方法的顶部分配你的类实例:
...
import 'package:cached_annotation/cached_annotation.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
PersistentStorageHolder.storage = await MyStorageImpl();
runApp(const MyApp());
}
正如上面所见,Cached
并不提供任何通用的错误或类型处理方式。它将仅使用PersistentStorageHolder.storage
在生成的代码中保存和读取缓存数据。你必须自己在其内部代码中处理。
保存到持久化存储的数据可以通过使用[@ClearCached](/user/ClearCached)()
、@ClearAllCached()
或[@DeletesCache](/user/DeletesCache)
注解进行删除。
使用持久化存储不会改变库的缓存行为。它只添加了新功能,但可能会改变你实现应用程序的方式:
重要提示
请注意,使用持久化存储需要你在使用Cached
注解时提供异步API。
对于示例项目,请查看cached/example
目录中的persistent_storage_example
。
贡献
我们接受任何对该项目的贡献!
功能请求
- 检查功能是否已经被解决或拒绝
- 描述为什么这很重要
只需创建一个带有标签
enhancement
和描述性标题的问题。然后,提供描述和/或示例代码。 这将帮助社区理解需求。 - 为你的功能编写测试 测试是解释提议功能如何工作的最佳方式。我们要求在合并代码之前提供完整的测试,以确保与现有代码库的一致性。
- 将新功能添加到README中并编写文档 将新功能添加到现有功能表中,并附加使用示例代码。
修复
- 检查错误是否已被发现
- 描述哪里出了问题 报告错误修复的最低要求是一条重现路径。写出应该遵循的步骤来找到代码中的问题。理想情况是,你能提供完整的描述为什么某些代码不起作用以及解决方案代码。
贡献者
完整示例
以下是完整的示例代码,展示了如何使用Cached
库进行持久化缓存。
import 'dart:convert';
import 'package:cached_annotation/cached_annotation.dart';
import 'package:http/http.dart';
/// Do not forget add part keyword for file,
/// because then class not be generated.
part 'gen.cached.dart';
/// You can change url to your own and run main method to check
/// how many time you save with [Cached] package.
///
/// IMPORTANT!
/// response body must be a json string
const _url = 'https://jsonplaceholder.typicode.com/todos/94';
/// Annotating a class with [@WithCache](/user/WithCache) will flag it as a needing
/// to be processed by Cached code generator.
/// It can take one additional boolean parameter useStaticCache.
/// If this parameter is set to true, generator will generate
/// cached class with static cache. It means each instance of this class
/// will have access to the same cache. Default value is set to false
[@WithCache](/user/WithCache)(useStaticCache: true)
abstract class Gen implements _$Gen {
/// Factory constructor
factory Gen() = _Gen;
/// Method decorator that flag it as needing to be processed
/// by Cached code generator.
///
/// There are 4 possible additional parameters:
///
/// * ttl - time to live. In seconds. Set how long cache will be alive.
/// Default value is set to null, means infinitive ttl.
///
/// * syncWrite - Affects only async methods (those one that returns Future)
/// If set to true first method call will be cached, and if
/// following (the same) call will occur, all of them will get
/// result from the first call. Default value is set to false;
///
/// * limit - limit how many results for different method call arguments
/// combination will be cached. Default value null, means no limit.
///
/// * where - function triggered before caching the value.
/// If returns `true`: value will be cached,
/// if returns `false`: cache will not happen.
/// Useful to signal that a certain result must not be cached
/// (e.g. condition whether or not to cache known once acquiring data)
///
/// * persistentStorage - defines optional usage of external persistent
/// storage (e.g. shared preferences)
///
/// If set to `true` in order to work, you have to set
/// `PersistentStorageHolder.storage` in your main.dart
/// file
///
/// Important:
/// If you want to utilize persistent storage, all
/// methods which use Cached library's annotations has
/// to be async
///
/// Additional annotation [@IgnoreCache](/user/IgnoreCache)
///
/// That annotation must be above a field in a method and must be bool,
/// if true the cache will be ignored. Also you can use parameter `useCacheOnError`
/// in the annotation and if set true then return the last cached value
/// when an error occurs.
///
/// Additional annotation [@ignore](/user/ignore)
///
/// Arguments with [@ignore](/user/ignore) annotations will be ignored while generating cache key.
[@Cached](/user/Cached)(
syncWrite: true,
ttl: 30,
limit: 10,
where: _shouldCache,
)
Future<Response> getDataWithCached({
[@IgnoreCache](/user/IgnoreCache)(useCacheOnError: true) bool ignoreCache = false,
}) {
return get(Uri.parse(_url));
}
/// [@Cached](/user/Cached) annotation also works with getters
[@Cached](/user/Cached)(
syncWrite: true,
ttl: 30,
limit: 10,
where: _shouldCache,
)
Future<Response> get getDataWithCachedGetter async => get(Uri.parse(_url));
/// Method for measure example, you can go to example.dart file
/// and run main method, so you can check how great [Checked] package is it.
Future<Response> getDataWithoutCached() {
return get(Uri.parse(_url));
}
/// Method for getting stream of cache updates
[@StreamedCache](/user/StreamedCache)(methodName: "getDataWithCached")
Stream<Response> getDataCacheStream();
/// Method for getting data of cache method
[@CachePeek](/user/CachePeek)("getDataWithCached")
Response? peekDataCache();
/// Method annotated with this annotation can be used to clear result
/// of method annotated with Cached annotation.
///
/// IMPORTANT!
/// the ClearCached argument or method name has to correspond
/// to cached method name.
[@ClearCached](/user/ClearCached)('getDataWithCached')
void clearDataCache();
/// Method annotated with [@DeletesCache](/user/DeletesCache) annotation will clear
/// cached results if it returns with value; however if this method
/// would throw exception, cached data would not be cleared
///
/// IMPORTANT!
/// Method names passed in annotation must correspond to
/// valid cached method names
[@DeletesCache](/user/DeletesCache)(["getDataWithCached"])
Future<int> deletesCache() async {
return 1;
}
/// Method with this annotation will clear cached values for all methods.
[@clearAllCached](/user/clearAllCached)
void clearAllCache();
}
Future<bool> _shouldCache(Response response) async {
final json = jsonDecode(response.body) as Map<String, dynamic>;
print('Up to you : check conditionally and decide if should cache: $json');
print('For now: always cache');
return true;
}
更多关于Flutter持久化缓存插件persistant_cached的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter持久化缓存插件persistant_cached的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用persistent_cache
插件的示例代码。这个插件可以帮助你实现持久化缓存,以便在应用程序重新启动后仍然可以访问缓存的数据。
首先,你需要在你的pubspec.yaml
文件中添加persistent_cache
依赖:
dependencies:
flutter:
sdk: flutter
persistent_cache: ^x.y.z # 请将x.y.z替换为最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,你可以在你的Flutter项目中按如下方式使用persistent_cache
:
- 导入插件
在你的Dart文件中导入persistent_cache
插件:
import 'package:persistent_cache/persistent_cache.dart';
- 初始化缓存
你可以在你的应用启动时初始化缓存。通常,这可以在你的主文件(例如main.dart
)中完成:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化缓存
final cache = await PersistentCache.builder()
.setStorage('my_cache_dir') // 设置缓存目录名称
.build();
// 设置全局缓存实例(可选,但方便后续使用)
PersistentCache.instance = cache;
runApp(MyApp());
}
- 使用缓存
现在,你可以在你的应用中使用这个缓存实例来存储和检索数据。例如,在一个页面或组件中:
import 'package:flutter/material.dart';
import 'package:persistent_cache/persistent_cache.dart';
class CacheExamplePage extends StatefulWidget {
@override
_CacheExamplePageState createState() => _CacheExamplePageState();
}
class _CacheExamplePageState extends State<CacheExamplePage> {
String? cachedValue;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Persistent Cache Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(labelText: 'Enter a value'),
onChanged: (value) {
// 存储值到缓存
PersistentCache.instance?.setValue('my_key', value);
},
),
SizedBox(height: 16),
Text(
'Cached Value: ${cachedValue ?? 'N/A'}',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
// 从缓存中获取值
cachedValue = await PersistentCache.instance?.getValue<String>('my_key');
setState(() {});
},
child: Text('Get Cached Value'),
),
],
),
),
);
}
}
在这个示例中,我们在TextField
的onChanged
回调中将输入的值存储到缓存中,使用PersistentCache.instance?.setValue('my_key', value);
。然后,通过点击按钮,我们可以从缓存中检索这个值,并显示在屏幕上。
请注意,由于persistent_cache
插件的具体API可能会随着版本更新而变化,因此请务必参考最新的插件文档以获取最准确的信息和最佳实践。上述代码是一个基本示例,展示了如何初始化并使用persistent_cache
插件进行数据的持久化缓存。