☰
OpenHarmony上用Flutter开发血压记录App:环境搭建与状态管理实战
2026/10/7 22:03:19 网站建设 项目流程

最近我在做一个 OpenHarmony 平台上的个人健康管理 App,里面最核心也最刚需的一个模块就是血压记录。本来这个功能用 ArkTS 写也能做,但我最后还是选了 Flutter,原因很简单:团队本来的移动端技术栈就是 Flutter,直接复用组件和状态管理方案,省掉一大半重复工作。OpenHarmony 官方这几年一直在推 ArkTS,但 Flutter 生态并没有闲着,已经有比较成熟的移植分支,日常的 UI 搭建、状态管理、本地存储这些能力基本都能跑起来。

这篇就把“添加血压记录”从零到一的完整实现过程拆开讲一遍。不光是贴代码,还会说清楚为什么要这么设计,Provider 到底解决什么问题,表单校验怎么做得不容易被吐槽,以及在 OpenHarmony 真机上调试时最容易踩的几个坑。如果你正准备在 OpenHarmony 上起一个 Flutter 项目,或者想给自己的健康类 App 加一个记录模块,这篇可以直接当参考。

1. 方案选型:为什么我在 OpenHarmony 上用 Flutter 做健康记录 App

1.1 Flutter 与 ArkTS 的取舍,别被“谁更流行”带偏

网上关于“ArkTS 和 Flutter 谁更流行”的讨论一直没停过。流行度这种事情,其实跟你手头要解决的问题没什么关系。ArkTS 确实是 OpenHarmony 应用层的亲儿子,系统组件适配最到位,调原生能力最直接。但如果你只是想在 OpenHarmony 上快速落地一个偏业务向的 App,Flutter 的优势非常明显:跨端复用、热重载开发效率高、Dart 语言本身的上手门槛比 TypeScript 衍生出来的 ArkTS 低。

OpenHarmony 系统底层并不是用 ArkTS 写的,ArkTS 只是应用层推荐语言。真到了工程化层面,Flutter 的一套 UI 描述、组件渲染、状态管理思路完全可以平移到 OpenHarmony 上,前提是拿到对应的 Flutter for OpenHarmony SDK。我的实际体感是:列表、表单、卡片这种典型业务页面,Flutter 写起来比 ArkTS 顺手很多,尤其是状态一多的时候,Provider 配合声明式 UI 直接省掉一堆 setter 回调。

题外话:Flutter 的渲染引擎 Impeller 在移动端逐渐成为默认选项,OpenHarmony 适配分支上还是以 Skia 为主。这个细节对业务开发影响不大,但你要是做复杂动画或者排查渲染异常,得知道有这么个差异。

1.2 血压记录模块到底需要哪些功能

很多新手做健康类 App 一上来就铺功能,今天加心率、明天加血糖、后天加 BMI,结果每个模块都做得稀烂。我建议把一个血压记录模块先收敛好。

我的做法是先列核心使用场景:

  • 用户打开 App,看到最近的血压记录列表。
  • 用户点击“添加血压”,进入填写页面。
  • 用户输入收缩压、舒张压、心率,可选填备注。
  • 保存后回到列表,新纪录排在最前面。
  • 如果某条记录填错了,可以删除。

就这么点需求,不涉及云同步,不涉及图表曲线,先把链路跑通。等基础版本稳定了,再考虑趋势图表、用药提醒、多用户分权之类的进阶能力。过早设计复杂度只会让自己在真机调试的时候怀疑人生。

1.3 项目整体结构与模块划分

模块划分上我用了最朴素的分层方式,没有引一堆架构库:

lib/ ├── main.dart // 入口,配置 Provider ├── models/ │ └── blood_pressure_record.dart // 数据模型 ├── providers/ │ └── blood_pressure_provider.dart // 状态管理 ├── pages/ │ ├── home_page.dart // 列表首页 │ ├── record_list.dart // 血压记录列表组件 │ └── add_record_page.dart // 添加记录页 ├── utils/ │ └── validators.dart // 校验工具 └── services/ └── local_store.dart // 本地存储封装

