新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Flutter的OpenHarmony家庭药箱管理App开发实战

发布时间:2026/9/30 15:07:41来源:尧图网络
基于Flutter的OpenHarmony家庭药箱管理App开发实战
家里老人药箱里的药越堆越多有些过期了也没人发现降压药和感冒药混在一起每次找药都要翻半天;每个月称体重全靠手写在本子上翻起来一片混乱。我决定自己动手做一款家庭药箱管理App顺手把体重记录也做进去目标设备是国产的OpenHarmony平板。技术路线没有纠结太久——直接用Flutter。原因很简单Flutter在OpenHarmony上的适配已经走过了最不稳定的阶段openharmony分支能正常出包一套代码还能同时覆盖手机和平板以后想上Android、iOS也不用重写。这篇文章会把项目从环境搭建、数据模型设计、体重记录功能、原生通道、状态管理到打包细节完整捋一遍全程按我的实测流程来写。里面涉及的所有坑都是真实踩过的适合准备在OpenHarmony上用Flutter起项目的开发者参考尤其是做医疗健康类工具的朋友。1. Flutter 在 OpenHarmony 上的适配现状与起手准备1.1 版本仓库和分支选择别用主干SDK硬编OpenHarmony的Flutter适配不是Google官方在维护而是由openharmony-sig组织维护的独立仓库flutter_flutter。这里要提醒一句千万不要直接拿官方主干版本的Flutter SDK去编译OpenHarmony应用。主干分支面向Android/iOS/Web对OpenHarmony的抽象层、Engine接入和Native Plugin机制完全不兼容强行编包会死在CMake或Gradle阶段。我项目用的是3.7.12-ohos这个tag稳定性和接口完整度都够用。选它而不是最新版的原因很简单社区适配版本滞后于官方版本追求最新反而容易踩到还没修完的Engine Bug。实际操作命令如下git clone https://gitee.com/openharmony-sig/flutter_flutter.git cd flutter_flutter git checkout 3.7.12-ohos export PATH$PWD/bin:$PATH flutter doctorflutter doctor执行完能看到OpenHarmony Toolchain相关的检查项只要Device/Platform项没有飘红环境基本就OK。另外一个容易被忽略的点是flutter_flutter仓库会把引擎预编译产物塞到bin/cache/artifacts/engine路径下首次执行命令时下载可能需要十几分钟建议用稳定的网络环境。1.2 创建工程和OpenHarmony宿主壳环境变量配好后创建工程需要显式指定目标平台。OpenHarmony在Flutter工具链里的平台标识是ohosflutter create --platforms ohos medicine_kit生成工程后目录结构和你熟悉的普通Flutter工程基本一致多出来的ohos目录就是OpenHarmony宿主壳工程本质是一个标准的Stage模型应用可以用DevEco Studio打开做原生调试。后续编译产物验证、添加系统权限、注册原生插件都需要在这个宿主壳上操作。工程创建完毕先跑一个空壳应用确认设备能正常装包。连接OpenHarmony设备后执行flutter run --debug如果看到引擎启动日志和Flutter渲染画面说明基础链路已经打通。这一步不要跳过我之前在模拟器上一切正常真机上却因为屏幕旋转矩阵问题导致白屏排查了很久才发现是宿主工程没配置Abilities的orientation。1.3 原生ArkUI和Flutter的取舍我为什么没选ArkUI很多人在OpenHarmony上做应用会本能地选ArkUI声明式开发毕竟是官方亲儿子。但我做了个简单对比最终依然坚持Flutter路线对比维度ArkUI原生Flutter (openharmony)uni-app跨端能力只能在OpenHarmony/鸿蒙上跑一套代码覆盖OH/Android/iOS覆盖Web/小程序/App渲染性能系统原生组件性能优秀Impeller/Skia自绘引擎性能稳定依赖WebView渲染复杂页面有压力生态复用官方组件库偏系统类可复用pub.dev大量纯Dart包uni_modules插件市场学习成本需要额外学ArkTS声明式语法会Flutter即可无额外语法成本会Vue即可原生插件适配系统API直接调用需针对ohos做少量适配部分原生插件要重新封装核心原因是沉淀价值。医疗健康类工具大概率不会只做一个平台Flutter把页面和业务逻辑沉淀下来后续如果要出Android版、iOS版不需要另起炉灶。而ArkUI虽然开发体验不错一旦跨端需求出现前期的UI和状态管理代码都要推翻。2. 家庭药箱的两张核心表药品与体重记录的数据设计2.1 药品清单字段设计每一个字段都有明确职责家庭药箱管理的核心是药不能放过期医嘱不能忘。药品表的设计直接决定了后续提醒功能和列表筛查好不好做。我最终采用了这些字段字段名类型说明idINTEGER主键自增nameTEXT药品名称必填用于列表展示specTEXT规格描述比如0.25g*24片stockINTEGER剩余库存数量默认0expiry_dateTEXT有效期用yyyy-MM-dd字符串存储usage_guideTEXT用法用量比如每日两次每次一片remind_timeTEXT提醒时间用HH:mm存储created_atTEXT创建时间写入时自动填充有效期为什么用TEXT存yyyy-MM-dd而不是直接存时间戳两个原因一是日期字符串可读性好调试时一眼看懂格式化成本为零二是yyyy-MM-dd字典序等于时间序ORDER BY expiry_date出来的结果天然就是从近到远的过期顺序不需要额外转换函数。提醒时间单独拆出来是为了和药品服用频率解耦。有些药是一天三次有些是隔天一次提醒逻辑差异较大统一存一个HH:mm字段界面展示和通知解析都方便。2.2 体重记录字段设计BMI在写入时就算好体重记录功能听起来简单实际要做顺手并不简单不能只存一个体重数字体脂率、BMI、测量日期、备注都会影响用户体验。我的体重表设计如下CREATE TABLE weight_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, weight REAL NOT NULL, body_fat_rate REAL, bmi REAL, measure_date TEXT NOT NULL, remark TEXT, created_at TEXT DEFAULT (datetime(now, localtime)) ); CREATE UNIQUE INDEX idx_weight_date ON weight_record(measure_date);measure_date建了唯一索引防止同一天重复录入。UI层不需要额外判断今天有没有记录过直接写入时利用ON CONFLICT REPLACE把同一天的数据覆盖既省代码又保证数据一致性这是我的实际习惯。BMI为什么不查询时现算因为展示BMI的场景太多了列表卡片、趋势图顶部、统计面板每次现算就要把身高传过来查询SQL也会变复杂。不如在保存时用身高一次算好之后所有查询直接读字段。2.3 数据库初始化和版本管理用sqflite的分支包OpenHarmony上的本地数据库方案我选的是sqflite_ohos。它本质是社区对Android版sqflite的OpenHarmony适配分支接口和原版保持了一致业务层几乎感觉不到差异。Dart侧直接依赖它即可不需要区分运行平台。初始化流程放在main()里完成用一个单例Repository持有数据库引用final dbPath await getDatabasesPath(); final path $dbPath/medicine_kit.db; _database await openDatabase( path, version: 2, onCreate: _createTables, onUpgrade: _onUpgrade, );版本号管理是有讲究的。如果后续新增表或改字段只要把version加1并在_onUpgrade里写迁移逻辑不要直接在_createTables里顺手改老表结构否则老用户升级时会崩。这就是数据库迁移的基本规律新表的CREATE TABLE放onCreate老表的ALTER TABLE放onUpgrade两者分开维护。3. 体重记录功能实战统计卡片、折线图与增删改查3.1 页面结构拆分三层布局各司其职体重记录页面我拆成了三个区域。顶部是统计卡片展示当前体重、BMI、近30天变化量中间是最近30条记录的折线趋势图下方是可以侧滑删除的记录列表。Column( children: [ _buildSummaryCard(currentRecord), Expanded( flex: 3, child: _buildTrendChart(records), ), Expanded( flex: 4, child: _buildRecordList(records), ), ], )统计卡片这里的近30天变化量很容易做错不是用最新体重减去最老体重而是要用最新体重减去记录存在的最早体重。如果用户最近才恢复记录最老记录可能就在昨天变化量会误显示为0体验很差。3.2 折线图绘制fl_chart在OpenHarmony上的表现趋势图用的插件是fl_chart版本0.66。这个包是纯Dart绘制依赖的是Flutter自带的Canvas能力不涉及PlatformView所以OpenHarmony上跑起来没有任何适配问题。这一点很关键选图表组件时最好避开那些依赖WebView或原生View的库否则到了OpenHarmony上就是无尽痛苦。核心代码片段LineChartData( minY: yMin.floorToDouble() - 1, maxY: yMax.ceilToDouble() 1, lineBarsData: [ LineChartBarData( spots: spots, isCurved: true, color: const Color(0xFF4CAF50), barWidth: 2, dotData: const FlDotData( show: true, checkToShowDot: (spot, barData) true, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [Color(0x334CAF50), Colors.transparent], ), ), ), ], titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (value, meta) { final index value.toInt(); if (index 0 index dates.length) { return Text(${dates[index].month}/${dates[index].day}); } return const SizedBox.shrink(); }, ), ), ), )这里有两个细节值得说。第一minY和maxY不要直接用数据里的最值要往上下各扩1否则折线会贴在图表边缘视觉效果特别差。第二横轴坐标点改造成日期标签时必须做边界判断因为fl_chart会扫描整个X轴范围超出数据范围的坐标如果直接索引数组会报空指针。3.3 添加与删除记录的完整流程添加记录用showModalBottomSheet弹出三层表单日期选择器、体重输入框、体脂率输入框。体重输入框的类型要设成TextInputType.numberWithOptions(decimal: true)允许小数点输入。校验规则我写得很保守日期不能晚于今天防止未来数据体重范围30到300kg体脂率范围3到75%同一天已存在记录时弹出确认框是否覆盖当日记录用户点保存后先把BMI算好再执行INSERT或UPDATE。BMI计算公式是体重除以身高米的平方。身高我在设置页放了常量读取后传入工具函数。删除记录直接用Dismissible包裹列表项右滑露出背景色confirmDismiss里调用数据库删除逻辑再通过Cubit发通知刷新页面。需要注意的一点Dismissible的key必须唯一稳定我是用数据库记录id生成的ValueKey千万不要用记录在列表中的索引否则删除后列表重排Dismissible会错乱。4. EventChannel 接入用药提醒与原生能力协作4.1 MethodChannel 和 EventChannel 怎么分工很多初学者把Flutter和原生的通道都叫Channel其实它们分工完全不同。MethodChannel是请求-响应模型Dart调用一次原生处理一次返回一个结果。EventChannel是订阅-推送模型Dart监听一个事件流原生可以持续不断往里推数据直到取消订阅。在家庭药箱App里我用EventChannel实现了一个核心场景系统日历/系统提醒服务到点后触发把该吃药了事件推送出来。这是因为药箱的定时提醒在App退到后台时依然要生效而Flutter引擎在后台无法保证Dart代码持续运行必须交给系统侧处理事件再通过EventChannel回到Dart。4.2 Dart侧和ArkTS侧的通道注册Dart侧代码非常简洁就是一个EventChannel定义加订阅方法class ReminderChannel { static const EventChannel _eventChannel EventChannel(com.medicine_kit/medicine_reminder); static Streamdynamic reminders() { return _eventChannel.receiveBroadcastStream(); } }使用方只需要在页面initState时订阅_reminderSub ReminderChannel.reminders().listen((event) { final data jsonDecode(event as String); // 弹通知、刷新药品列表 });OpenHarmony原生侧的注册逻辑大致是创建一个EventChannel实例传入宿主Context和与Dart侧一致的Channel名然后设置StreamHandler。下面是一个ArkTS侧示意代码实际工程里细节会多一些import { EventChannel } from ohos/flutter_ohos; let eventChannel new EventChannel(abilityContext, com.medicine_kit/medicine_reminder); eventChannel.setStreamHandler({ onListen: (arguments, eventSink) { // 注册系统提醒服务到点后调用 eventSink.success(payload) }, onCancel: (arguments) { // 取消系统提醒服务释放资源 } });EventChannel发送的数据如果是复杂结构统一用JSON字符串传递Dart侧再解析。不要试图直接传ArkTS的Object或Map跨语言边界的类型转换容易翻车——我之前传过一次原生ArrayDart侧接收后类型变成List 还得自己做类型转换不如直接传字符串干净。4.3 提醒事件的重连策略EventChannel需要重新订阅EventChannel有一个隐藏的坑它是一次性订阅关系。如果原生侧因为内存压力重启了Flutter引擎或者应用从后台被系统回收后恢复事件流会断掉Dart侧拿到异常。所以我在App生命周期回调里做了一次事件重新订阅onResume时先取消旧的广播订阅再重新receiveBroadcastStream监听onError事件捕获通道异常后延迟1秒重连这个重连思路本质上是把EventChannel事件视为易失连接不能假设它永远在线。实际项目里提醒事件从系统到Dart的链路损耗很小但可靠性需要自己在业务层兜底。5. 状态管理选型Cubit方案和页面状态保活5.1 为什么用Cubit而不是完整版Bloc家庭药箱App的状态其实就两类药品列表状态、体重记录列表状态。用完整版Bloc会引入Event和State两个维度的样板代码对这类数据流不算复杂的App有点冗余。Cubit把Event层去掉了只保留State和emit写起来轻量很多。class WeightCubit extends CubitWeightState { WeightCubit(this._repository) : super(WeightState.initial()); Futurevoid loadRecords() async { final records await _repository.getAllRecords(); emit(WeightState(records: records)); } Futurevoid addRecord(WeightRecord record) async { await _repository.upsertRecord(record); final records await _repository.getAllRecords(); emit(WeightState(records: records)); } }Cubit适合的是数据从仓库到页面同步这个简单闭环。如果你以后要把操作日志、撤销回滚、复杂表单交互都融进来再考虑升级到完整Bloc。技术选型永远为当前的业务复杂度服务不是为了架构好看给自己增加工作量。5.2 Navigator切换页面不丢状态的正确姿势热门搜索词里有一条flutter navigator切换页面后会丢失状态吗这个问题我在药箱App里真实遇到了从药品详情页返回列表页时列表滚动位置回到了顶部。本质上不是Navigator本身丢状态而是页面被销毁重建导致的。我的解决方案分两层第一层Tab切换保留状态使用了AutomaticKeepAliveClientMixin混入并配合IndexedStack承载页面。这样Tab页之间切换时不会触发销毁重建滚动位置和输入框内容都还在。class _MedicinePageState extends StateMedicinePage with AutomaticKeepAliveClientMixin { override bool get wantKeepAlive true; }第二层跨页面传参的场景核心数据不要只存在Widget的局部变量里。比如新增药品页面填写的表单数据如果通过构造参数传回列表页一旦列表页重建数据就丢了。正确做法是让所有页面共享同一个Repository实例页面重建后从Repository重新读取数据UI自然恢复到最新状态。5.3 组件通信的三种常见姿势父子组件通信最简单的方式构造器传参加回调函数适合单个子组件的场景跨页面通信通过共享的Cubit或Repository注入方式用BlocProvider.value页面不依赖路由传参跨组件层级使用InheritedWidgetbloc库底层就是靠它实现的自定义状态共享也可以直接用它这三种姿势在药箱App里都用到了。体重记录的图表和列表是兄弟组件它们共用同一个WeightCubit实例所以图表数据不会和列表数据不一致。本质上这些都是状态提升的思路——把状态放到公共的Cubit里让多个组件共享同一个数据源比不断在组件间传值和回调清晰得多。6. 构建Hap包踩坑实录从Gradle警告到PlatformView性能6.1 构建流程与产物hap包才是OpenHarmony应用的真实格式OpenHarmony应用最终交付物是.hap包不是Android的.apk。Flutter工程构建Hap包的命令很直接flutter build hap --release这条命令会先编译Flutter引擎产物再调用OpenHarmony的打包工具链把宿主壳和资源打包。产物输出到build/ohos/outputs/hap/release目录。整体构建时间比Android慢一些第一次全量构建可能需要四到六分钟主要原因是要处理OpenHarmony的工具链依赖和引擎库合并。构建日志里如果出现针对某个so文件找不到的报错先检查是不是没有安装完整的Native SDK组件。DevEco Studio的SDK Manager里Native和ArkTS组件都要装上只装默认的ArkTS组件会缺链接库。6.2 Gradle插件声明方式引发的版本警告构建时日志里出现了很常见的警告You are applying Flutters main Gradle plugin imperatively using the apply script method.这是ohos模块的settings.gradle用了老式apply from写法导致的。新版Flutter工具链希望你把插件用声明式pluginManagement引进去否则后续升级工具链时会出现插件的版本解析问题。我把ohos/settings.gradle改成声明式引入插件后警告消失构建也更稳定了。这种警告看着不影响编译但如果你一年后升级Flutter SDK它可能会变成直接报错。6.3 打包期AssertionError与SDK版本校验Flutter 3.x版本打包时偶发这个异常java.lang.AssertionError: java.lang.Exception: could not close i...我遇到的原因是构建缓存里有残留的索引文件句柄没有释放缓存目录跨进程切换后出现竞争。执行flutter clean然后手动删掉项目下ohos/.idea目录里的临时缓存重新构建就正常了。如果依然复现检查工程路径是否包含中文或空格——这类IO流异常在非ASCII路径下很容易被触发。另一个SDK版本校验警告值得留意The current configured Flutter SDK is not known to be fully supported. Please...这个警告是因为项目同时被flutter_flutteropenharmony分支和系统的官方Flutter SDK混用了。确认flutter --version输出里的commit号是openharmony分支对应的版本后警告即可消除。如果还报删掉项目下ohos/.gradle和.dart_tool目录重新执行flutter pub get。6.4 PlatformView和Impeller在OpenHarmony上的性能适配应用内嵌了WebView做用药说明帮助中心在OpenHarmony上首次打开时能看到明显的渲染延迟这属于PlatformView的性能问题。原因是OpenHarmony的PlatformView嵌入目前没有像Android那样成熟的虚拟显示优化原生View和Flutter纹理层合成时存在额外开销。我采用的优化手段有三个减少PlatformView实例数用单例WebView容器复用关闭无关的页面切换动画避免PlatformView合成时机错过垂直同步在宿主工程里显式关闭Impeller引擎命令是flutter.impeller.enabledfalseImpeller引擎在OpenHarmony上的适配还不够成熟某些渲染路径会回退到软件绘制导致帧率不稳。对于以列表和图表为核心的药箱AppSkia引擎的表现反而更稳定。这一点和Android/iOS上的趋势相反在OpenHarmony上做项目不能照搬主流平台的经验。6.5 最终安装验证与发布前自检构建成功后我用hdc工具把hap包装到设备上进行真机验证。安装命令行是hdc install build/ohos/outputs/hap/release/medicine_kit-release.hap验证清单我会重点关注三块一是EventChannel提醒事件进程被杀后是否能恢复测试方式是调起应用后强行杀后台进程观察系统提醒是否还能拉起通知二是体重图表的滚动流畅度连续滑动30秒看帧率三是数据库升级直接从旧版本覆盖安装新包确认表结构迁移没有报错。7. 几个容易被忽视的细节最后一起说透这个项目做完有一些零碎但确实影响体验的点集中在最后分享第一药品过期筛选逻辑。expiry_date字符串直接比较大小是可行的因为yyyy-MM-dd格式字典序即时间序但要注意一定统一格式不能在入库时混入yyyy/M/d这种不规范的写法。我专门在Repository层写了一个formatDateString工具函数所有日期字段写入前都过一遍从源头杜绝脏数据。第二日期选择器不要直接用第三方库Flutter自带的showDatePicker在OpenHarmony上表现良好组件是纯Dart实现没有原生依赖主题风格和Material设计保持一致。第三字体显示在OpenHarmony上需要单独验证。中文字体在部分OpenHarmony设备上默认字重偏细我在TextStyle里显式设置了fontFamily回退链保证数字和中文混排时不会出现奇怪的字体宽度跳动。第四药品提醒通知的权限。OpenHarmony的通知权限和Android不太一样需要在宿主工程module.json5里显式声明ohos.permission.NOTIFICATION否则Dart侧申请权限拿到的永远是false。最后说点体感。Flutter在OpenHarmony上已经具备实际项目落地的条件但不要把它当成和Android完全对等的环境尤其要注意引擎渲染特性、PlatformView性能和原生插件的适配边界。这个药箱App目前一直在平板电视上服役用药提醒、体重曲线、药品过期预警这三个核心功能稳定运行没有遇到需要推翻重来的问题。希望这篇文章能帮你少踩几个我踩过的坑。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Cloudflare免费层实战配置:DNS、缓存与安全策略黄金三角 2026/10/1 3:03:24

