ARTICLE DETAIL

资讯详情

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

鸿蒙化适配指南:error_or库在Flutter流式错误处理中的应用

鸿蒙化适配指南:error_or库在Flutter流式错误处理中的应用 写这篇鸿蒙化适配指南之前先说说我为什么会对 error_or 这个库上心。做 Flutter 应用这几年业务逻辑里最磨人的不是 UI 渲染而是那些散落在各个回调里的错误分支网络超时、参数非法、本地缓存穿透、权限被拒……每个地方都要手写 if-else 判断写多了代码就变臭。正好前阵子公司开始推进鸿蒙化需要把一批 Flutter 三方库迁到 OpenHarmony 平台上error_or 就是我重点处理的第一个纯 Dart 库。这篇文章把整个适配过程、踩坑记录和业务接入后的效果都整理出来给同样在做 Flutter 鸿蒙化的团队一个可参考的样板。先说结论error_or 这类纯 Dart 的函数式错误处理库鸿蒙化成本比原生插件低得多基本不需要改 Dart 代码但你得把依赖解析、构建验证和业务接入方式搞清楚才能真正发挥它“流式错误处理”的价值。1. 为什么需要流式错误处理error_or 的核心价值1.1 传统错误处理的痛点Flutter 应用里的错误处理最传统的写法就是 try-catch 加返回值判断。比如请求用户信息时要先检查网络是否正常再检查返回数据是否为空再解析 JSON 是否有异常三层嵌套下来代码就变成这样FutureUserProfile? fetchUserProfile() async { try { final response await httpClient.get(/user/profile); if (response.statusCode ! 200) { return null; } final json jsonDecode(response.body); if (json[data] null) { return null; } return UserProfile.fromJson(json[data]); } catch (e) { // 记日志 return null; } }这个方法的调用方拿到 null 之后根本不知道是网络问题、数据格式问题还是服务端返回了异常。更麻烦的是 null 本身就是合法的业务值比如用户确实没设置头像时 data 字段就可能是 null。你用 null 表示错误就等于把“用户没有头像”和“请求失败”这两种完全不同的情况混在了一起后面排查问题只能靠猜。还有一种方案是用自定义异常 catch 分支但异常会打断函数执行的正常流不适合表达“每一步都可能出错”的链式逻辑。比如你要先校验参数再查缓存再走网络三个步骤必须串在一起每个步骤都可能产生不同类型的错误。用 try-catch 写出来就像一大段面条代码可读性极差。1.2 error_or 是什么从 Either 到 ErrorOrerror_or 本质上借鉴了函数式编程里的 Either 类型。它提供的核心类型叫 ErrorOrT一个对象要么承载正常值要么承载错误信息不会同时为空。这样你就不用再用 null 或 -1 这种魔法值去表达异常了。跟 Dart 社区常见的 Either 类型相比error_or 有几个针对 Flutter 场景的优化它把错误设计成可扩展的数据结构而不是只能塞一个字符串。你可以把错误码、错误消息、底层异常、堆栈、额外上下文都放进去。它提供了丰富的链式操作符比如 map、flatMap、onFailure、recover适合流式的错误处理链路。它支持异步版本配合 async/await 用起来很自然不像 RxDart 那样需要引入一整套响应式框架。举一个实际用法例子。改造后的 fetchUserProfileFutureErrorOrUserProfile fetchUserProfile() async { final response await httpClient.get(/user/profile); if (response.statusCode ! 200) { return ErrorOr.failure( businessError: BusinessError( code: HTTP_${response.statusCode}, message: 用户接口返回异常, ), ); } final json jsonDecode(response.body) as MapString, dynamic; return ErrorOr.value(UserProfile.fromJson(json)); }调用方就可以这样处理final result await userRepository.fetchUserProfile(); result.when( (profile) _renderProfile(profile), (error) _showErrorToast(error.message), );一眼就能看出成功和失败两条路径不会再混淆“没数据”和“出错了”。而且你可以把多个 ErrorOr 串联成一组操作错误会自动短路不需要手动判断每一步的返回值。1.3 适合谁用如果你正在做鸿蒙化改造同时又要保证业务代码的稳定性和可维护性那这个库值得你花半天时间接进来。它特别适合这几类场景统一封装网络层、数据库层、文件 IO 层的错误反馈让上层 UI 不用关心底层细节。需要在用户界面上展示“优雅的失败提示”而不是弹出一段丑陋的堆栈。需要在团队内推行一套标准的错误码规范减少不同开发之间随意的字符串错误。正在从 null 判断和异常捕获混用的旧代码迁移到更结构化的错误处理模型。当然如果你的项目只有一两个接口或者团队里没人熟悉函数式概念那引入 error_or 的收益没那么明显。但它作为库本身非常轻量没有原生依赖这就为鸿蒙化打下了很好的基础。2. 鸿蒙化适配准备从 Flutter 工程到 OpenHarmony SDK2.1 适配前的环境清单要把一个 Flutter 三方库适配到鸿蒙首先得清楚你的目标到底是什么。鸿蒙端的 Flutter 支持走的是 OpenHarmony 社区维护的 Flutter SDK 分支这套 SDK 基本沿用了标准 Flutter 的项目结构但增加了 ohos 平台目录和对应的构建产物。我用的是下面这套环境写出来给你参考组件版本 / 说明OpenHarmony SDKAPI 10 及以上建议直接用 DevEco Studio 自带的 SDKFlutter SDK for OpenHarmony建议用社区 3.7 的分支跟随上游 Flutter 版本更新Dart SDK跟随 Flutter SDK无需单独安装DevEco Studio4.0 以上的版本用于编译鸿蒙原生壳构建工具hvigor随 DevEco Studio 分发这里要提醒一下别把鸿蒙的 Flutter SDK 和官方 Flutter SDK 混用。鸿蒙版 Flutter 会在编译时检查目标平台如果你用官方 Flutter 构建 ohos 目标会报找不到 ohos 平台的错误。我当时就是没注意直接在一个老项目里跑flutter build ohos结果折腾了半天才发现是 SDK 分支不对。2.2 判断依赖是纯 Dart 还是含原生代码鸿蒙化适配的难度完全取决于你要适配的包是不是纯 Dart。如果包里没有任何原生平台代码理论上在鸿蒙 Flutter 工程里把它当作普通 pub 依赖就行编译期和运行期都不会牵扯到 Android 或 iOS 的 API。error_or 就是这种情况它没有依赖 flutter/services也没有 plugin 注册逻辑。反过来如果包内部用了dart:ffi、package_info_plus这类平台通道那就需要为鸿蒙单独实现插件接口工作量会大很多。所以适配的第一步就是确认包的纯净度。怎么快速判断打开包的源码目录看一下ls ~/.pub-cache/hosted/pub.dev/error_or-*/如果发现里面有 android/、ios/ 这样的目录说明它带原生组件。error_or 我看了下只有 lib/ 目录和 pubspec.yaml没有任何平台插件目录。再进一步检查 pubspec.yaml 中的 dependencieserror_or 只依赖了 meta 或者更小的基础包没出现 flutter/services也没有 plugin_class 声明。这种包在鸿蒙上几乎不会遇到“找不到原生实现”的问题。2.3 一个关键的 pubspec 检查很多人在适配时忽略了依赖传递问题。error_or 本身适配容易但它依赖的上游包如果有平台特性就会间接影响你。我当时的做法是先列出完整依赖树确认没有隐藏的平台通道依赖flutter pub deps --stylecompact输出结果里能看到 error_or 的依赖项。只要这些依赖不涉及 dart:ui 之外的平台能力就可以放心构建。另外还要注意 Dart SDK 版本兼容。鸿蒙版 Flutter 的 Dart SDK 版本一般比官方稳定版落后一点点而 error_or 可能用到了较新的语法。如果编译报语法错误要么升级鸿蒙 Flutter SDK要么在 pubspec 里锁定一个兼容版本。这个坑在第 3 节详细讲。3. 鸿蒙化适配实操三步让 error_or 跑起来3.1 第一步建立鸿蒙 Flutter 工程骨架如果你已经有一个要鸿蒙化的 Flutter 应用这一步可以跳过。如果是从零开始推荐用鸿蒙版 Flutter SDK 自带的 flutter create 命令创建工程它会自动生成 ohos 目录。命令大概是flutter create --platformsohos,android,ios -e my_app注意--platforms参数里要带上 ohos而且你当前 PATH 里的 flutter 命令必须指向鸿蒙版 SDK。创建成功后工程目录结构大致是my_app/ lib/ ohos/ android/ ios/ pubspec.yamlohos 目录相当于一个完整的鸿蒙原生壳工程里面是 ets、entry、build-profile 等文件。后续调试时用 DevEco Studio 打开 ohos 目录就能跑通鸿蒙原生部分。3.2 第二步调整依赖声明与版本约束接下来处理 pubspec.yaml。通常你想把 error_or 安装到项目里直接flutter pub add error_or这样做的结果是在 dependencies 里生成一行error_or: ^x.y.z。对于纯 Dart 包鸿蒙 Flutter 和标准 Flutter 同样使用 pub 仓库依赖解析规则没有本质区别。但在实际操作中我遇到过两种情况需要手动干预第一种是鸿蒙 Flutter SDK 的 Dart 版本较旧而 error_or 新版要求更高的 Dart 版本。此时 pub 会提示版本兼容冲突解决方式就是把 error_or 锁定到旧版本比如error_or: 1.4.2。锁定后重新flutter pub get看看是否有其他冲突。第二种是如果你使用私有 pub 仓库或镜像需要确认 error_or 是否同步到了你的仓库。我当时因为公司内网用私有仓库pub get 一直失败换成官方仓库源才成功。这个问题在鸿蒙化团队里挺常见的尤其是有多个镜像源的时候。再提一个点鸿蒙化 Flutter 项目里pubspec.yaml 中可能还需要添加一个dependency_overrides来对齐鸿蒙 SDK 需要的特定版本库比如sky_engine或flutter的版本。一般情况下不用但如果你发现编译时出现“flutter SDK 版本不匹配”的警告就可以用这个方式兜底。3.3 第三步构建、运行与单元测试验证依赖装好之后先别急着写业务先跑一次构建验证。鸿蒙 Flutter 工程的构建命令是flutter build ohos --debug如果构建过程中没有报错那 error_or 在编译期已经确认兼容。接下来做单元测试验证。error_or 的逻辑不依赖 UI非常适合跑 dart 单测。我一般在适配后补一个最小的测试用例保证原有代码逻辑在鸿蒙环境下没有行为变化。test(ErrorOr should hold value, () { final result ErrorOr.value(42); expect(result.isSuccess, isTrue); expect(result.value, 42); });运行flutter test test/error_or_smoke_test.dart这一步能快速定位 Dart 运行时行为是否跟标准 Flutter 一致。我在测试时发现鸿蒙上的 Dart VM 对 stack trace 的处理有些差异但 ErrorOr 的纯逻辑不受影响测试顺利通过。最后用真机或模拟器跑一次包含 error_or 的小页面重点验证异步链路的错误触发是否正常。这里可以参考鸿蒙开发里的常规做法用 DevEco Studio 连接模拟器然后在 Flutter 侧flutter run -d device-id启动应用。到这里error_or 的鸿蒙化适配就算完成一大半了。接下来才是真正的重头戏怎么把它用在业务里提升应用的业务反馈质量。4. 业务反馈质量实战用 ErrorOr 改造异步流4.1 改造前后对比我这里拿一个典型的“用户登录后拉取初始化数据”场景举例。登录成功之后客户端要并行请求用户资料、应用配置、未读消息数三个接口。任何一个接口失败都不能让用户感觉“白登录了”而是要在页面上给出清晰、可恢复的提示。传统写法是这样的Futurevoid loadInitData() async { setState(() _loading true); try { final profileFuture fetchUserProfile(); final configFuture fetchAppConfig(); final messageFuture fetchUnreadCount(); await Future.wait([profileFuture, configFuture, messageFuture]); // 真正用数据时还要小心哪些接口返回了 null } catch (e) { _showRetryDialog(初始化失败); } finally { setState(() _loading false); } }问题很明显catch 只能捕获第一个抛出的异常另外两个接口万一也失败根本不知道。而且Future.wait默认是“全部成功才算成功”如果其中一个失败其他成功的数据也会跟着丢弃。用 error_or 改造后每个接口单独返回 ErrorOr再逐个判断既能局部失败也能整体聚合。4.2 构建统一的业务错误模型要让 ErrorOr 真正发挥作用不能只把它当箱子装字符串。我建议先在项目里建一个统一的错误模型放在 core/error 目录下sealed class AppError { const AppError({required this.code, required this.message}); final String code; final String message; } class NetworkError extends AppError { const NetworkError({required String message}) : super(code: NETWORK, message: message); } class ApiError extends AppError { const ApiError({required String statusCode, required String message}) : super(code: API_$statusCode, message: message); } class BizError extends AppError { const BizError({required String code, required String message}) : super(code: code, message: message); }然后给 error_or 定义数据的包装类型比如ErrorOrUserProfile result ErrorOr.tryCatch(() ..., onError: (e) AppError.fromException(e));这样 error_or 里承载的错误永远是我们业务认识的 AppError而不是底层异常类型。UI 层只需要面向 AppError 展示文案和重试逻辑。这个模型的另一个好处是鸿蒙侧的日志服务可以统一读取 code 和 message不用解析异常对象。4.3 与 EventChannel 结合上报错误流刚才提到的热搜词里有 flutter 组件通信和 eventchannel这其实跟错误处理有关系。在鸿蒙应用里Flutter 页面和鸿蒙原生页面经常需要互相通信。比如你把错误抛给了 ErrorOr但用户停留在原生页面你需要让原生页面也能感知到这个错误并弹出一个鸿蒙原生的提示框。这时候可以借助 Flutter 的 EventChannel把 ErrorOr 里的错误信息流式推送到鸿蒙侧。具体做法是在 Flutter Dart 侧定义一个平台通道const _errorStreamChannel EventChannel(com.example.app/error_stream); void startErrorStream() { _errorStreamChannel.receiveBroadcastStream().listen((event) { // 接收来自原生侧的错误事件 final errorMap event as Mapdynamic, dynamic; handleNativeError(errorMap); }); } void sendErrorToNative(AppError error) { const methodChannel MethodChannel(com.example.app/error_channel); methodChannel.invokeMethod(reportError, {code: error.code, message: error.message}); }鸿蒙侧是 ets 的 Module 里注册 EventChannel 对应的 handler。思路很简单Dart 侧触发业务错误之后先交给 ErrorOr 统一处理再把错误模型序列化之后推给原生侧。这样做的好处是错误处理逻辑全部集中在 Dart 层原生层只负责展示和上报职责单一。我实测下来这个方案比在原生侧重复处理错误要高效得多。原来坏了参数、超时、缓存读取失败都要在两边各写一套判断现在只需要在 Dart 侧用 ErrorOr 链式处理然后按需推流。用户看到的反馈提示也更统一了不会再出现“Flutter 页面提示 A原生页面提示 B”的割裂感。4.4 流式错误处理的链式写法ErrorOr 真正的威力在于链式组合。比如你加载用户详情时需要先读本地缓存缓存没有则走网络网络成功再写缓存。每一步都可能失败但失败的原因不同FutureErrorOrUserDetail loadUserDetail(int userId) async { final fromCache await _cache.load(userId); if (fromCache.isSuccess) { return fromCache; } final fromNet await _api.fetchUserDetail(userId); return fromNet.flatMap((detail) async { await _cache.save(userId, detail); return ErrorOr.value(detail); }).recover((error) { // 缓存和网络都失败时尝试用本地兜底副本 return _localBackup.loadSafely(userId); }); }recover这个操作很关键它让错误不再是一句“完了”而是一个可以挽救的机会。你可以把“缓存失败”这个错误捞起来继续尝试网络甚至可以把可恢复错误重新包装成一种“降级成功”的状态UI 上可以提示“当前为离线数据”。这种写法相比层层 try-catch最大的区别是错误被当作数据流中的一个节点而不是程序崩溃的导火索。在鸿蒙应用里用户对“反馈质量”的感知往往很敏感如果网络不好就弹一个干巴巴的“网络错误”对话框很容易被打低分。用 ErrorOr 可以精确控制“什么时候该提示、什么时候该静默重试、什么时候该降级”这些逻辑都可以用一行链式调用表达清楚。5. 常见问题与避坑经验5.1 版本兼容引发的“类型不匹配”鸿蒙 Flutter SDK 的 Dart 版本通常不是最新这就导致 error_or 新版本里用到的Uri解析或者集合操作 API 可能与旧版 Dart 不一致。我当时适配时遇到一个很奇怪的问题代码能通过编译但运行时 error_or 内部抛出的断言错误一直显示“类型不匹配”。排查后发现问题不在 error_or 本身而在我项目里同时引用了另一个依赖把 error_or 依赖的 meta 包升级到了一个不兼容的版本。pub 的依赖解析器在鸿蒙分支上会有不同的冲突处理策略它会选中一个看似兼容但实际 ABI 不一致的版本。建议做法在 pubspec.yaml 中明确锁定 error_or 及其上游 key 依赖尽量不要用^范围升级。比如这样dependencies: error_or: 2.1.0 meta: 1.9.0如果项目里必须依赖高版本 meta再用dependency_overrides统一覆盖。不要嫌麻烦鸿蒙生态里的 pub 解析本来就不如官方 Flutter 成熟多锁定一层少一个隐患。5.2 热重载后 ErrorOr 状态丢失鸿蒙版 Flutter 的热重载Hot Reload整体可用但如果你在 ErrorOr 里保存了某些涉及原生资源的对象比如数据库连接通道、平台通道回调热重载之后可能会出现“上次的错误状态被清空”的假象。这是因为热重载会重建部分 Widget 树但平台的 service instance 可能没有同步重建。所以不要把ErrorOr实例直接塞进 InheritedWidget 或静态变量里。我踩坑之后的做法是错误数据流统一走状态管理比如使用 Cubit 或者 ChangeNotifier 持有 ErrorOr 状态。热重载时状态管理器会重建但 ErrorOr 本身只是纯数据重新获取一次最新数据就能恢复。另外鸿蒙平台上热重载的延迟比模拟器上更高你看到错误提示可能不是实时的。如果要调试错误处理链路别依赖热重载老老实实用flutter run的冷启动。5.3 错误堆栈在鸿蒙平台上被裁剪Dart 在鸿蒙虚拟机上的堆栈信息默认情况下会比标准 Flutter 平台少很多尤其是涉及到 ErrorOr 的 flatMap 回调时可能只剩下“Error or failure”而没有具体业务堆栈。这个问题在排查线上异常时非常致命。我的做法是给 AppError 增加一个stackTrace字段在创建错误时显式保留当前堆栈factory AppError.fromException(Object e, StackTrace st) { return AppError( code: UNKNOWN, message: e.toString(), stackTrace: st, ); }然后把 stackTrace 序列化到日志上报平台。虽然有些堆栈在鸿蒙上仍然不完整但至少保留了 Dart 侧的错误触发点排查时能少走很多弯路。5.4 性能考量不要滥用闭包ErrorOr 的 flatMap 和 map 都接收闭包每个闭包都会创建新的对象。如果你在列表中循环调用大量 ErrorOr 链会产生额外的 GC 压力。鸿蒙设备的性能和安卓中端机差不多我建议在密集计算场景里直接用 switch 表达式代替过长的链式调用。一个优化例子final result ErrorOr.value(list) .flatMap((items) items.map(_parseItem).toList()) .flatMap((parsed) _saveToDb(parsed)) .onFailure((e) _log(e));如果 list 有几百条数据这个链上面会创建很多中间 ErrorOr 实例。更好的做法是把 map 里需要逐项处理的逻辑先普通循环算完最后再用 ErrorOr 包裹结果。error_or 的设计初衷是表达错误流不是用来做集合变换的。5.5 常见问题速查表给一张表方便直接查阅现象可能原因解决方案pub get 卡住或报依赖冲突鸿蒙版 Flutter SDK 的 Dart 版本旧锁定 error_or 旧版本或升级 Flutter SDK 分支编译报 undefined class某个依赖是纯插件但未声明 ohos 实现检查 pubspec hooks改用纯 Dart 替代包运行时报 MissingPluginException误把平台通道当纯逻辑使用确认调用原生接口前Native 侧已注册ErrorOr 热重载后丢失错误状态被静态变量持有改为使用 Cubit / Provider 管理状态堆栈信息缺失鸿蒙 VM 裁剪了 Dart 堆栈显式保存 StackTrace 并上报错误消息 UI 上乱码错误码或消息包含特殊字符统一在 AppError 中控制 message 格式这些坑看着不大但每一条都能耗掉半天时间。我做完这个适配后最大的体会是纯 Dart 三方库鸿蒙化技术难度其实不高真正的复杂度在于你要确保业务侧的使用方式严格遵守“错误即数据”的原则同时在鸿蒙平台上把日志、上报、UI 反馈这些周边设施串起来。最后分享一个小技巧如果你要在鸿蒙 Flutter 工程里持续迭代 error_or 相关逻辑建议在 ohos 目录外再维护一个纯 Dart 的测试工程用来跑快速的单元测试。鸿蒙真机编译一次要一两分钟单元测试秒级完成。我的日常工作流是先在纯 Dart 工程里把业务错误流的逻辑跑通再同步到鸿蒙 Flutter 工程做真机验证。这样既保证了开发效率又能在上真机前排除大部分低级错误。希望这套流程对正在做鸿蒙化改造的你能有些帮助。
返回列表