这套结构没有刻意做抽象,但每个文件职责明确。后续想加血糖模块,照着 models、providers、pages 这三层复制一套就行。本质上所有“记录类功能”都是同一个套路:数据模型 + 表单 + 列表 + 持久化。

2. 环境搭建与工程接入:OpenHarmony 版 Flutter 的“第一道坎”

2.1 环境到底要配哪些东西

OpenHarmony 上跑 Flutter 并不是装个官方 Flutter SDK 就行,需要用针对 OpenHarmony 的 Flutter 分支。这套分支一般由 OpenHarmony 生态伙伴在维护,支持 OpenHarmony 的 ArkUI 组件桥接和系统 API 调用。

我当时配置环境花了大半天,核心步骤整理如下:

  1. 安装 DevEco Studio 和 OpenHarmony SDK,这是基础运行环境。
  2. 准备对应版本的 Flutter for OpenHarmony SDK,下载后配置到环境变量。
  3. 创建 Flutter 工程后,通过集成方式接入到 OpenHarmony 工程里,不是直接flutter run就能完事。
  4. 用 DevEco Studio 打开 harmony 目录,配置签名和调试证书。

这个步骤听起来简单,但版本匹配是个大坑。OpenHarmony SDK 版本、Flutter 分支版本、DevEco Studio 版本三者必须对齐,跨版本经常出现编译过但运行崩溃的情况。我的建议是直接看分支仓库的 README,上面会写清楚对应关系。

2.2 新建项目跑不起来的常见原因

“flutter 新建项目后跑不起来”这个问题我周围同事遇到太多了,搜索引擎里也是高频词。常见的表现包括:编译到一半报 Gradle 错误、真机连接不上、Dart VM 初始化异常。根据我的排查经验,绝大多数情况下不是代码问题,而是环境不匹配。

比如那种you are applying flutter's main gradle plugin imperatively using the apply的报错,就是 Gradle 插件应用方式不对。Flutter 的新版本 Gradle 插件要求在 settings.gradle 里声明插件,而不是在 build.gradle 里用老式apply方式。解决方案就是删掉原来 build.gradle 里的apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle",改成标准插件声明方式。

再比如运行时报[error:flutter/runtime/dart_vm_initializer.cc(41)] unhandled这种神秘的 Dart 异常,多半是原生插件注册失败或 SDK 版本不匹配。我在 OpenHarmony 真机上遇到过一次,最后发现是某个依赖库的 so 库只编译了 ARM64,没有适配目标设备的 ABI。

建议:升级 Flutter 分支版本之后,最好清理一遍 build 目录,执行flutter clean再重新构建。增量编译有时候真的会让人找半天错误。

2.3 两个值得提前知道的渲染与调试细节

先说 Impeller。OpenHarmony 版 Flutter 默认渲染引擎和 Android 官方版不完全一样,我跑下来没有发现明显渲染问题,但如果你做了复杂的自定义绘制或者大量图片动画,建议尽早针对目标设备做一轮真机测试,别在模拟器上看得挺流畅就当没事了。还有就是日志排查,OpenHarmony 的日志系统是 hilog,Flutter 的日志输出不一定全在 Flutter console 里,有时候异常信息只在 DevEco Studio 的 Log 窗口里才有,两边配合着看效率才高。

3. 添加血压记录的完整实现

3.1 先定数据模型:字段校验比 UI 重要

血压记录听起来就是“收缩压 + 舒张压 + 心率 + 时间”,但实际做的时候会有一堆细节。数据模型我这样定义:

class BloodPressureRecord { final int systolic; final int diastolic; final int pulse; final DateTime measuredAt; final String note; const BloodPressureRecord({ required this.systolic, required this.diastolic, required this.pulse, required this.measuredAt, this.note = '', }); double get meanArterialPressure => (systolic + 2 * diastolic) / 3; bool get isNormal => systolic >= 90 && systolic <= 140 && diastolic >= 60 && diastolic <= 90 && pulse >= 50 && pulse <= 100; Map<String, dynamic> toMap() { return { 'systolic': systolic, 'diastolic': diastolic, 'pulse': pulse, 'measuredAt': measuredAt.millisecondsSinceEpoch, 'note': note, }; } factory BloodPressureRecord.fromMap(Map<String, dynamic> map) { return BloodPressureRecord( systolic: map['systolic'] as int, diastolic: map['diastolic'] as int, pulse: map['pulse'] as int, measuredAt: DateTime.fromMillisecondsSinceEpoch( (map['measuredAt'] as int)), note: map['note'] as String? ?? '', ); } }