Cloudflare免费层实战配置:DNS、缓存与安全策略黄金三角

1. 这不是“又一个CDN教程”,而是我用Cloudflare给37个网站做加速踩出来的实操地图你搜“Cloudflare配置网站免费CDN加速使用教程”,首页跳出的几乎全是2018年写的、截图还带着旧版UI、步骤卡在“点一下DNS设置”的半成品。更糟的是,很多教程…

阅读更多 →
C++从编译期多态到可变参数模板实战指南 2026/10/1 3:03:17

C++从编译期多态到可变参数模板实战指南

1. 项目概述&#xff1a;为什么我们需要深入理解模板 如果你写过一段时间的C&#xff0c;尤其是接触过标准库&#xff08;STL&#xff09;&#xff0c;那么“模板”这个词对你来说一定不陌生。 std::vector<int> &#xff0c; std::map<std::string, double> &…

阅读更多 →
LSTM与迁移学习如何破解非定常气动力建模难题 2026/10/1 3:02:58

LSTM与迁移学习如何破解非定常气动力建模难题

简介&#xff1a;面向航空航天与机器学习交叉领域的研究者及工程师&#xff0c;这份资源基于迁移学习与LSTM神经网络构建气动力建模方案&#xff0c;利用大规模气动数据预训练模型&#xff0c;再针对特定飞行工况微调&#xff0c;有效降低训练成本并提升预测精度。压缩包共156个…

