ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Riverpod 纯 Dart 实战:用 FutureProvider 构建异步 Provider 依赖链(Marvel 漫画示例解读)

Riverpod 纯 Dart 实战:用 FutureProvider 构建异步 Provider 依赖链(Marvel 漫画示例解读) 前端移动开发【免费下载链接】riverpodA reactive caching and>项目地址https://gitcode.com/gh_mirrors/ri/riverpod点击查看免费下载本篇文章基于 Riverpod 官方仓库中的 packages/riverpod/example 示例讲解如何在完全不依赖 Flutter 的纯 Dart 环境中使用 Riverpod通过FutureProvider从一个 JSON 配置文件异步加载 API 密钥再让另一个 Provider 依赖它去请求 Marvel 漫画 API。读完本文你将掌握ProviderContainer的用法、ref.watch(...future)的异步等待模式、ref.onDispose的资源清理以及如何搭建一个可运行、可测试的纯 Dart Riverpod 工程。一、示例定位Riverpod 不止属于 FlutterRiverpod 的核心是一个与 UI 框架解耦的状态与依赖注入层。大多数教程都围绕 Flutter Widget 展开但本示例packages/riverpod/example/lib/main.dart刻意选择了一条更纯粹的路径一个纯 Dart 脚本从 https://developer.marvel.com/ 抓取漫画列表全程不出现任何 Widget、BuildContext 或 Flutter 依赖。示例将整个流程拆分成两个职责单一的对象对象职责加载方式Configuration存储 Marvel 账号的 API 密钥异步读取本地configuration.json文件并解码Repository依赖Configuration连接 Marvel API 请求漫画数据由 Provider 组合而成隐式等待配置就绪两个对象均由 Riverpod 的 Provider 创建恰好覆盖了两个经典场景无 Flutter 使用 Riverpod—— 用ProviderContainer代替 Widget 层持有状态异步 Provider 依赖异步 Provider——Repository的 Provider 通过ref.watch(configurationProvider.future)等待配置加载完成这正是本示例的核心示范点。二、环境准备配置依赖与工程结构2.1 依赖清单示例的依赖声明在 packages/riverpod/example/pubspec.yamlname: riverpod_example publish_to: none environment: sdk: ^3.12.0 resolution: workspace dependencies: crypto: ^3.0.0 dio: 5.11.1 freezed_annotation: ^3.0.0 json_annotation: ^4.9.0 riverpod: dev_dependencies: build_runner: ^2.11.0 freezed: 3.2.6-dev.1 5.0.0 json_serializable: ^6.3.0 test: ^1.16.0各依赖的用途riverpod—— 本文主角提供FutureProvider、ProviderContainer等核心 APIdio—— HTTP 客户端用于请求 Marvel API见Repository.fetchComicscrypto—— 计算 Marvel 认证所需的 MD5 哈希freezed / freezed_annotation—— 生成不可变数据类减少样板代码json_annotation / json_serializable—— 配合 Freezed 完成 JSON 序列化build_runner—— 运行代码生成器生成models.freezed.dart与models.g.dart。注意resolution: workspace该示例是仓库根目录 pubspec.yaml 声明的 Dart workspace 成员见packages/riverpod/example条目因此它直接依赖工作区内的riverpod包源码。若脱离本仓库独立运行你需要将riverpod替换为对 pub.dev 的版本依赖。2.2 运行前置条件创建configuration.json示例运行前必须先在示例目录下创建configuration.json文件该文件已被 packages/riverpod/example/.gitignore 忽略不会入库。README 中明确了目录结构example/ pubspec.yaml README.md configuration.json 放置位置 lib/文件内容格式如下public_key与private_key均需前往 https://developer.marvel.com/account 注册账号后获取{ public_key: 1234, private_key: 5678 }三、数据模型用 Freezed 定义Configuration在 packages/riverpod/example/lib/models.dart 中Configuration通过freezed注解定义。README 特别说明使用 Freezed 并非必需只是为了减少样板代码——你完全可以手写普通类。freezed sealed class Configuration with _$Configuration { JsonSerializable(fieldRename: FieldRename.snake) factory Configuration({ required String publicKey, required String privateKey, }) _Configuration; factory Configuration.fromJson(MapString, Object? json) _$ConfigurationFromJson(json); }关键点JsonSerializable(fieldRename: FieldRename.snake)让 Dart 侧的驼峰命名publicKey、privateKey与 JSON 侧的蛇形命名public_key、private_key自动映射。这一点在生成的 models.g.dart 中看得一清二楚_Configuration _$ConfigurationFromJson(MapString, dynamic json) _Configuration( publicKey: json[public_key] as String, privateKey: json[private_key] as String, );代码生成由 build.yaml 驱动运行dart run build_runner build即可重新生成。如果安装阶段需要手动生成模型代码这就是执行时机。四、Provider 依赖链从配置文件到 HTTP 请求这是整个示例的灵魂。让我们逐段剖析 main.dart 中的两个FutureProvider。4.1configurationProvider异步读取配置文件final configurationProvider FutureProviderConfiguration((_) async { final file await File.fromUri(Uri.file(configuration.json)) // .readAsString(); final map json.decode(file) as MapString, Object?; return Configuration.fromJson(map); });要点Provider 的类型是FutureProviderConfiguration其对外暴露的状态是AsyncValueConfiguration内部则是对应的Future构建函数为async从当前工作目录读取configuration.jsonjson.decode得到MapString, Object?后交给Configuration.fromJson完成解码。4.2repositoryProvider等待另一个异步 Providerfinal repositoryProvider FutureProviderRepository((ref) async { final configs await ref.watch(configurationProvider.future); final repository Repository(configs); ref.onDispose(repository.dispose); return repository; });这里浓缩了本示例想要展示的核心技巧——一个异步 Provider 如何依赖另一个异步加载的 Providerref.watch(configurationProvider.future)future修饰符返回一个解析出首个非 loading 结果的Future详见下文源码分析。用await等待后即可拿到类型安全的Configuration对象无需手动处理 loading/error 分支拿到配置后构造Repository(configs)ref.onDispose(repository.dispose)注册销毁回调——当该 Provider 被销毁时自动释放Repository持有的资源Dio连接同时也会终止尚未完成的 HTTP 请求。4.3 源码印证.future与onDispose的底层实现future修饰符并非魔法其行为定义在 packages/riverpod/lib/src/core/modifiers/future.dart。源码中AsyncProviderListenable接口的文档注释给出的正是与本示例几乎一致的组合用法final configsProvider FutureProvider((ref) async Configs()); final productsProvider FutureProvider((ref) async { // Wait for the configurations to resolve final configs await ref.watch(configsProvider.future); // Do something with the result return await http.get(${configs.host}/products); });同时该接口文档明确了几条语义见 future.dart获取到的Future实例可能随时间变化——当 Provider 发出新的 data/error 或重新进入 loading 时会更换该 Future 并不一定等待build完成如果在build完成前state被修改Future 会以新值解析当状态为 error 时Future 会以AsyncValue.error中的错误失败。ref.onDispose的官方推荐用法同样在 packages/riverpod/lib/src/core/ref.dart 中有详述它用于在 Provider 被销毁时取消挂起的任务例如CancelToken.cancel或在此处关闭 HTTP 客户端。README 原文也强调onDispose会停止挂起的 HTTP 请求。4.4Repository真实调用 Marvel APIRepository类定义在 models.dart构造时接收Configuration内部持有Dio()客户端class Repository { Repository(this._configuration); final Configuration _configuration; final _client Dio(); FutureListComic fetchComics() async { final timestamp DateTime.now().millisecondsSinceEpoch; final hash md5 .convert( utf8.encode( $timestamp${_configuration.privateKey}${_configuration.publicKey}, ), ) .toString(); final result await _client.getMapString, Object?( http://gateway.marvel.com/v1/public/comics, queryParameters: String, Object?{ ts: timestamp, apikey: _configuration.publicKey, hash: hash, }, ); // ...解析响应映射为 ListComic } void dispose() { _client.close(force: true); } }值得注意的工程细节Marvel API 认证要求ts毫秒时间戳、apikey、hash三个参数其中hash md5(ts privateKey publicKey)这正是crypto依赖的用武之地响应解析链路为MarvelResponse→MarvelData→Comic三者均用 Freezed 定义并支持fromJson见 models.dart序列化实现见 models.g.dartdispose()强制关闭 Dio 客户端配合 Provider 的onDispose完成资源释放。五、入口函数ProviderContainer的创建、读取与销毁Futurevoid main() async { // 存放 provider 状态的容器。 // 避免将其做成全局变量以利于测试。 // 如果使用 Flutter则无需这一步。 final container ProviderContainer(); // 获取 Repository。这会隐式地一并加载 Configuration。 final repository await container.read(repositoryProvider.future); final comics await repository.fetchComics(); for (final comic in comics) { print(comic.title); } // 释放与 container 关联的所有 provider。 container.dispose(); }5.1 为什么用ProviderContainer在 Flutter 中Provider 的状态由 Widget 树ProviderScope托管在纯 Dart 中你需要手动创建ProviderContainer来充当状态的存放地。README 特别提醒不要把它声明为全局变量而是作为局部变量创建以便测试时能针对每次用例创建独立容器、互不污染。ProviderContainer定义于 packages/riverpod/lib/src/core/provider_container.dart它实现了MutationTarget接口支持read、listen、watch、dispose等操作。5.2 隐式依赖加载container.read(repositoryProvider.future)读取的是repositoryProvider但由于repositoryProvider内部await ref.watch(configurationProvider.future)Riverpod 会自动触发configurationProvider的构建——因此读取 Repository 的同时配置文件的异步加载也一并完成了。这正是异步 Provider 依赖链的完整闭环依赖关系由框架自动解析、自动等待。5.3read的语义与源码印证ProviderContainer.read的实现在 provider_container.dart它读取 Provider 但不去监听它返回当前暴露的值读取后立即关闭内部订阅。文档中的示例final greetingProvider Provider((_) Hello world); void main() { final container ProviderContainer(); print(container.read(greetingProvider)); // Hello World }而对于异步 Providerread(...future)返回的是代表该 Provider 的FutureFutureProvider均实现了AsyncProviderListenable的future修饰符配合await即可拿到解析后的值。5.4 生命周期收尾container.dispose()见 provider_container.dart会递归销毁整个 Provider 图先销毁子容器/子 Provider再销毁自身。此时repositoryProvider注册的ref.onDispose(repository.dispose)会被触发Dio连接随之关闭。六、运行方式与常见问题6.1 运行步骤在仓库根目录执行dart pub get工作区模式会一并解析所有成员的依赖在 packages/riverpod/example 目录下创建configuration.json填入真实的 Marvelpublic_key与private_key如需修改模型或首次生成代码执行dart run build_runner build生成models.freezed.dart、models.g.dart运行脚本dart run lib/main.dart成功执行后程序会在控制台逐行打印从 Marvel API 拉取到的漫画标题comic.title。6.2 常见问题configuration.json读取失败脚本基于当前工作目录定位文件请确保在示例目录下执行dart run认证失败401检查public_key/private_key是否与 Marvel 账户一致并确认hash生成顺序为ts privateKey publicKey见 models.dart无需 Flutter SDK本示例只依赖 Dart SDKsdk: ^3.12.0验证了 Riverpod 核心包与 UI 框架完全解耦。七、从示例到生产可复用的模式总结这个不到百行的示例浓缩了三个在生产项目中反复出现的 Riverpod 模式异步配置加载用FutureProvider包装文件/网络读取Configuration.fromJson负责解码——配置来源从 JSON 文件换成远程接口、环境变量时只需替换 Provider 内部实现异步依赖组合await ref.watch(otherProvider.future)让依赖链保持类型安全、可读性强且自动获得依赖重建则自身重建的响应式语义资源生命周期管理ref.onDispose与ProviderContainer.dispose()配合确保网络客户端、文件句柄等资源在 Provider 销毁时被确定性释放——这正是 ref.dart 所强调的取消挂起任务的官方推荐路径。若想继续深入可对比仓库内其他示例带 UI 层的 Flutter 用法见 examples/marvel同为 Marvel API 主题、examples/first_appRiverpod 核心 API 的完整导出清单见 packages/riverpod/lib/riverpod.dart。赞分享前端移动开发【免费下载链接】riverpodA reactive caching and>项目地址https://gitcode.com/gh_mirrors/ri/riverpod点击查看免费下载相关推荐用 Riverpod 与 StackOverflow API 构建异步数据绑定客户端StackOverflow 示例应用深度解析用 Riverpod 与 StackOverflow API 构建异步数据绑定客户端StackOverflow 示例应用深度解析 本指南以 Riverpod前端移动开发Aptos Move 链上依赖实战使用 aptos 依赖语法引用链上已发布包pack1 依赖 pack2 最小示例Aptos Move 链上依赖实战使用 aptos 依赖语法引用链上已发布包pack1 依赖 pack2 最小示例 本文以 aptos core 仓库中区块链Web3Swarm测试指南如何编写可靠的分布式系统测试用例Swarm测试指南如何编写可靠的分布式系统测试用例 Swarm是一款为Erlang/Elixir设计的分布式系统工具提供简单的集群管理、进程注册和工作节点分上一篇gmsm性能优化指南如何让你的国密算法应用运行得更快下一篇RTranslator 离线翻译模型部署指南1.2GB 模型不靠下载的落地方法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表