字段都设成final,保证不可变性,这样在状态管理里替换数据时不容易出现引用混乱。meanArterialPressure和isNormal是计算属性,分别算平均动脉压和是否正常范围,这两个值界面展示时会用到,直接放在模型里比放在 UI 逻辑里干净很多。

注意:医学范畴的“正常血压”有更严谨的定义,这里只是做逻辑演示,真实产品里应该由专业医学指南来定义判定规则。

时间戳这里我直接用millisecondsSinceEpoch存,不要存格式化字符串,否则后续做按天、按周分组统计时会非常痛苦。格式化是展示层的事情,存储层只处理最原始的数据。

3.2 表单页实现:数字输入、单位提示与交互反馈

添加血压记录的页面是整个模块交互的核心,表单校验做得好不好,直接影响用户对产品的信任感。血压值不是随便填的,收缩压正常区间在 90 到 140 mmHg,舒张压在 60 到 90 mmHg,心率在 50 到 100 次/分钟,超出合理范围的输入必须阻止并给出明确提示。

我用了Form+TextFormField+TextInputFormatter的组合,数字键盘 + 只能输入数字的正则过滤,从源头挡住非法字符:

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class AddRecordPage extends StatefulWidget { const AddRecordPage({super.key}); @override State<AddRecordPage> createState() => _AddRecordPageState(); } class _AddRecordPageState extends State<AddRecordPage> { final _formKey = GlobalKey<FormState>(); final _systolicCtrl = TextEditingController(); final _diastolicCtrl = TextEditingController(); final _pulseCtrl = TextEditingController(); final _noteCtrl = TextEditingController(); @override void dispose() { _systolicCtrl.dispose(); _diastolicCtrl.dispose(); _pulseCtrl.dispose(); _noteCtrl.dispose(); super.dispose(); } String? _validatePressure(String? value, int min, int max, String label) { if (value == null || value.isEmpty) { return '请输入$label'; } final number = int.tryParse(value); if (number == null) { return '$label必须是数字'; } if (number < min || number > max) { return '$label需在$min-$max之间'; } return null; } void _save() { if (!_formKey.currentState!.validate()) { return; } final record = BloodPressureRecord( systolic: int.parse(_systolicCtrl.text), diastolic: int.parse(_diastolicCtrl.text), pulse: int.parse(_pulseCtrl.text), measuredAt: DateTime.now(), note: _noteCtrl.text.trim(), ); context.read<BloodPressureProvider>().addRecord(record); Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('添加血压记录')), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ TextFormField( controller: _systolicCtrl, decoration: const InputDecoration( labelText: '收缩压', suffixText: 'mmHg', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(3), ], validator: (v) => _validatePressure(v, 60, 260, '收缩压'), ), const SizedBox(height: 16), TextFormField( controller: _diastolicCtrl, decoration: const InputDecoration( labelText: '舒张压', suffixText: 'mmHg', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(3), ], validator: (v) => _validatePressure(v, 40, 200, '舒张压'), ), const SizedBox(height: 16), TextFormField( controller: _pulseCtrl, decoration: const InputDecoration( labelText: '心率', suffixText: '次/分钟', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(3), ], validator: (v) => _validatePressure(v, 30, 200, '心率'), ), const SizedBox(height: 16), TextFormField( controller: _noteCtrl, decoration: const InputDecoration( labelText: '备注(可选)', hintText: '比如:晨起后测量', border: OutlineInputBorder(), ), maxLines: 2, ), const SizedBox(height: 32), FilledButton( onPressed: _save, child: const Text('保存记录'), ), ], ), ), ); } }

