这个标题看着就亲切,最近正好在折腾跨端适配的活儿。把 Flutter 那套 UI 思路搬到 OpenHarmony 上,还要落地一个完整的习惯打卡功能,听起来像是个小而美的练手项目,但真上手你会发现,里面藏着不少平台差异的坑和工程化设计的门道。这篇文章就围绕「Flutter for OpenHarmony 生活助手 App」里的习惯打卡模块,把从环境搭建、状态管理、数据持久化到核心打卡逻辑实现的完整过程拆开来讲,穿插我在实际开发中踩过的坑和验证过的方案,希望能给正在做类似跨端应用的朋友一点参考。
1. 项目整体设计与思路拆解
1.1 为什么选 Flutter 来做 OpenHarmony 应用
先聊一个很多人会纠结的问题:OpenHarmony 应用开发,官方主推的是 ArkTS 和 ArkUI,为啥还要绕一圈用 Flutter?我当时的判断很简单——团队里已经有现成的 Flutter 技术栈积累,而且这个生活助手 App 不是只跑在 OpenHarmony 一个平台上,还要兼顾 Android 和 iOS。如果每个平台都单独维护一套 UI 代码,后续迭代成本会翻好几倍。Flutter 的跨端渲染机制刚好能把这套 UI 逻辑统一起来,业务层代码复用率能做到 80% 以上,剩下需要处理的就是平台差异的适配层。
用 Flutter 跑在 OpenHarmony 上还有一个隐藏优势:生态里的第三方库非常丰富。比如习惯打卡功能里要用到的日期选择、图表统计、本地通知,这些在 Flutter 生态里都有成熟方案,直接拿过来适配到 OpenHarmony 平台就行。相比之下,ArkTS 的第三方库数量还在起步阶段,很多功能得从零开始造轮子,效率上差了不少。
1.2 习惯打卡功能的需求拆解
一个习惯打卡功能,表面上看着简单,就是「今天打了没」两个状态,但真要做成一个能用的模块,里面涉及的子功能还挺多的。我按照使用场景把它拆成了这么几块:
- 习惯管理:创建习惯(比如「每天喝水 8 杯」「每周跑步 3 次」)、编辑习惯名称和频率、删除习惯。这是整个打卡功能的数据基础。
- 每日打卡:针对某个习惯标记「今日已完成」,并且要有防重复提交和补打卡的机制。
- 打卡日历:用日历视图展示每个习惯的连续打卡天数、月度打卡率,让用户有直观的成就反馈。
- 统计激励:连续打卡天数、累计打卡次数、每周/每月的完成率曲线,这是留存用户的关键模块。
技术选型上,我用了 Provider 做全局状态管理,配合 shared_preferences 做轻量级本地存储,再加一个 sqflite 存历史打卡记录。这套组合在 Flutter 社区里非常主流,文档齐全,遇到问题也容易搜到答案。整体架构考虑到了 OpenHarmony 平台对部分插件还不兼容的现状,所以把数据层抽象出了一道接口,后续如果某个插件在 OpenHarmony 上有问题,替换实现类就行,不用大改业务代码。
1.3 架构设计中的平台兼容考量
架构设计这块我特别想多说两句。很多人写跨端应用,脑子里想的是「一套代码跑所有平台」,但实际做下来你会发现,这个理想状态几乎不存在。每个平台都有自己的生命周期管理、文件系统路径规则、权限系统差异,OpenHarmony 在这方面的差异尤其明显。所以我在设计项目结构时,专门建了一个platform目录,把所有平台相关的调用都隔离在里面。UI 层放共享代码,平台层放各自独立的实现,这样既保证了开发效率,又给后续平台适配留了余地。
举个例子,OpenHarmony 上获取应用缓存目录的接口和 Android 不一样,如果你在业务代码里直接写死某个平台的 API,换到 OpenHarmony 上就会崩。我的做法是定义一个抽象类CachePathService,然后分别在android和ohos目录下实现对应的子类,通过工厂方法动态创建实例。这样业务层永远只面向抽象接口编程,平台差异被完全屏蔽掉了。
2. 环境准备与工程搭建全流程
2.1 Flutter for OpenHarmony 环境配置明细
先说环境搭建,这一步折腾了我整整一天,主要是版本匹配的问题。Flutter 官方主分支目前还不直接支持 OpenHarmony,需要用社区维护的 flutter_flutter 仓库,也就是集成 OpenHarmony 适配的分支版本。版本对应关系非常关键,用错了版本编译时会出现各种莫名其妙的报错。
我实际验证过的组合是这样的(基于当时最新版本):
# 克隆 OpenHarmony 分支的 Flutter SDK git clone https://gitee.com/openharmony-sig/flutter_flutter.git -b master # 克隆 flutter engine(OpenHarmony 版本) git clone https://gitee.com/openharmony-sig/flutter_engine.git -b master # 配置环境变量 export FLUTTER_ROOT=/your/path/flutter_flutter export PATH=$FLUTTER_ROOT/bin:$PATH配置完记得跑一遍flutter doctor,确认 OpenHarmony 相关的检查项都通过。我第一遍跑的时候报了个FlutterOpenHarmony未安装的错误,排查后才发现是因为缺少 DevEco Studio 的命令行工具路径配置。把hdc和ohpm的路径加进环境变量后问题就解决了。
2.2 工程目录结构与模块划分
工程结构方面,我参考了一些成熟的 Flutter 开源项目,再结合生活助手 App 的业务特点,最终拍板用 feature-first 的目录结构。这种结构的好处是模块边界清晰,后续加新功能不会把代码搅成一团。
lib/ ├── main.dart # 入口文件 ├── app.dart # 应用根组件,配置主题和路由 ├── core/ │ ├── constants/ # 常量定义 │ ├── theme/ # 主题样式 │ └── utils/ # 通用工具函数 ├── features/ │ ├── habit/ │ │ ├── models/ # 数据模型 │ │ ├── providers/ # 状态管理 │ │ ├── repositories/ # 数据仓库接口 │ │ ├── screens/ # 页面 │ │ ├── widgets/ # 小组件 │ │ └── services/ # 平台服务抽象 │ └── settings/ ├── platform/ │ ├── android/ # Android 平台实现 │ └── ohos/ # OpenHarmony 平台实现 └── shared/ ├── widgets/ # 跨功能共享组件 └── services/ # 共享服务features/habit目录就是文章主角——习惯打卡功能所在的位置。把所有相关代码收拢到一个目录里,后续如果要迁移成独立的模块,直接把这个目录拎出来配套依赖就能用。
3. 数据层设计与核心打卡逻辑实现
3.1 数据模型定义:习惯与打卡记录
数据模型是整个功能的地基。我设计了两个核心实体:Habit和HabitRecord。Habit 存的是习惯的基础信息,HabitRecord 存的是某一天某个习惯的打卡状态。
class Habit { final String id; // 习惯唯一标识,用 UUID final String name; // 习惯名称 final String icon; // 习惯图标标识 final int targetDays; // 目标打卡天数(月度目标) final DateTime createdAt; // 创建时间 final bool isArchived; // 是否归档 } class HabitRecord { final String habitId; // 关联习惯 ID final DateTime date; // 打卡日期(只精确到天) final bool isCompleted; // 是否已完成 final int checkInCount; // 当天打卡次数(支持多次打卡的习惯) }这里有个设计细节容易被忽视:date字段要只存日期部分,不要带时间。我一开始为了省事,直接存了DateTime.now(),结果后面做统计查询时,因为时区和时间部分不同,日期范围匹配总是出问题。后来改成统一归一化处理,把时间部分清掉再入库,这个问题就彻底解决了。
3.2 本地存储方案:共享参数与结构化数据结合
数据存储我采用了「双轨制」:习惯的元数据量不大,用 shared_preferences 存 JSON 序列化后的字符串;打卡记录会随着时间增长变得很大,必须上数据库。这里有个关键考量,OpenHarmony 平台的 shared_preferences 插件支持情况还行,但 sqflite 的适配插件花了不少工夫才跑通。
存储层接口先抽象出来,再分别实现:
abstract class HabitRepository { Future<List<Habit>> getAllHabits(); Future<Habit> createHabit(Habit habit); Future<void> updateHabit(Habit habit); Future<void> deleteHabit(String id); Future<List<HabitRecord>> getRecordsForHabit(String habitId, DateTime start, DateTime end); Future<void> saveRecord(HabitRecord record); }Android 和 OpenHarmony 平台分别用 shared_preferences + sqflite 实现这个接口。数据量超过一万条记录后,纯 JSON 存文件的方式会有明显的读写延迟,所以打卡记录必须走数据库索引查询,这也是我坚持双轨制的原因。
3.3 打卡逻辑的核心算法:连续天数与补签规则
打卡功能的核心算法是「连续打卡天数」计算。这个数字直接决定用户的成就感和留存率,算法必须准确。我最初的想法很简单,遍历该习惯的所有打卡记录,从今天往前数连续有打卡记录的天数就完事了。但实际测试发现有几个边界情况需要考虑:
- 跨天问题:用户凌晨 00:05 打卡,算法判断的是「昨天」还是「今天」,取决于设备时区。
- 补签场景:用户昨天漏打了,今天补签,连续天数要能正确衔接。
- 倒序中断:最新一条未打卡记录出现后,连续天数就断了,后面再怎么往前找都没意义。
最终实现的算法是倒序扫描法:从今天开始往前逐天判断有没有打卡记录,遇到断档就停止。判断「今天有没有打卡」用的是归一化后的日期字符串做精确匹配,避免时间部分干扰。
int calculateStreakDays(List<HabitRecord> records) { if (records.isEmpty) return 0; final today = DateUtils.dateOnly(DateTime.now()); final recordMap = <String, bool>{}; for (final record in records) { final key = DateUtils.dateOnly(record.date).toIso8601String(); recordMap[key] = record.isCompleted; } var streak = 0; var cursor = today; // 如果今天还没打卡,从昨天开始算连续天数 if (!recordMap.containsKey(today.toIso8601String())) { cursor = today.subtract(const Duration(days: 1)); } while (recordMap[cursor.toIso8601String()] == true) { streak++; cursor = cursor.subtract(const Duration(days: 1)); } return streak; }补签逻辑我设计成了「允许补签最近 7 天」,但补签后当天不能再标记为「未打卡」,避免用户通过反复补签来刷数据。补签入口放在日历视图上,用户点选某个过去的日期,如果当天没有打卡记录,就弹出一个补签确认框。这里的权限校验逻辑要严格一点,不然容易出现用户补签了却看不到统计更新的 bug。
3.4 日期处理与统计计算的最佳实践
日期处理是打卡类应用最容易翻车的地方。我踩过一个典型的坑:用DateTime.now()直接做日期比较,结果在 UTC+8 时区下,晚上 8 点以后的时间戳和当天日期字符串对不上,导致打卡记录「跑到明天去了」。解决方案是封装一个统一的日期工具类,所有地方都走这个工具,禁止直接调用原始的 DateTime 方法做跨天判断。
统计模块我做了三个维度:日维度(当天完成率)、周维度(本周打卡天数)、月维度(本月打卡率和连续天数)。月维度的计算有个细节:要区分「自然月」和「滚动 30 天」,用户通常感知的是自然月,所以月度统计严格按每月 1 号到月底划分。日历视图上每个日期格子的状态直接读记录表,加了一层FutureBuilder缓存,因为用户频繁翻日历月份时,每次都查数据库会有明显的卡顿感。
4. UI 界面实现与交互体验优化
4.1 打卡首页布局与组件拆分
打卡首页的信息层级是:顶部是今日概览(今日完成习惯数/总习惯数),中间是习惯列表(每个习惯显示名称、图标、连续打卡天数、打卡按钮),底部或侧边是快捷入口(新增习惯、进入统计页)。布局确定后,我把页面拆成了多个 Widget,每个 Widget 负责一块独立功能,方便复用和测试。
这里有个组件设计的经验:打卡按钮的状态分为三种——未打卡(可点击)、已打卡(灰色禁用)、已补签(显示补签标识)。三种状态的颜色和文案都不同,状态切换要通过 Provider 统一驱动,不能在 Widget 内部用临时变量维护,否则页面刷新时会状态错乱。
我实际开发时踩过一个坑:打卡按钮的点击事件里做了异步数据库写入,但 UI 层没有等待异步完成就更新了状态,结果出现「点击打卡后按钮变灰,重启应用后又变成未打卡」的问题。后来在 Provider 的方法里返回Future<bool>,UI 层 await 成功后刷新状态,这个问题就消失了。
4.2 自定义日历组件的实现思路
日历组件是整个打卡页里最复杂的部分。一开始我打算直接用第三方日历库,但翻了一圈,很多库对 OpenHarmony 平台的兼容性没有验证过,而且还要定制每个日期格子的打卡状态标记,改造成本也不低。最后决定基于 Flutter 的 Table 和 GridView 自己写一个。
核心思路是这样的:每个日期格子是一个自定义 Widget,接收三个状态参数——日期值、当天打卡状态、是否属于本月。渲染逻辑上,本月日期正常显示,非本月日期灰色淡显。日期格子的打卡状态用一个小圆点表示,已完成是实心绿点,未完成是空心灰点,今天则额外加一个边框高亮。
class DayCell extends StatelessWidget { final DateTime date; final bool isCurrentMonth; final bool isCompleted; final bool isToday; final VoidCallback? onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( border: isToday ? Border.all(color: theme.primaryColor, width: 2) : null, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${date.day}'), const SizedBox(height: 2), Container( width: 6, height: 6, decoration: BoxDecoration( shape: BoxShape.circle, color: isCompleted ? Colors.green : Colors.grey.shade300, ), ), ], ), ), ); } }日历滑动切换月份的效果是用 PageView 实现的,每个页面承载一个月份网格。好处是滑动流畅,坏处是内存开销大了一点。为了优化,我加了缓存策略,只保留当前页、前一页、后一页的状态,其他月份销毁重建。
4.3 打卡动效反馈的最佳实践
打卡这个动作虽然简单,但良好的动效反馈能明显提升用户的满足感。我在实现时设计了三个层次的反馈:
- 即时反馈:点击打卡按钮后,按钮立刻显示加载状态(转圈),然后变成绿色对勾,这个变化要在 100ms 内完成,不然用户会觉得卡顿。
- 成就反馈:连续打卡天数达到 7 天、30 天、100 天等里程碑时,弹出一个全屏成就弹窗,展示徽章图标和鼓励文案,同时更新统计页面的里程碑列表。
- 数据反馈:打卡成功后,今日概览的完成率数字有个从旧值滚动到新值的动画,配合一个轻微的震动反馈(调用系统 HapticFeedback),让用户从触觉上确认操作成功。
这些细节看起来不起眼,但真正影响用户对应用「质感」的判断。我当时做完初版,自己用了两天,总觉得打卡有点「干」,加上动画之后明显好多了。不过要注意动效不能喧宾夺主,动画时长控制在 300ms 以内,否则连续打卡时反而会觉得拖沓。
5. 多平台适配:从 Android 到 OpenHarmony 的坑与对策
5.1 OpenHarmony 插件适配实战
用 Flutter 开发 OpenHarmony 应用,最头疼的就是插件适配。Flutter 的三方库大多数只官方支持 Android 和 iOS,OpenHarmony 要靠社区维护的适配版本。我在项目里用了十多个常用插件,其中四个遇到兼容问题:shared_preferences、path_provider、sqflite、flutter_local_notifications。
shared_preferences的 OpenHarmony 适配版改名成了shared_preferences_ohos,使用方式基本一致,但要在 pubspec.yaml 里改依赖路径。sqflite的 OpenHarmony 版本则需要额外引入一个数据库驱动库,初始化方式有差异。遇到插件不兼容时,别急着换技术方案,先看看有没有 ohos 后缀的适配库,社区维护得还算勤快。
如果某个插件实在找不到适配版本,还有个备选方案:用MethodChannel自己写平台桥接代码,在 OpenHarmony 原生层实现一套逻辑。这个方案灵活度高,但维护成本也高,我一般只在功能简单、接口明确的情况下用。
5.2 Flutter 页面嵌入 OpenHarmony 的混合开发方案
实际项目里,不可能把整个 App 都用 Flutter 重写一遍,尤其是生活助手这类应用,原本可能就有不少 OpenHarmony 的原生页面。这时候需要用到混合开发方案,把 Flutter 页面作为一个子模块嵌入到原生工程里。
Flutter 官方提供了FlutterFragment和FlutterActivity的 Android 版本,OpenHarmony 也有对应的集成方式。核心思路是在 DevEco Studio 的工程里创建一个 UIAbility 作为 Flutter 页面的容器,通过FlutterEngine对象加载 Flutter 模块。通信机制有两种:Flutter 主动调原生用MethodChannel,原生主动调 Flutter 用EventChannel。
混合开发的坑主要在生命周期管理上。OpenHarmony 的 UIAbility 生命周期和 FlutterActivity 的 lifecycle 事件不完全对等,导致页面切换时 Flutter 侧可能收不到 onPause/onResume 事件,打卡页在后台待了一会儿后计时器会异常。解决办法是在 UIAbility 的onPause/onResume里手动调用 FlutterEngine 的生命周期分发方法。
5.3 生命周期管理与平台通道设计
生命周期这块值得专门说一说。做打卡应用,有时候用户只是切到后台去聊个微信,十几秒切回来,这时候如果打卡状态被重置了,体验就很崩。Flutter 在 Android 上能正常感知生命周期,但在 OpenHarmony 集成时要额外透传事件。
我在platform/ohos目录下建了一个LifecycleObserver类,监听 UIAbility 的前后台切换事件,然后通过事件渠道把状态同步给 Flutter 侧。Flutter 侧的顶层 Widget 监听到切后台事件时,记录一个时间戳;回到前台时,判断时间差,如果跨天了,就刷新今日打卡状态。这套机制比单纯依赖 Flutter 自己的生命周期检测要可靠得多,因为 OpenHarmony 的 UIAbility 生命周期触发点和 Android 不完全一致。
平台通道设计上,我统一了命名规范:通道名用com.example.app/xxx的格式,方法名都用小驼峰。所有平台调用的错误码也统一规范,比如 10001 表示存储不可用、10002 表示权限未开启,方便上层统一做异常提示。这个习惯帮我节省了大量联调时间。
6. 常见问题与排查技巧实录
6.1 连续打卡天数计算错误的案例复盘
这是我在开发中花时间最长排查的一个 bug。现象是:用户连续打了 5 天卡,第 6 天早上打开应用,连续天数显示为 1,而不是 6。当时第一反应是数据库查询出了问题,检查了好多遍也没发现异常。后来调试时打印出每一天的日期字符串,才发现问题出在时区上。
DateUtils.dateOnly(DateTime.now())在 UTC+8 时区返回的日期是正常的,但存储层在序列化时会调用toUtc()转成 UTC 时间,导致日期字符串少了一天。也就是说,第 6 天 UTC+8 的凌晨 0 点在 UTC 时区还是第 5 天的 16 点,数据库里查出来是第 5 天,连续天数自然就断了。
解决办法:存库前统一将日期转成 UTC 字符串存储,查出来后再转回本地时区。或者在数据库层直接把日期当作字符串存储,不做时区转换。我最终选择了后者,因为打卡记录的日期本质上是「日历日期」,和时区无关。这个修改看着简单,但排查过程很折磨人,分享出来给遇到类似问题的朋友指条明路。
6.2 跨天刷新机制与状态同步策略
打卡应用跨天刷新是个典型的隐藏需求。用户前一天晚上打完工,把应用挂在后台没关,第二天早上再打开,界面上的日期和打卡状态应该自动更新,而不是一直停留在昨天的状态。
我实现的方案是这样:在应用的根组件里注册生命周期监听,应用从后台回到前台时触发一次状态检查,对比当前日期和内存里的「当前日期」变量,如果不同就触发全局刷新。具体到打卡页面,刷新逻辑是重新计算今日概览、重置每个习惯的今日打卡状态、更新日历高亮。
class AppState extends ChangeNotifier { DateTime _currentDate = DateUtils.dateOnly(DateTime.now()); Future<void> checkForDateChange() async { final now = DateUtils.dateOnly(DateTime.now()); if (now != _currentDate) { _currentDate = now; await _reloadAllHabits(); notifyListeners(); } } }这里有个细节:跨天刷新时要先判断新的一天是否已过零点,如果用户在 23:59 打开应用,等界面加载完已经过了零点,那就要再触发一次刷新。为了解决这个问题,我加了一个「延迟 5 秒检查」机制,首页加载完成后定时检查日期是否变化,只有连续两次检查结果一致才认为跨天完成。
6.3 性能优化:列表卡顿与启动速度排查
打卡页面的习惯列表是长列表,习惯了滚动加载问题不大,但我在开发中发现给每个习惯加载统计信息时,UI 会出现明显的掉帧。原因是每个 VListItem 都在 build 方法里执行了数据库查询,数据处理耗时阻塞了渲染线程。
优化方案是加一个数据预加载层。进入打卡页时,先从数据库一口气拉取当前月的所有打卡记录,聚合统计后再交给 UI 层渲染,而不是等到每个列表项 build 时才去查数据。这样列表滚动就流畅多了。实测优化后,列表滑动帧率从大概 30fps 提升到了稳定 60fps。
启动速度方面,主要瓶颈在初始化数据库和加载首页数据。我用了两个手段优化:一是数据库初始化放到 splash 页加载时进行,不阻塞首个路由的渲染;二是把 SharedPreferences 的读取改成异步版本,避免同步读取阻塞 UI 线程。这两种手段叠加,冷启动时间从原来的 2.1 秒降到了 1.3 秒左右。
6.4 一个容易被忽略的坑:本地通知权限
打卡应用通常会配合提醒功能,让用户每天固定时间收到打卡提醒。这涉及到本地通知权限,在 Android 和 OpenHarmony 上的处理方式不太一样。OpenHarmony 的通知权限需要在模块配置里显式声明,而且首次调用通知接口时还要动态申请授权,否则会静默失败,用户收不到任何提醒。
我在实现时写了一个工具类,统一处理通知权限的逻辑:启动时检查是否有通知权限,如果有就注册每日提醒;如果没有就弹窗引导用户去设置页开启。这里要注意,每天定时提醒的准确性和时区切换有关,如果用户改了系统时区,提醒时间可能错乱,需要在生命周期回调里监听时区变化并重新注册提醒。
7. 项目测试与发布要点
7.1 单元测试与组件测试怎么覆盖核心逻辑
习惯打卡功能的核心逻辑比较适合做单元测试:连续天数计算、补签判断、日期归一化。我写测试用例时,会刻意构造一些边界数据,比如跨月数据、时区切换场景、空记录场景。特别是连续天数的测试,我在本地构造了几十条不同组合的记录,确保算法在各种边界条件下结果正确。
组件测试则聚焦几个关键 Widget:打卡按钮的状态切换、日历格子的渲染逻辑、统计曲线的数据展示。Flutter 自带的测试框架对组件测试支持很好,直接 mock 掉数据仓库层,用固定的测试数据集来验证 UI 是否符合预期。这套测试跑通之后,后续加新功能时安心不少。
7.2 OpenHarmony 应用的打包签名与上架流程
OpenHarmony 应用的打包流程和 Android 不完全一样,核心差异在签名体系上。OpenHarmony 用的是自己的签名工具 hap-sign-tool,签名证书分为发布证书和调试证书,调试证书直接用 DevEco Studio 自动生成,发布证书需要到应用市场申请。
打包时有个关键的配置项在build-profile.json5里,要确认signingConfigs里配置的证书路径和密钥库密码没有拿错环境。我踩过一个大坑:开发机上调试证书能正常安装调试,但一旦切换成发布证书,应用装到真机上闪退。排查半天发现是签名证书的 profile 文件过期了,重新申请并配置后问题解决。
7.3 多端联调与真机调试经验
开发过程中我同时测试过 Android 模拟器、OpenHarmony 模拟器、OpenHarmony 真机三种环境。模拟器的运行速度勉强能用,但一些依赖硬件能力的功能(比如振动反馈、传感器数据)模拟器上体验不完整,最好还是用真机做最终验证。
OpenHarmony 的真机调试需要通过 hdc 命令连接设备,和 adb 的语法很相近但略有差异。我整理了一条高频使用的 hdc 命令,分享给需要的人:
# 查看已连接设备 hdc list targets # 安装 HAP 包 hdc install path/to/your/hap/file.hap # 启动应用 hdc shell aa start -b com.example.lifeassistant -a EntryAbility # 查看日志 hdc shell hilog | grep Flutter用日志排查问题时,Flutter 侧的日志默认输出到 hilog 里,直接用flutter run可能看不到完整的 platform view 报错信息,建议配合hilog一起看。真机调试时还有一个常见坑:OpenHarmony 的设备屏幕比例和 Android 的主流机型差异较大,UI 适配要多测几组不同的屏幕尺寸,特别是日历组件和统计图表的布局容易在某些尺寸下溢出。
个人体会与后续扩展建议
整套习惯打卡功能开发下来,我最深的感受是:跨平台开发的难点不在 UI 代码怎么写,而在平台差异怎么抹平。Flutter 帮我们统一了 UI 层,但数据存储、生命周期、权限管理、插件适配这些基础设施层面的差异,还是要靠工程架构上的提前规划来消化。如果你正准备做类似的项目,一定要在项目初期就把平台抽象层搭好,不要等到功能写了一半再去适配新设备,那时候改动的成本会成倍增加。
另外想分享一个后续扩展的方向:习惯打卡这类功能天然适合做「社交化」和「数据可视化」的延伸。比如加入好友之间的打卡比拼、习惯养成的数据报告生成、基于打卡数据的智能提醒策略优化,这些功能在现有架构上扩展都不太费力,因为数据层和状态管理已经做好了模块化拆分。如果项目迭代到中后期,还可以考虑把打卡数据的统计逻辑抽成独立包,不仅在生活助手 App 里用,也能复用到其他项目。
最后说句实在话,Flutter 在 OpenHarmony 上的生态还不算成熟,开发过程中大概率会遇到一些文档查不到的边角问题。但换个角度看,这也意味着这个方向的技术红利期还没过,现在入局积累的经验会越来越值钱。如果你也在折腾这个技术栈,希望这篇文章能帮你少踩几个坑。