Flutter 跨平台实战:OpenHarmony 健康管理应用 Day5|引入本地存储 + 新增性别单选选项
·
🎯Flutter 跨平台实战:OpenHarmony 健康管理应用 Day5|引入本地存储 + 新增性别单选选项
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🚀 前言
大家好,本篇是 Flutter + OpenHarmony 健康管理应用 开发系列的第五篇笔记。在 Day4 完成表单非空校验与提交提示功能的基础上,今天主要完成两大核心任务:第一,引入 shared_preferences 第三方本地持久化存储依赖;第二,在健康录入表单中新增性别单选选项,使用 Radio 单选按钮实现男女选择,并将性别信息和其他健康数据一起保存到本地,实现关闭应用重启后数据不丢失。全程适配 OpenHarmony 鸿蒙模拟器,步骤完整、代码可直接复制运行,适合课程实训作业提交,自查分值高、结构规范。
💥 本文你能学到
- Flutter 第三方依赖
shared_preferences引入与配置 - pubspec.yaml 依赖添加规范写法
- Radio 单选按钮组件实现性别选择功能
- 单选按钮状态管理与选中值获取
- 多表单字段包含性别一起本地存储保存
- 鸿蒙端第三方依赖适配与编译运行方法
🥝 开发环境
1. 环境信息
- 开发工具:DevEco Studio
- 开发语言:Dart
- 开发框架:Flutter
- 调试设备:OpenHarmony 手机模拟器
- 适配平台:OpenHarmony
2. 依赖配置
Day5 需要新增本地存储依赖,手动修改 pubspec.yaml:
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.2

📝 今日核心开发功能
- 配置并引入 shared_preferences 本地持久化存储依赖
- 健康录入页面新增性别男女单选按钮组件
- 实现性别选中状态管理与值获取
- 提交时连同姓名、年龄、身高、体重、心率、性别一起校验
- 合法信息全部保存到本地存储
- 适配鸿蒙布局,单选组件显示正常无错乱
✅ 完整可运行核心代码
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '鸿蒙健康管理',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: const MainPage(),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
final List<Widget> _pages = const [
HomePage(),
HealthInputPage(),
ProfilePage(),
];
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onItemTapped,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
BottomNavigationBarItem(icon: Icon(Icons.add_chart), label: "健康录入"),
BottomNavigationBarItem(icon: Icon(Icons.person), label: "个人中心"),
],
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("首页")),
body: const Center(
child: Text("欢迎使用健康管理应用", style: TextStyle(fontSize: 18)),
),
);
}
}
class HealthInputPage extends StatefulWidget {
const HealthInputPage({super.key});
@override
State<HealthInputPage> createState() => _HealthInputPageState();
}
class _HealthInputPageState extends State<HealthInputPage> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _ageController = TextEditingController();
final TextEditingController _heightController = TextEditingController();
final TextEditingController _weightController = TextEditingController();
final TextEditingController _heartRateController = TextEditingController();
String _gender = "男";
// 保存方法(带第三方库 + 必弹提示)
Future<void> _saveData() async {
String name = _nameController.text.trim();
String age = _ageController.text.trim();
String height = _heightController.text.trim();
String weight = _weightController.text.trim();
String heart = _heartRateController.text.trim();
// 不填写 → 必提示
if (name.isEmpty || age.isEmpty || height.isEmpty || weight.isEmpty || heart.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("请填写完整信息")),
);
return;
}
// 保存到本地存储(第三方库)
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString("name", name);
await prefs.setString("age", age);
await prefs.setString("height", height);
await prefs.setString("weight", weight);
await prefs.setString("heart", heart);
await prefs.setString("gender", _gender);
// 保存成功 → 必提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("✅ 数据保存成功")),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("健康录入")),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("姓名", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
TextField(
controller: _nameController,
decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入姓名"),
),
const SizedBox(height: 16),
const Text("性别", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
Row(
children: [
Expanded(
child: ListTile(
title: const Text("男"),
leading: Radio<String>(
value: "男",
groupValue: _gender,
onChanged: (val) {
setState(() {
_gender = val!;
});
},
),
),
),
Expanded(
child: ListTile(
title: const Text("女"),
leading: Radio<String>(
value: "女",
groupValue: _gender,
onChanged: (val) {
setState(() {
_gender = val!;
});
},
),
),
),
],
),
const SizedBox(height: 16),
const Text("年龄", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
TextField(
controller: _ageController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入年龄"),
),
const SizedBox(height: 16),
const Text("身高(cm)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
TextField(
controller: _heightController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入身高"),
),
const SizedBox(height: 16),
const Text("体重(kg)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
TextField(
controller: _weightController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入体重"),
),
const SizedBox(height: 16),
const Text("心率(次/分)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
TextField(
controller: _heartRateController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入心率"),
),
const SizedBox(height: 30),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _saveData,
child: const Text("保存数据", style: TextStyle(fontSize: 18)),
),
),
],
),
),
);
}
@override
void dispose() {
_nameController.dispose();
_ageController.dispose();
_heightController.dispose();
_weightController.dispose();
_heartRateController.dispose();
super.dispose();
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("个人中心")),
body: const Center(child: Text("个人信息展示区域", style: TextStyle(fontSize: 16))),
);
}
}
📱 调试与运行完整步骤
- 停止上一个项目运行
- 打开项目根目录
pubspec.yaml,添加 shared_preferences 依赖 - 终端执行依赖安装命令
flutter pub get - 打开
lib/main.dart,修改原有代码,上方有完整代码 - 终端执行运行命令
flutter run - 选择 OpenHarmony 模拟器,编译完成后进入健康录入页面
- 测试选择性别、填写全部信息,点击保存提示存储成功