几个细节说明一下:

  • FilteringTextInputFormatter.digitsOnly直接过滤掉非数字字符,用户输入不了字母和符号,校验压力小很多。
  • LengthLimitingTextInputFormatter(3)限制三位数字,收缩压最大超不过 999,配合范围校验双重兜底。
  • suffixText在输入框尾部显示单位,用户体验比把单位放在 label 里强很多,一眼就能看懂填的是什么量纲。
  • 校验函数_validatePressure做成了通用工具,收缩压、舒张压、心率共用一套逻辑,只是传不同范围参数。

页面写到这里已经可以交互了,但它是“死”的,点击保存只是跳走页面,数据没地方去。接下来就要靠 Provider 了。

3.3 Provider 状态管理:为什么我不用 setState

很多 Flutter 新手从 setState 开始学,页面少的时候能用,但一旦涉及跨页面数据共享,setState 就非常难受。在血压记录这个场景里,添加页保存了记录,列表页要立刻更新,历史记录详情要能查到同一条数据。如果每个页面各管一份数据,凭空多出一堆同步逻辑。所以这里用 Provider 做全局状态管理,这也是搜索“flutter provider 怎么用”时最典型的应用场景。

Provider 是个纯 Dart 库,不依赖 Android/iOS 原生通道,所以在 OpenHarmony 的 Flutter 分支上可以放心用。整个实现分三步:

第一步,定义 Provider。继承ChangeNotifier,持有一份List<BloodPressureRecord>,提供增删方法:

import 'package:flutter/foundation.dart'; import '../models/blood_pressure_record.dart'; class BloodPressureProvider extends ChangeNotifier { List<BloodPressureRecord> _records = []; List<BloodPressureRecord> get records => List.unmodifiable(_records); void addRecord(BloodPressureRecord record) { _records.insert(0, record); notifyListeners(); } void removeRecord(String id) { _records.removeWhere((r) => r.measuredAt.millisecondsSinceEpoch == id); notifyListeners(); } void loadRecords(List<BloodPressureRecord> records) { _records = List.of(records); notifyListeners(); } }

这里返回List.unmodifiable(_records)是为了防止外部直接修改列表,所有变更必须走 Provider 的方法,保证状态流动是单向的、可控的。

第二步,在入口用MultiProvider注册。不止一个状态的时候MultiProvider比层层嵌套ChangeNotifierProvider好看得多:

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'providers/blood_pressure_provider.dart'; import 'pages/home_page.dart'; void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => BloodPressureProvider()), ], child: const HealthApp(), ), ); } class HealthApp extends StatelessWidget { const HealthApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '健康记录', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: HomePage(), ); } }

第三步,在具体页面里使用。关键点在context的使用时机,很多人在这里踩坑。

添加页保存时用context.read<BloodPressureProvider>().addRecord(record),因为它是一次性动作,只需要读取 Provider 实例,不需要监听。

列表页展示时用context.watch<BloodPressureProvider>(),因为列表需要响应数据变化,每次notifyListeners()被调用,列表页就会重新构建。

我的列表页核心逻辑:

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/blood_pressure_provider.dart'; import '../models/blood_pressure_record.dart'; class RecordList extends StatelessWidget { const RecordList({super.key}); @override Widget build(BuildContext context) { final provider = context.watch<BloodPressureProvider>(); final records = provider.records; if (records.isEmpty) { return const Center( child: Text('还没有血压记录,点击右下角按钮添加'), ); } final recent = records.take(3).toList(); final avgSystolic = recent.fold<int>(0, (sum, r) => sum + r.systolic) ~/ recent.length; final avgDiastolic = recent.fold<int>(0, (sum, r) => sum + r.diastolic) ~/ recent.length; return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _MetricItem(label: '平均收缩压', value: '$avgSystolic mmHg'), _MetricItem(label: '平均舒张压', value: '$avgDiastolic mmHg'), ], ), ), ), ), Expanded( child: ListView.builder( itemCount: records.length, itemBuilder: (context, index) { final record = records[index]; return _RecordCard(record: record); }, ), ), ], ); } }

这套逻辑清晰地解释了“flutter 组件通信”最核心的问题:状态提升 + 共享数据源。页面之间不直接通信,而是通过共同的 Provider 中转。这也解决了flutter 组件通信里经常被问到的“ListView 点击某个 item 更新另一个组件”的问题,本质就是让两个组件监听同一个状态。

