🎯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))),
    );
  }
}

📱 调试与运行完整步骤

  1. 停止上一个项目运行
  2. 打开项目根目录 pubspec.yaml,添加 shared_preferences 依赖
  3. 终端执行依赖安装命令
    flutter pub get
  4. 打开 lib/main.dart,修改原有代码,上方有完整代码
  5. 终端执行运行命令
    flutter run
  6. 选择 OpenHarmony 模拟器,编译完成后进入健康录入页面
  7. 测试选择性别、填写全部信息,点击保存提示存储成功
  8. 关闭应用重新打开,数据可后续读取,实验完成

🔐 跨平台适配说明

本次 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 才可以正常导入
  • 代码可直接全覆盖使用,无需额外修改逻辑
  • 点赞收藏不迷路,后续每日开发笔记持续同步更新!

    更多推荐