Flutter for OpenHarmony实战DAY3:从零搭建健康管家App之新增手动更改健康数据功能,完整本地持久化业务逻辑

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言

在上一篇文章中,我们完成了健康管家 App 的数据可视化图表(步数、心率、睡眠趋势图)。
但一个完整的健康 App,必须支持用户手动管理自己的健康数据。
本篇将带你实现:
✅ 手动录入健康数据(步数、心率、睡眠时长)
✅ 编辑已有数据
✅ 删除不需要的数据
✅ 本地持久化存储(重启 App 数据不丢失)
✅ 数据与图表页面自动联动
✅ 表单校验 + 用户友好提示
✅ 兼容 Flutter + OpenHarmony 鸿蒙双平台

一、功能说明
本功能模块包含以下核心能力:

  1. 数据录入:用户输入步数、心率、睡眠时长,自动记录当前日期
  2. 数据编辑:点击编辑按钮,快速修改已有记录
  3. 数据删除:一键删除无用记录
  4. 本地存储:使用 shared_preferences 实现数据持久化
  5. 数据校验:防止空数据、无效数据提交
  6. 列表展示:清晰展示所有历史健康记录
  7. 图表联动:录入的数据自动同步到数据统计页面

二、添加项目依赖
在 pubspec.yaml 中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  # 图表(上一篇已添加)
  fl_chart: ^0.65.0
  # 本地持久化存储
  shared_preferences: ^2.5.3
  # 时间格式化
  intl: ^0.19.0

执行命令安装依赖:

flutter pub get

三、完整核心代码实现
3.1 健康数据管理页面(录入 + 编辑 + 删除)
新建文件:pages/health_record_page.dart
在这里插入图片描述

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'package:intl/intl.dart';

class HealthRecordPage extends StatefulWidget {
  const HealthRecordPage({super.key});

  @override
  State<HealthRecordPage> createState() => _HealthRecordPageState();
}

class _HealthRecordPageState extends State<HealthRecordPage> {
  // 健康数据列表
  List<Map<String, dynamic>> _healthRecords = [];

  // 输入框控制器
  final TextEditingController _stepController = TextEditingController();
  final TextEditingController _heartController = TextEditingController();
  final TextEditingController _sleepController = TextEditingController();

  // 标记当前是否为编辑状态
  int? _editIndex;

  @override
  void initState() {
    super.initState();
    // 页面初始化时加载本地数据
    _loadLocalData();
  }

  // 从本地加载健康数据
  Future<void> _loadLocalData() async {
    final prefs = await SharedPreferences.getInstance();
    final String? dataStr = prefs.getString("health_records");
    if (dataStr != null) {
      setState(() {
        _healthRecords = List<Map<String, dynamic>>.from(json.decode(dataStr));
      });
    }
  }

  // 保存数据到本地
  Future<void> _saveLocalData() async {
    final prefs = await SharedPreferences.getInstance();
    prefs.setString("health_records", json.encode(_healthRecords));
  }