3.4 数据持久化:先本地存,再谈云同步

Flutter 原生的shared_preferences插件依赖于 Android/iOS 的原生实现,在 OpenHarmony 上能不能直接用,取决于适配版分支有没有把对应平台通道接上。我当时的方案比较稳妥:数据量不大时用 shared_preferences 的 OpenHarmony 适配版本,把记录序列化成 JSON 数组存,读出来再反序列化成对象。如果数据量大了、要按时间区间查询了,再考虑接 SQLite 或者 OpenHarmony 的分布式数据库。

我写了一个简单的LocalStore封装,主要解决两个问题:读取时 JSON 解析容错,写入时同步阻塞还是异步落盘。

import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/blood_pressure_record.dart'; class LocalStore { static const _key = 'blood_pressure_records'; static const _maxRecords = 500; static Future<List<BloodPressureRecord>> loadRecords() async { final prefs = await SharedPreferences.getInstance(); final jsonString = prefs.getString(_key); if (jsonString == null || jsonString.isEmpty) { return []; } try { final list = jsonDecode(jsonString) as List<dynamic>; return list .map((e) => BloodPressureRecord.fromMap(e as Map<String, dynamic>)) .toList(); } catch (_) { return []; } } static Future<void> saveRecords(List<BloodPressureRecord> records) async { final prefs = await SharedPreferences.getInstance(); final limited = records.take(_maxRecords).toList(); final jsonString = jsonEncode( limited.map((r) => r.toMap()).toList(), ); await prefs.setString(_key, jsonString); } }

_maxRecords = 500是容量保护,防止记录无限膨胀把 SharedPreferences 撑爆。每条记录包含时间戳和备注,纯文本 JSON,500 条也就几十 KB,完全可控。真到了 500 条之后,我会引导用户做数据导出或清理,那已经是另一个功能了。

在 Provider 里把加载和保存串起来,启动时加载,变更时保存:

class BloodPressureProvider extends ChangeNotifier { List<BloodPressureRecord> _records = []; Future<void> init() async { _records = await LocalStore.loadRecords(); notifyListeners(); } Future<void> addRecord(BloodPressureRecord record) async { _records.insert(0, record); notifyListeners(); await LocalStore.saveRecords(_records); } }

启动时在 main 里调一次provider.init(),数据就自动从磁盘恢复了。这里要注意SharedPreferences的读写是异步的,如果用户快速添加多条记录,理论上会有并发写覆盖的问题。我的经验是:本地单机场景,用户操作频率不会高到触发竞态,真在意的话可以做一个简单的写入队列,串行化写操作。

3.5 记录列表与趋势展示

列表展示阶段我做了两层信息:顶部是三天的平均血压卡片,下面是每条记录的完整卡片。平均血压其实不算复杂逻辑,就是取最近三条记录求算术平均,但用户看到“平均收缩压 118 mmHg”这种信息时,会觉得这个 App 不是简单的记账本,而是有基础分析能力的。

单条记录卡片我用_RecordCard组件实现,卡片左侧是日期时间,中间是收缩压/舒张压的/形式,右侧是心率。正常的记录用常规底色,超出正常范围的记录用浅红色底纹标注,一眼就能看出异常:

class _RecordCard extends StatelessWidget { final BloodPressureRecord record; const _RecordCard({required this.record}); @override Widget build(BuildContext context) { final normal = record.isNormal; return Card( color: normal ? null : Colors.red.shade50, child: ListTile( title: Text('${record.systolic}/${record.diastolic} mmHg'), subtitle: Text( '心率 ${record.pulse} 次/分钟\n${record.note}', ), trailing: Text( '${record.measuredAt.month}-${record.measuredAt.day} ' '${record.measuredAt.hour}:${record.measuredAt.minute.toString().padLeft(2, '0')}', ), onLongPress: () => _showDeleteDialog(context), ), ); } }

删除交互我用的是长按弹出确认框。这里有个产品细节:像血压记录这种健康数据,用户误删的代价很高,所以删除必须二次确认,绝对不能滑动直接删。这是我做健康类产品的一个坚持。

4. 实战踩坑与排查实录

4.1 Flutter for OpenHarmony 编译与启动报错速查