- 关闭应用重新打开,数据可后续读取,实验完成

🔐 跨平台适配说明
本次 Day5 新增依赖与单选组件完美适配 OpenHarmony 鸿蒙系统:
- shared_preferences 第三方依赖兼容鸿蒙编译环境,无适配报错;
- Radio 单选按钮布局自适应鸿蒙屏幕,点击选中状态切换正常;
- 页面滚动布局依旧适配软键盘弹出,不会遮挡单选区域与输入框;
- 本地存储逻辑在鸿蒙端正常生效,数据持久化保存稳定可靠。
🧩 常见错误排查
| 错误现象 | 解决方法 |
|---|---|
| 导入 shared_preferences 报错 | 检查 pubspec.yaml 依赖格式,执行 flutter pub get 重新拉取依赖 |
| 单选按钮无法切换选中 | 确认 onChanged 中调用 setState 更新 _gender 变量 |
| 选择性别仍提示未选择 | 检查初始默认值是否设置,校验逻辑是否包含性别判空 |
| 编译提示依赖版本冲突 | 使用文中指定版本号,执行 flutter clean 后重新运行 |
🎨 项目后续规划
Day5 已完成本地依赖引入、性别单选功能与全字段本地保存,后续规划:
- Day6:实现首页自动读取本地存储健康数据并展示
- Day7:个人中心页面回显已保存的姓名、性别、身体指标信息
- Day8:新增清空本地数据、重置表单功能
- 继续按任务规划依次完成 UI 美化、BMI 计算、图表可视化等功能
📌 项目总结
本篇笔记完整记录了 Flutter 鸿蒙健康管理项目 Day5 开发全过程,完成了第三方本地存储依赖配置、性别单选按钮开发、全表单数据连同性别一起本地持久化保存的功能。熟练掌握了 pubspec 依赖配置、Radio 单选状态管理、异步本地存储写入、多字段联合校验等核心知识点,同时兼容 OpenHarmony 鸿蒙端运行适配,为后续数据读取展示、个人中心回显等功能筑牢基础。
✅ 结尾小贴士
- 先改 pubspec.yaml 再加依赖,必须执行 flutter pub get 才可以正常导入
- 代码可直接全覆盖使用,无需额外修改逻辑
- 点赞收藏不迷路,后续每日开发笔记持续同步更新!
更多推荐
所有评论(0)