做 Flutter 开发这么多年,我一直觉得跨端框架最大的价值不在于“一次编写、处处运行”这句口号,而在于它能把成熟的 UI 渲染、状态管理、热重载这些工程化能力带进一个新平台。OpenHarmony 生态这两年变化很快,社区里已经有 Flutter for OpenHarmony 的适配方案,配合生活助手这类高频工具型 App,正好是验证这套跨端能力的最好场景。这篇文章就记录我从零搭建 Flutter for OpenHarmony 生活助手 App、重点实现待办事项管理模块的完整过程。
这次实战的核心目标是做一个轻量但功能完整的生活助手,第一版只聚焦待办事项管理:展示列表、新建编辑、标记完成、删除恢复、按日期排序,再加一个系统通知提醒。技术选型上使用 Flutter 作为 UI 框架,通过 ohos 平台目录适配 OpenHarmony 设备,本地存储用 shared_preferences 的鸿蒙适配版本,状态管理用 Cubit。如果你有 Flutter 基础,想了解怎么把现有技能迁移到 OpenHarmony,或者正在规划自己的第一款鸿蒙 App,这篇文章应该能帮你少踩不少坑。
1. 立项思路与需求拆解
1.1 为什么把生活助手落脚在待办事项
生活助手这个概念其实可以很大:天气、日历、记账、习惯打卡、备忘录,每一项拿出来都是独立产品。但作为个人实战项目,最怕的是贪多嚼不烂。我一开始列了五个模块,实际排期后发现以个人业余时间估算,三个月都未必能把交互打磨到能用的状态。所以果断砍到只剩一个“待办事项管理”,其余模块全部推迟到第二期。
选待办事项作为切入点,原因有三点。第一,它是典型的 CRUD 应用,新增、查询、修改、删除全部覆盖,适合用来验证 Flutter 在 OpenHarmony 上的基础组件和生命周期是否完整可用。第二,它包含状态变化,比如完成/未完成切换、列表排序变化,正好用来测试状态管理方案在鸿蒙平台的性能表现。第三,它有与系统交互的需求,比如设置提醒时间后触发通知,这一点能逼迫我去打通 Flutter 与 OpenHarmony 的原生通道,而不是只写纯 Dart 层的 demo。
1.2 目标用户与核心使用场景
我给这个 App 定义的使用场景很朴素:普通人日常记几件重要的事,比如下班取快递、晚上给花浇水、周末交水电费。用户打开 App 的第一诉求是“今天有什么还没做完”,所以首页不需要广告和花哨的推荐位,而是要一眼看到未完成待办的优先级排序。移动端的典型操作路径是“进入列表 → 勾选完成任务 → 滑掉过期事项”,整个流程要能在单手操作下三秒内完成。
因此交互设计上我做了几个关键取舍:新建入口常驻底部按钮;完成操作通过点击左侧复选框实现而不是左滑菜单,因为复选框的触达成本更低;删除操作采用左滑露出按钮,同时用 SnackBar 提供撤销能力,防止误删。这个设计决策直接影响了后续 Widget 层的实现,后文会详细讲。
1.3 待办事项的数据模型设计
一开始就把字段定义清楚,后面能少返工很多。我的待办项模型包含这些字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 唯一标识,用时间戳加随机数生成 |
| title | String | 待办标题,必填,不超过50字 |
| note | String? | 备注说明,可空 |
| isDone | bool | 是否完成 |
| createdAt | DateTime | 创建时间 |
| dueDate | DateTime? | 截止时间,用于排序和提醒 |
| remindAt | DateTime? | 提醒时间,可选 |
| priority | int | 优先级 0-2,默认0 |
这里有一个容易被忽略的点:remindAt 和 dueDate 是两个不同语义的字段。截止时间只是展示在列表上提醒用户“别过期”,而提醒时间才真正触发系统通知。如果混成一个字段,就会出现“我把截止时间设到明天早上,结果半夜给我弹通知”的尴尬情况,所以从数据模型上就要分离。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与工程初始化
2.1 Flutter for OpenHarmony 的工具链准备
Flutter for OpenHarmony 的适配方案目前已经有比较明确的分支管理,我用的方式是安装带 ohos 平台支持的 Flutter SDK,配合 DevEco Studio 里配套的 OpenHarmony SDK。老实说,这套环境第一次配起来比 Android 要繁琐一些,主要原因是 OpenHarmony 的 SDK 目录结构、hb 工具链以及鸿蒙应用包的签名机制都是独立体系,不能照搬 Android 的经验。
我的安装顺序是这样的:先装 DevEco Studio 和 OpenHarmony SDK,保证 SDK 路径能被系统环境变量找到;再下载适配 ohos 的 Flutter SDK 版本,配好 PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL(国内网络环境实测不配置这两个镜像源会慢到怀疑人生);最后用 flutter doctor 检查 SDK 是否识别到 ohos 平台。实际上 flutter doctor 对 ohos 的支持在早期版本并不完整,我当时是直接运行 flutter config --enable-ohos-desktop 之类命令手工启用,这一步不同分支差异很大,建议以你下载的 SDK 内附文档为准。
2.2 使用 flutter create 生成三端工程
环境没问题之后,创建项目这一步我已经形成肌肉记忆了。命令如下:
bash复制flutter create --org com.example --project-name todo_app --platforms ohos,android,ios todo_app
注意我同时保留了 android 和 ios 目录,目的很明确:开发调试时先用 Android 模拟器跑通 UI 和数据逻辑,鸿蒙真机主要用于做平台能力和性能验证。这样可以规避早期 ohos 桌面模拟器不稳定的干扰,把 UI 迭代速度提上去。
项目生成后,工程里会多出一个 ohos 目录,它的结构和 Android 的 android 目录非常类似——有自己的 entry 模块、build-profile.json5、oh-package.json5 这些鸿蒙工程文件。要在 Flutter 层调用鸿蒙原生能力,需要在 entry/src/main/ets 下写相关逻辑,后面讲 EventChannel 时再展开。
2.3 依赖管理策略与 pubspec.yaml 配置
创建完项目的第一件事就是改 pubspec.yaml。因为核心是本地存储和状态管理,依赖控制在最小集合。我的做法是只引入真正用得上的包,减少鸿蒙适配验证的工作量:
yaml复制dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.0
provider: ^6.1.1
flutter_bloc: ^8.1.3
intl: ^0.18.1
这里有个实战经验:版本号不能盲目选最新。Flutter for OpenHarmony 的插件生态依赖 OpenHarmony SDK 的版本兼容,我在尝试 shared_preferences 更新版本时遇到过编译期找不到 ohos 平台实现的问题,最后锁定在社区验证过的版本上。另外,为了管理大型列表页面的代码,我用了 Dart 的 part 和 part of 语法来拆分文件,后面数据层实现里会展示用法。这种拆分方式在常规 Flutter 项目里用得不多,但如果你像我一样习惯把 UI、状态、数据访问分成独立文件,part 比 import 更合适处理有私有成员互相引用的场景。
3. 数据层设计:本地存储方案
3.1 shared_preferences 还是 SQLite
待办事项数据量很小,正常用户一年也积累不到一万条,所以本地存储选型上我一度很纠结。SQLite 方案需要引入 sqflite 或 drift 的鸿蒙适配,表结构设计和迁移脚本都要写,对于一个以练手为主的项目这属于过度设计。而纯文件存储虽然简单,但 JSON 序列化和反序列化的可靠性、并发写的问题都需要自己处理。
权衡后我选了 shared_preferences,理由有三个:一是官方适配成本最低,OpenHarmony 版本直接依赖鸿蒙的轻量偏好存储,接口与标准版一致;二是存储结构可以设计成“一条记录一个 JSON 字符串,整体作为字符串列表”,既能满足排序,也能快速整体重写;三是它天然是异步接口,符合 UI 线程的约束。下面是数据访问层的核心代码:
dart复制class TodoRepository {
static const _kTodoItems = 'todo_items';
Future<List<TodoItem>> loadItems() async {
final prefs = await SharedPreferences.getInstance();
final rawList = prefs.getStringList(_kTodoItems) ?? [];
return rawList.map((e) {
final map = jsonDecode(e) as Map<String, dynamic>;
return TodoItem.fromJson(map);
}).toList();
}
Future<void> saveItems(List<TodoItem> items) async {
final prefs = await SharedPreferences.getInstance();
final rawList = items.map((e) => jsonEncode(e.toJson())).toList();
await prefs.setStringList(_kTodoItems, rawList);
}
}
这段代码的核心思路是整体重写式持久化:每次增删改之后都拿全量列表存一遍。数据量小的时候这种策略完全可行,胜在逻辑简单不易出错。要是以后事项数量涨到几千条,再切换成 drift 也不迟——到时候只需要替换 Repository 的实现,上层 UI 不用动。
3.2 用 part 组织文件,保持工程清爽
待办模块涉及模型、仓库、状态管理、事件、UI 五个部分,文件如果全是 import 相对路径会显得乱。我这里用到了 part / part of:
dart复制// todo_models.dart
part of 'todo_data.dart';
class TodoItem {
final String id;
final String title;
final String? note;
final bool isDone;
final DateTime createdAt;
final DateTime? dueDate;
final DateTime? remindAt;
final int priority;
TodoItem({
required this.id,
required this.title,
this.note,
this.isDone = false,
required this.createdAt,
this.dueDate,
this.remindAt,
this.priority = 0,
});
// toJson / fromJson / copyWith ...
}
part 的关系是把多个文件编译成一个库,私有成员可以在这些文件之间互相访问。这样 TodoItem、TodoState、TodoEvent 可以共用一个私有工具函数,不必为了几个内部方法去扩大类的公开 API。
3.3 状态管理选择:Cubit 更适合 CRUD 场景
Flutter 生态里状态管理的方案多到让人选择困难,Redux、Bloc、Provider、Riverpod 我都用过。对这次的项目,我最终选了 flutter_bloc 中的 Cubit,而不是完整版的 Bloc。原因在于:待办模块的事件种类有限,无非就是加载、新增、切换完成、删除、更新这几种,Cubit 直接方法调用的方式足够应对;Cubit 不需要定义大量 Event 类,代码量少一半,阅读起来更直白。
dart复制class TodoCubit extends Cubit<TodoState> {
TodoCubit(this._repo) : super(TodoState(items: []));
final TodoRepository _repo;
Future<void> load() async {
final items = await _repo.loadItems();
emit(TodoState(items: _sort(items)));
}
Future<void> toggle(String id) async {
final items = state.items.map((e) {
return e.id == id ? e.copyWith(isDone: !e.isDone) : e;
}).toList();
await _persistAndEmit(items);
}
Future<void> add(TodoItem item) async { /* ... */ }
Future<void> remove(String id) async { /* ... */ }
List<TodoItem> _sort(List<TodoItem> items) {
// 未完成在前,按截止时间升序,再按创建时间降序
}
Future<void> _persistAndEmit(List<TodoItem> items) async {
await _repo.saveItems(items);
emit(TodoState(items: _sort(items)));
}
}
有一类特殊场景值得注意:Cubit 的 emit 是同步触发的,但 _repo.saveItems 是异步操作。如果连续快速操作(比如疯狂勾选多个项),可能出现“后 emit 的旧列表覆盖最新状态”的情况。因此在 _persistAndEmit 里,我每次拿的都是 state.items 的最新值做修改,而不是赋值给一个局部变量再多次修改。这是实操中容易出 bug 的小细节。
4. 核心功能实现与交互细节
4.1 列表页:让列表滚动足够跟手
列表是待办事项的门面,性能感受直接决定用户评价。我用 ListView.builder 来渲染,每一条目是一个自绘的 TodoTile Widget,整个列表页的关键性能优化点有三个:
第一,每条 TodoTile 必须是 const 构造,除非数据变化否则复用原对象。第二,把复选框的点击区域和文字展示区域拆成两个 Widget,避免点一次复选框导致整行 rebuild。第三,用 RepaintBoundary 包裹每个列表项,阻止单行状态变化时重绘相邻区域。这三个优化做下来,我在 OpenHarmony 开发板上滚动 300 条待办数据时能保持 60 帧,实测效果符合预期。
交互上还做了一个小细节:点击列表项左侧的 Checkbox 完成切换时,给文字加一条删除线的过渡动画,而不是瞬间改变样式。这里用到 AnimatedContainer 的 duration 属性,300 毫秒比较合适。太短没感觉,太长会拖慢连续操作。
4.2 新建与编辑:底部弹窗的表单处理
新建待办我一开始设计的是独立页面,后来发现跳转会打断用户的记录节奏,改用模态底部弹窗。原因很朴素:从用户点击“+”到弹出键盘输入,底部弹窗的路径比页面跳转短一半,而且保留列表上下文,新输入的内容能立刻看到效果。
弹窗里有一个 TextFormField 和一个可选的日期时间选择器。表单校验的核心逻辑是标题不能为空且不能超过50字,校验消息用 validator 回调返回,配合 AutovalidateMode.onUserInteraction 实现“输入过再提醒”而不是“一进来就报错”。日期选择器方面,flutter 自带的 showDatePicker 在 OpenHarmony 上实测可用,但样式偏 Material,如果要追求鸿蒙原生的卡片风格,后续考虑用 EventChannel 调原生日期选择器。
dart复制showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (ctx) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(ctx).viewInsets.bottom),
child: TodoEditSheet(
onSave: (title, note, dueDate) {
context.read<TodoCubit>().add(
TodoItem(
id: DateTime.now().microsecondsSinceEpoch.toString(),
title: title,
note: note,
createdAt: DateTime.now(),
dueDate: dueDate,
),
);
},
),
),
);
这里 MediaQuery.of(ctx).viewInsets.bottom 是必须写的,否则键盘弹出后弹窗会被遮挡,这个问题在 Android 上同样存在,属于跨端通用经验。还有一个小坑:OpenHarmony 上软键盘弹出动画和底部弹窗的 showModalBottomSheet 动画会相互干扰,偶尔出现闪现。我的临时方案是设置 enableDrag: false 并加 animationDuration 为 200ms,实测基本消除闪动。
4.3 完成、删除与撤销
删除操作的交互我花了不少心思。列表项向左滑动,露出红色删除按钮,滑动手势用 Flutter 自带的 Dismissible 就不太合适,因为 Dismissible 通常用来“滑到底就删”,这与我要的“滑动露出按钮”不一致。我改用了 flutter_slidable 库,配置如下:
dart复制Slidable(
key: ValueKey(item.id),
endActionPane: ActionPane(
motion: const DrawerMotion(),
children: [
SlidableAction(
onPressed: (_) => context.read<TodoCubit>().remove(item.id),
backgroundColor: Colors.redAccent,
icon: Icons.delete_outline,
label: '删除',
),
],
),
child: TodoTile(item: item),
)
配合删除后的 SnackBar 撤销按钮,形成完整的防误删闭环。体验逻辑是:用户滑动删除后,数据从列表和存储中移除,同时弹出 SnackBar 显示“已删除”,右侧带“撤销”按钮。点击撤销则把被删的 TodoItem 重新插回原位置。要实现原位置插入,需要注意在调用 remove 前先记录旧列表里的索引。这一步代码不多,但很容易忽略索引而把撤销项排到末尾,影响体验。
4.4 提醒通知:用 EventChannel 打通鸿蒙原生
Flutter 层做 UI 和状态管理都很顺手,但设置提醒时间后要弹系统通知,就得走鸿蒙原生能力了。我选择 EventChannel 作为通信方式,原因是通知的回调和点击事件是异步且多次的,MethodChannel 更适合“请求-响应”式调用,EventChannel 则天然适合推送这类事件流。
Dart 侧先定义一个通道管理器:
dart复制class OhosNotificationBridge {
static const EventChannel _eventChannel =
EventChannel('com.example.todo_app/notification');
static void listen() {
_eventChannel.receiveBroadcastStream().listen((event) {
if (event is Map && event['type'] == 'notificationTap') {
// 用户点击了通知,跳转到对应待办详情
}
});
}
}
鸿蒙侧的 ets 代码负责创建通知通道、设置提醒时间并触发通知。这里有一个跨端开发的常见误区:在 OpenHarmony 上实现提醒不能依赖 Dart 层的定时器。因为 Flutter 的 Timer 在 App 进入后台或进程被系统回收后不会再执行,必须把提醒逻辑放到鸿蒙原生侧的 reminderAgentManager 里,由系统调度。很多 Flutter 开发者第一次做提醒功能时会在纯 Dart 层写倒计时,放到真机上一锁屏就失效,这个坑值得提前避开。
EventChannel 的另一个用途是在 App 启动时监听鸿蒙系统发出的时区变化、电池低电量等系统事件,后续版本可以扩展适配这些能力。
5. 平台差异适配与性能优化
5.1 OpenHarmony 与 Android 的几处关键差异
Flutter for OpenHarmony 的最大优势是大部分 Widget 在鸿蒙设备上能直接渲染,但它并不是一个“零差异”平台。我在实测中记录了三类关键差异,提前了解能显著减少调试时间:
- 返回手势与物理返回键:OpenHarmony 的系统返回手势在 Flutter 页面栈中对应的
PopScope行为与 Android 不完全一致。实测需要在ohos目录的原生入口里配置返回事件与 Flutter 的appFlutterView联动,否则从二级页面返回到桌面时,Flutter 内部的 Navigator 状态可能不更新。 - 图片与字体资源解析:
Image.asset在 Android 上能直接读取@mipmap资源,但鸿蒙工程有自己的media目录结构。因此跨端项目里建议全部统一走 Flutter 的assets目录,不要依赖原生资源目录。 - 权限模型:通知权限、日历权限在 OpenHarmony 上与 Android 是两套 API,Dart 层无法直接判断权限状态。我封装了一个
PermissionService接口,分别对接permission_handler的 Android 实现和鸿蒙侧的模块。
5.2 UI 适配:字体缩放与安全区域
OpenHarmony 设备形态跨度大,从手机到平板甚至开发板都有。我用 MediaQuery.textScaler 处理字体缩放:待办列表里的标题字号设置为自适应,但最大缩放比限制在 1.3,避免大字号模式下标题换行导致列表高度抖动。同时,所有滚动视图的底部都加了 SafeArea,防止系统导航条遮挡内容。
另一个细节是选项卡切换时的动画。因为列表页和设置页之间用了 TabBar,默认点击 tab 会有 300ms 的切换动画,在 OpenHarmony 低端设备上偶尔有掉帧。我参考社区方案关掉了 TabBar 的点击动画效果,改为页面滚动时联动指示器,显著提升了切换的跟手度。具体做法是通过 TabController 监听 offset,手动设置 indicator 位置。
5.3 反映一个真实问题:Flutter Impeller 在 OpenHarmony 上的采用度
Flutter 3.10 之后 iOS 默认使用 Impeller 渲染引擎,Android 上也在逐步铺开。很多文章在问 Impeller 在 OpenHarmony 的支持情况,我也在社区查过。结论是:目前 Flutter for OpenHarmony 的适配分支仍然主要基于 Skia 渲染,Impeller 的接入还在推进中。这让很多依赖新渲染引擎特性的动画效果在鸿蒙上的表现会保守一些。
实际写代码时,我的策略是避免过度依赖 Impeller 才能发挥的渲染优势,比如复杂的模糊特效、大幅面的 BackdropFilter。对于列表这种高频 UI,用普通的 Material + 半透明遮罩实现的效果差别不大,但兼容性要好得多。待办事项的完成态动画我用的是 AnimatedOpacity 和 AnimatedContainer,这类基础动画在 Skia 下性能完全够用。如果你的业务离不开高级渲染特效,建议针对鸿蒙机型做专门的降级方案。
5.4 列表性能实测与构建优化
待办列表的性能优化不能只靠感觉,我挂在 Demo 机上进行过几次简单实测。300 条数据滚动场景,开启每条 const 构造 + RepaintBoundary 之后,帧耗时从平均 16ms 降到 8ms 左右。这说明列表略卡的问题大概率不是渲染引擎不行,而是 Widget 构建太频繁。常用的排查手段是打开 Flutter DevTools 的 Widget Inspector,观察列表滚动时 TodoTile 的 build 数量是否大于可视区域的条目数。
App 安装包体积方面,Flutter 在 OpenHarmony 上的产物主要包含 so 库和资源文件。Debug 包动辄上百兆,因为带了很多调试符号;Release 包按我的配置最终减到 48MB 左右。如果还想再压,可以裁剪 flutter_localizations 之类的冗余国际化资源,但考虑到后续模块扩展,我保留了一部分余量。
6. 常见问题与排查实录
6.1 高频报错速查表
下面这些问题是 Flutter 开发者初次接触 OpenHarmony 大概率会遇到的,每个我都实际踩过:
| 问题表现 | 根因 | 快速解决方案 |
|---|---|---|
| The current configured Flutter SDK is not known to be fully supported | Flutter SDK 与 OpenHarmony 适配分支版本不匹配 | 切换到官方适配分支支持的版本,同时检查 flutter --version 输出 |
| You are applying Flutter's main Gradle plugin imperatively using the apply script | 项目里的 Gradle 插件声明方式过旧 | 在 android/settings.gradle 中改用 plugins DSL 方式声明 Flutter Gradle 插件 |
| Could not close i... AssertionError | 持久化写入时文件句柄未释放 | 检查 shared_preferences 的临时文件生命周期,升级到修复版本 |
| Flutter Web 引擎启动慢 | 本地开发时误用了 Web 调试目标 | 明确指定 -d 设备为 ohos 模拟器或真机,别让进程自动选 Web |
| 打包时提示未添加 videoplayer 模块 | 这个报错常出现在集成了 video_player 但没在 ohos 目录注册插件时 | 检查 ohos 目录的插件注册配置,确保原生模块已添加对应依赖 |
第一行和第三行是环境问题,我建议在 flutter doctor -v 里认真看 ohos 部分的状态,而不是只看总览的绿色对勾。有时候医生工具因为 SDK 路径配置不完整,不能完全反映适配分支的兼容性。
6.2 一个被低估的问题:本地调试时的时序竞态
状态管理在模拟器和真机上行为不一致,是这次开发中最折磨人的问题之一。在 Android 模拟器上,列表加载完成后立刻点击切换完成,一切正常;但换到 OpenHarmony 开发板,同样的操作偶发出现“切换完又变回未完成”的现象。定位后问题出在 TodoRepository.saveItems 和 Cubit 的 emit 顺序。
我最初的写法是:
dart复制emit(newState);
await _repo.saveItems(newState.items);
如果用户在保存完成前又触发了一次操作,第二次操作基于旧状态 emit,然后第一次保存完成时又 emit 了旧状态……但时序已经乱了。修正后的写法是先保存成功再 emit,并给加载状态加一个 isSaving 标志位,当保存进行中时新操作排队等待。修复后连续快速操作 50 次也没有出现状态回退。这里值得写进自己的 checklist:在状态管理里,UI 的响应应该永远基于持久化完成之后的最新状态。
6.3 插件适配鸿蒙的流程观察
我看到不少朋友关心“Flutter 平台插件如何适配鸿蒙”,其实核心流程就四步:第一步,确认插件的 Android 和 iOS 实现类的接口;第二步,在 ohos 目录下创建对应模块,实现相同的接口方法;第三步,在 pubspec.yaml 中用 ohos 平台路径指向原生实现;第四步,通过 MethodChannel/EventChannel 把鸿蒙的 API 能力暴露给 Dart。这套流程本质是利用 Flutter 的插件注册机制扩展平台,我在做通知模块时就是照着这个流程走的。社区里现在已经有 okta 等安全类插件在走鸿蒙适配的案例,说明这套机制已经比较成熟。
7. 打包发布与后续迭代思考
7.1 生成 HAP 包与签名配置
OpenHarmony 应用最终发布的产物是 .hap 包,而不是 APK 或 IPA。打包流程和 Android 的 Gradle 思路相近,但细节上更依赖 DevEco Studio 的配置界面。我在项目的 ohos 目录里配置了签名文件,然后在 Flutter 侧执行构建命令,把生成的可执行产物交给原生工程去组装 HAP。
这中间有个容易混淆的地方:Flutter 的构建产物是 libflutter.so 加 Dart 编译出来的 libapp.so,它们会作为鸿蒙工程里 entry 模块的 native 依赖被打进 HAP。所以每次修改 Dart 代码后都要重新执行 Flutter 构建,再走 DevEco 的打包流程。我建议在 CI 或者本地脚本里把两条命令串起来,避免忘记重新构建 Flutter 产物而把旧逻辑打成包。
7.2 后续迭代方向
待办事项管理跑通之后,第二期我计划做三个扩展:一是把本地存储迁移到 drift,支撑更复杂的搜索和按标签筛选;二是通过 EventChannel 让 App 能响应系统日历事件,自动把截止日期同步成日历日程;三是加一个桌面小组件(卡片),不走 Flutter UI,而是直接在鸿蒙 ArkTS 侧实现,利用平台能力完成待办展示。这三个方向恰好覆盖了数据层、通信层和平台集成层的不同深水区,对个人技术成长很有帮助。
最后再分享一个我这次项目里体会最深的点:跨端开发最值钱的能力不是写 UI,而是抽象边界。把数据存储、状态管理、平台通知这些能力用接口隔离开,未来无论你是换存储方案、换状态库,还是换一个全新的平台,上层 UI 都能保留下。Flutter for OpenHarmony 现在还年轻,生态里有很多细节要靠读源码和踩坑去填平,但这种“把一套工程带去新平台”的实战积累,恰恰是做技术最让人兴奋的部分。