我在真机调试过程中,整理了几个典型报错,几乎每个 Flutter 开发者最终都会遇到。为了节省大家的时间,直接做成速查表。

报错特征根因解决方案
you are applying flutter's main gradle plugin imperatively using the applyGradle 插件应用方式过时在 settings.gradle 中用plugins { id "dev.flutter.flutter-plugin-loader" }标准方式声明,删除 build.gradle 里的 apply 语句
e/flutter unhandled exception dart_vm_initializer插件注册失败 / SDK 版本不匹配检查 pubspec 依赖是否为 OpenHarmony 适配版;清理 build 目录重新编译
真机安装后秒退ABI 架构不匹配检查是否缺少对应设备的 so 库,确认 Flutter 分支对目标 CPU 架构支持
界面中文显示为方块字体资源未打包在工程中显式配置中文字体文件,避免依赖系统字体

项目跑不起来的情况下,第一步永远是看完整日志而不是搜报错关键句。很多报错的根因和表现症状相差十万八千里,日志里往往前几行就有真正的线索。我见过太多人盯着Unhandled Exception查半天,结果根因是某个插件版本不兼容。补充一个习惯:每次改完依赖锁文件,第一时间flutter clean再跑,能够规避很多“莫名其妙”的启动失败。

4.2 Provider 与组件通信的常见坑

Provider 使用过程中遇到的坑,大多集中在context的误用上。最常见的错误是在build方法里调用context.read去读取数据做异步操作,或者在dispose之后再去访问 Provider 实例。

另外一个高频问题:MultiProvider的位置。ChangeNotifierProvider必须在MaterialApp外层或者至少是home的祖先级,不然页面里context.watch找不到 Provider 会直接抛异常。如果出现ProviderNotFoundException,先看提供者在组件树上是不是被包在正确位置。组件通信也是这样,父组件数据往子组件传用构造参数,跨层级共享用 Provider,这个边界要清晰。

我常用的排查技巧:在 Provider 的构造方法里打印日志,确认它被实例化的时机;在notifyListeners()前故意打一个断点,看数据变化的调用链路。这两个方法基本能解决 80% 的状态问题。

4.3 真机调试与发布阶段的小经验

OpenHarmony 真机调试和 Android 略不一样,连接设备后先用hdc list targets确认设备在线。日志方面,hilog和 Flutter 的print输出位置不同,调试时开着 DevEco Studio 的 Log 面板,同时开 Flutter 的 log 面板,两边对照看。我之前调试血压保存逻辑时,Flutter 侧只打印到Saved,但存储实际失败了,最后是看 DevEco 侧的进程日志才发现是权限问题。OpenHarmony 对文件读写、数据持久化有权限管控,manifest 里缺了权限声明,数据写入可能被静默丢弃,很坑。

发布阶段的注意事项:App 图标、应用名称、版本号在 DevEco 工程里配置,签名证书要提前申请。OpenHarmony 应用发布审核没有安卓那么复杂,但对隐私权限的描述要求很严格,健康类应用尤其如此,涉及血压数据属于敏感健康信息,在产品里要做隐私政策提示,在代码层面不要做任何未经用户授权的数据上报。作为开发者,这一点要有底线意识。

一点个人体会

把血压记录这个模块完整跑通之后,我最大的感受是:Flutter 在 OpenHarmony 上的成熟度比网上的评价要乐观,但你需要接受“官方文档不够集中”的现实。大多数适配细节要靠自己读源码、看 issue、跑 demo 来摸索。这个项目的核心逻辑,也就是数据模型 + 表单校验 + Provider 状态管理 + 本地存储的组合,放到任何健康记录类 App 上都能复用。后续如果再做血糖记录、体重记录,直接把 BloodPressure 这一套模型和页面复制改造一下,一两天就能推一个新模块出来。

最后分享一个小技巧:在 Provider 里写loadRecords和saveRecords时,可以先不接存储,用内存数据跑通整个 UI 流程,确认交互逻辑没问题再接持久化。这样调试的时候不用反复清数据,开发效率能高不少。健康类 App 的开发,功能可以慢慢加,但数据准确性和操作安全性这两件事,从一开始就要放在第一位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询