  // 提交数据(新增 / 编辑)
  void _submitData() {
    // 获取输入内容并校验
    final int step = int.tryParse(_stepController.text) ?? 0;
    final int heart = int.tryParse(_heartController.text) ?? 0;
    final double sleep = double.tryParse(_sleepController.text) ?? 0.0;

    if (step <= 0 || heart <= 0 || sleep <= 0) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请输入有效的健康数据!")),
      );
      return;
    }

    // 构建数据模型
    final Map<String, dynamic> newRecord = {
      "date": DateFormat("yyyy-MM-dd").format(DateTime.now()),
      "step": step,
      "heartRate": heart,
      "sleepTime": sleep,
    };

    setState(() {
      if (_editIndex != null) {
        // 编辑模式
        _healthRecords[_editIndex!] = newRecord;
        _editIndex = null;
      } else {
        // 新增模式
        _healthRecords.add(newRecord);
      }
    });

    // 保存到本地
    _saveLocalData();
    // 清空输入框
    _clearInput();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(_editIndex != null ? "修改成功!" : "添加成功!")),
    );
  }

  // 编辑数据
  void _editData(int index) {
    final item = _healthRecords[index];
    _stepController.text = item["step"].toString();
    _heartController.text = item["heartRate"].toString();
    _sleepController.text = item["sleepTime"].toString();
    setState(() {
      _editIndex = index;
    });
  }

  // 删除数据
  void _deleteData(int index) {
    setState(() {
      _healthRecords.removeAt(index);
    });
    _saveLocalData();
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text("删除成功!")),
    );
  }

  // 清空输入框
  void _clearInput() {
    _stepController.clear();
    _heartController.clear();
    _sleepController.clear();
    setState(() {
      _editIndex = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("健康数据管理"),
        centerTitle: true,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题
            const Text(
              "添加 / 编辑健康数据",
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 12),

            // 步数输入
            TextField(
              controller: _stepController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                labelText: "今日步数",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 10),

            // 心率输入
            TextField(
              controller: _heartController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                labelText: "静息心率(次/分)",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 10),

            // 睡眠时长输入
            TextField(
              controller: _sleepController,
              keyboardType: TextInputType.numberWithOptions(decimal: true),
              decoration: const InputDecoration(
                labelText: "睡眠时长(小时)",
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),

            // 按钮区域
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _submitData,
                  child: Text(_editIndex == null ? "添加记录" : "保存修改"),
                ),
                OutlinedButton(
                  onPressed: _clearInput,
                  child: const Text("清空"),
                ),
              ],
            ),

            const Divider(height: 30, thickness: 1),

            // 历史记录列表
            const Text(
              "历史健康记录",
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),

            Expanded(
              child: _healthRecords.isEmpty
                  ? const Center(child: Text("暂无记录,快去添加吧~"))
                  : ListView.builder(
                      itemCount: _healthRecords.length,
                      itemBuilder: (context, index) {
                        final item = _healthRecords[index];
                        return Card(
                          margin: const EdgeInsets.symmetric(vertical: 6),
                          child: ListTile(
                            title: Text("${item['date']} | 步数:${item['step']}"),
                            subtitle: Text(
                              "心率:${item['heartRate']} 次/分 | 睡眠:${item['sleepTime']}h",
                            ),
                            trailing: SizedBox(
                              width: 100,
                              child: Row(
                                children: [
                                  IconButton(
                                    icon: const Icon(Icons.edit, color: Colors.blue),
                                    onPressed: () => _editData(index),
                                  ),
                                  IconButton(
                                    icon: const Icon(Icons.delete, color: Colors.red),
                                    onPressed: () => _deleteData(index),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        );
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }
}

3.2 主页面底部导航加入入口(main.dart)

import 'package:flutter/material.dart';
import 'pages/home_page.dart';
import 'pages/data_page.dart';
import 'pages/health_record_page.dart';
import 'pages/setting_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '健康管家',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      debugShowCheckedModeBanner: false,
      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(),
    const DataPage(),
    const HealthRecordPage(), // 数据管理页
    const SettingPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        type: BottomNavigationBarType.fixed,
        onTap: (index) => setState(() => _currentIndex = index),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.bar_chart), label: "统计"),
          BottomNavigationBarItem(icon: Icon(Icons.edit_note), label: "记录"),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: "设置"),
        ],
      ),
    );
  }
}

3.3 图表页面自动读取录入数据(data_page.dart 关键代码)

// 加载本地健康数据,与录入页面完全联动
Future<void> _loadData() async {
  final prefs = await SharedPreferences.getInstance();
  final data = prefs.getString("health_records");
  if (data != null) {
    setState(() {
      healthData = List<Map<String, dynamic>>.from(json.decode(data));
    });
  }
}

四、功能运行效果

  1. 录入数据:填写步数、心率、睡眠,点击添加记录
  2. 编辑数据:点击列表编辑图标,自动填充输入框,修改后保存
  3. 删除数据:点击删除图标,一键移除并同步本地存储
  4. 数据持久化:关闭重启 App,数据依然保留
  5. 图表联动:进入统计页面,自动展示录入的数据趋势
    在这里插入图片描述
    在这里插入图片描述

五、代码亮点(CSDN 高赞必备)

  • 完整业务逻辑:真正实现增删改查,不是单纯 UI 演示
  • 本地持久化:使用行业通用 shared_preferences
  • 数据校验:防止非法输入,提升用户体验
  • 组件封装:代码结构清晰,易于维护和扩展
  • 双平台兼容:完美支持 Android /iOS/ OpenHarmony 鸿蒙
  • 注释齐全:每一个核心方法都配有详细注释

六、常见问题解决

  1. 依赖报错
    执行:
flutter clean
flutter pub get
  1. 图表不显示
    确保先录入数据,图表页面下拉刷新即可加载
  2. 数据不保存
    检查 shared_preferences 依赖是否正常安装
  3. 鸿蒙运行报错
    使用 DevEco Studio 打开项目,重新同步 Flutter 模块

总结

本篇我们完成了健康管家 App最核心的业务功能:
✅ 健康数据录入
✅ 数据编辑
✅ 数据删除
✅ 本地存储
✅ 图表联动
项目已经具备完整 App 的基础架构

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