阅读更多 →
16.RK3588 AI 边缘计算盒子怎么设计?从硬件到交付的要点拆解 2026/10/1 3:02:58

16.RK3588 AI 边缘计算盒子怎么设计?从硬件到交付的要点拆解

RK3588 AI 边缘计算盒子怎么设计&#xff1f;从硬件到交付的要点拆解摘要&#xff1a;AI 盒子看起来是"核心板加个壳"&#xff0c;真正量产时算力分配、散热、多路视频接入和远程运维每一环都可能翻车。本文按硬件构成、设计要点、交付形态三条线拆解&#xff0c;并附…

阅读更多 →
日志排查太慢?用这组grep组合拳提升效率 2026/10/1 3:02:58

日志排查太慢?用这组grep组合拳提升效率

一个人翻日志文件能慢到什么程度&#xff1f;我之前在工位上见过一次真实的&#xff1a;后端同事排查一个定时任务没执行的问题&#xff0c;他打开一个接近1GB的日志文件&#xff0c;先用编辑器硬扛着翻了好几分钟&#xff0c;然后开始CtrlF一个关键词&#xff0c;没搜到&#…

阅读更多 →
CNN-GRU-Attention负荷预测模型详解:从原理到TensorFlow落地 2026/10/1 3:02:58

CNN-GRU-Attention负荷预测模型详解:从原理到TensorFlow落地

简介&#xff1a;面向电气领域时间序列预测任务的深度学习项目资源包&#xff0c;以CNN-GRU-Attention混合模型为核心&#xff0c;适用于电力负荷预测、设备状态识别、故障趋势分析等场景&#xff0c;也适合算法对比和科研复现。压缩包共8个文件&#xff0c;主要包含Python训练…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