Flutter持久化缓存插件persistant_cached的使用

Flutter 持久化缓存插件 persistent_cached 的使用

本README描述了该插件。如果你将此插件发布到pub.dev,则此README的内容将出现在你的插件首页。

项目概览

Cached Logo

  • 测试状态: Test Status
  • 星标数量: Stars
  • 包版本: Pub Package
  • 许可证: License
  • 风格: Style

注意事项

原始仓库: 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注解的方法不存在
  • 如果配对的方法不存在
  • 如果方法不返回boolFuture<bool>或不是voidFuture<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注解,只能有一个
  • 如果方法不返回boolFuture<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方法的缓存
  • 如果方法返回类型不正确
  • 如果方法的参数与目标函数不同(排除IgnoreIgnoreCache
  • 如果方法不是抽象的

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

1 回复

更多关于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

  1. 导入插件

在你的Dart文件中导入persistent_cache插件:

import 'package:persistent_cache/persistent_cache.dart';
  1. 初始化缓存

你可以在你的应用启动时初始化缓存。通常,这可以在你的主文件(例如main.dart)中完成:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化缓存
  final cache = await PersistentCache.builder()
      .setStorage('my_cache_dir') // 设置缓存目录名称
      .build();

  // 设置全局缓存实例(可选,但方便后续使用)
  PersistentCache.instance = cache;

  runApp(MyApp());
}
  1. 使用缓存

现在,你可以在你的应用中使用这个缓存实例来存储和检索数据。例如,在一个页面或组件中:

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'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们在TextFieldonChanged回调中将输入的值存储到缓存中,使用PersistentCache.instance?.setValue('my_key', value);。然后,通过点击按钮,我们可以从缓存中检索这个值,并显示在屏幕上。

请注意,由于persistent_cache插件的具体API可能会随着版本更新而变化,因此请务必参考最新的插件文档以获取最准确的信息和最佳实践。上述代码是一个基本示例,展示了如何初始化并使用persistent_cache插件进行数据的持久化缓存。

回到顶部