当前位置: 首页 > news >正文

Flutter 跨平台实战:OpenHarmony 健康管理应用 Day6|基于 SharedPreferences 的数据本地持久化实现

🎯Flutter 跨平台实战:OpenHarmony 健康管理应用 Day6|基于 SharedPreferences 的数据本地持久化实现

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

🚀 前言

大家好,本篇是我真实完成Flutter + OpenHarmony 健康管理应用的 Day6 开发笔记,基于 Day1-Day5 已实现的多页面数据闭环,继续实现数据本地持久化功能,通过引入第三方库shared_preferences完成用户信息与健康数据的本地存储与读取,实现关闭应用数据不丢失的完整功能,全程只用 DevEco Studio 开发,一步不漏、全部写细,新手照着做就能一次成功!

我会把第三方库集成、数据存储与读取逻辑、跨平台适配的全过程记录得特别详细,照着做就能实现 Flutter 应用本地数据持久化,为后续完整应用开发打下坚实基础。

💥 本文你能学到!!!!!!全部都是干货!!!!!!

  1. Flutter 第三方库shared_preferences的完整集成方法
  2. 数据本地持久化的实现逻辑(存储 / 读取 / 更新)
  3. 应用启动自动读取本地数据的方法
  4. 表单数据提交与本地存储的联动逻辑
  5. 适配 OpenHarmony 的数据存储规范写法
  6. Web 端本地存储调试与踩坑解决方案
  7. 跨平台应用数据持久化的通用实现思路

🥝 开发环境与项目准备

1. 开发环境

  • 开发工具:DevEco Studio(全程仅使用这一个工具,不使用其他编辑器)
  • 开发语言:Dart
  • 框架:Flutter(OpenHarmony 适配版本)
  • 调试方式:Web 浏览器端调试(避开模拟器配置繁琐问题,完整验证数据持久化功能)
  • 后续适配:OpenHarmony 模拟器及真机,当前数据存储代码可直接兼容.hap打包发布

2. 项目准备

基于 Day5 已实现多页面数据闭环的项目,无需额外新建工程,直接在原项目中迭代开发,确保:

  1. 已完成用户信息录入、表单校验、多页面数据传递功能(Day1-Day5)
  2. 项目依赖完整、无报错,可正常启动运行
  3. 保留完整 ohos 跨平台适配目录,不破坏原有工程结构
  4. 已添加shared_preferences第三方依赖并安装成功

本项目全程基于 Flutter for OpenHarmony 开发,后续会集成更多第三方库完成数据可视化、用户管理等高级功能,本篇为数据持久化开发篇。

📝 核心功能:数据本地持久化实现

1. 功能说明

本次 Day6 开发,通过shared_preferences实现以下核心功能:

  • 数据本地存储:用户信息与健康数据提交时,同步保存到本地
  • 自动数据读取:应用启动时,自动读取本地存储的数据并填充到表单
  • 数据持久化:关闭应用重新打开,之前填写的数据不会丢失
  • 表单联动:数据读取、存储与表单校验、页面跳转无缝联动

2. 完整可运行核心代码(lib/main.dart

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: '鸿蒙健康管理APP', debugShowCheckedModeBanner: false, theme: ThemeData(primarySwatch: Colors.blue), home: const UserInfoPage(), ); } } // ========================== // 页面1:用户信息录入(支持本地保存/读取) // ========================== class UserInfoPage extends StatefulWidget { const UserInfoPage({super.key}); @override State<UserInfoPage> createState() => _UserInfoPageState(); } class _UserInfoPageState extends State<UserInfoPage> { final _formKey = GlobalKey<FormState>(); final nameCtrl = TextEditingController(); final ageCtrl = TextEditingController(); String? gender; // 打开页面时:自动读取本地数据 @override void initState() { super.initState(); loadLocalData(); } // 读取本地保存的数据 Future<void> loadLocalData() async { final sp = await SharedPreferences.getInstance(); setState(() { nameCtrl.text = sp.getString("name") ?? ""; ageCtrl.text = sp.getString("age") ?? ""; gender = sp.getString("gender"); }); } // 保存数据到本地 Future<void> saveLocalData() async { final sp = await SharedPreferences.getInstance(); sp.setString("name", nameCtrl.text); sp.setString("age", ageCtrl.text); sp.setString("gender", gender!); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("用户信息录入"), centerTitle: true, ), body: Padding( padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( children: [ TextFormField( controller: nameCtrl, decoration: const InputDecoration( labelText: "姓名", border: OutlineInputBorder(), ), validator: (v) => v == null || v.isEmpty ? "请输入姓名" : null, ), const SizedBox(height: 15), TextFormField( controller: ageCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: "年龄", border: OutlineInputBorder(), ), validator: (v) => v == null || v.isEmpty ? "请输入年龄" : null, ), const SizedBox(height: 15), DropdownButtonFormField<String>( value: gender, decoration: const InputDecoration( labelText: "性别", border: OutlineInputBorder(), ), items: const [ DropdownMenuItem(value: "男", child: Text("男")), DropdownMenuItem(value: "女", child: Text("女")), ], onChanged: (val) { setState(() { gender = val; }); }, validator: (v) => v == null ? "请选择性别" : null, ), const SizedBox(height: 25), // 保存 + 下一步 ElevatedButton( onPressed: () async { if (_formKey.currentState!.validate()) { await saveLocalData(); // 保存到本地 Navigator.push( context, MaterialPageRoute( builder: (context) => HealthInputPage( name: nameCtrl.text, age: ageCtrl.text, gender: gender!, ), ), ); } }, child: const Text("保存并下一步 → 健康数据"), ), ], ), ), ), ), ); } } // ========================== // 页面2:健康数据录入 // ========================== class HealthInputPage extends StatefulWidget { final String name; final String age; final String gender; const HealthInputPage({ super.key, required this.name, required this.age, required this.gender, }); @override State<HealthInputPage> createState() => _HealthInputPageState(); } class _HealthInputPageState extends State<HealthInputPage> { final formKey = GlobalKey<FormState>(); final heightCtrl = TextEditingController(); final weightCtrl = TextEditingController(); final heartCtrl = TextEditingController(); @override void initState() { super.initState(); loadHealthData(); } // 读取健康数据 Future<void> loadHealthData() async { final sp = await SharedPreferences.getInstance(); setState(() { heightCtrl.text = sp.getString("height") ?? ""; weightCtrl.text = sp.getString("weight") ?? ""; heartCtrl.text = sp.getString("heart") ?? ""; }); } // 保存健康数据 Future<void> saveHealthData() async { final sp = await SharedPreferences.getInstance(); sp.setString("height", heightCtrl.text); sp.setString("weight", weightCtrl.text); sp.setString("heart", heartCtrl.text); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("健康数据录入"), centerTitle: true, ), body: Padding( padding: const EdgeInsets.all(20), child: Form( key: formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("用户:${widget.name}", style: const TextStyle(fontSize: 16, color: Colors.blue)), const SizedBox(height: 15), TextFormField( controller: heightCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: "身高(cm)", border: OutlineInputBorder(), ), validator: (v) => v == null || v.isEmpty ? "请输入身高" : null, ), const SizedBox(height: 15), TextFormField( controller: weightCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: "体重(kg)", border: OutlineInputBorder(), ), validator: (v) => v == null || v.isEmpty ? "请输入体重" : null, ), const SizedBox(height: 15), TextFormField( controller: heartCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: "心率", border: OutlineInputBorder(), ), validator: (v) => v == null || v.isEmpty ? "请输入心率" : null, ), const SizedBox(height: 30), Center( child: ElevatedButton( onPressed: () async { if (formKey.currentState!.validate()) { await saveHealthData(); Navigator.push( context, MaterialPageRoute( builder: (context) => DataShowPage( name: widget.name, age: widget.age, gender: widget.gender, height: heightCtrl.text, weight: weightCtrl.text, heart: heartCtrl.text, ), ), ); } }, child: const Text("提交并查看数据"), ), ), ], ), ), ), ), ); } } // ========================== // 页面3:数据总览页(已修正语法错误) // ========================== class DataShowPage extends StatelessWidget { final String name; final String age; final String gender; final String height; final String weight; final String heart; const DataShowPage({ super.key, required this.name, required this.age, required this.gender, required this.height, required this.weight, required this.heart, }); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("健康数据总览"), centerTitle: true, ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "✅ 用户信息", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("姓名:$name", style: const TextStyle(fontSize: 16)), Text("年龄:$age", style: const TextStyle(fontSize: 16)), Text("性别:$gender", style: const TextStyle(fontSize: 16)), ], ), ), ), const Divider(height: 40), const Text( "✅ 健康数据", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("身高:$height cm", style: const TextStyle(fontSize: 16)), Text("体重:$weight kg", style: const TextStyle(fontSize: 16)), Text("心率:$heart 次/分", style: const TextStyle(fontSize: 16)), ], ), ), ), const SizedBox(height: 40), const Center( child: Text( "🎉 数据已保存到本地!", style: TextStyle(fontSize: 20, color: Colors.blue, fontWeight: FontWeight.bold), ), ), ], ), ), ); } }

3. 关键逻辑与加分点解析

  • 第三方库集成:引入shared_preferences第三方库,实现数据本地存储,完全符合课程要求。
  • 自动数据读取:通过initState钩子,在页面初始化时自动读取本地数据并填充表单,用户体验流畅。
  • 提交同步存储:表单校验通过、页面跳转前,同步将数据保存到本地,确保数据不会丢失。
  • 持久化效果验证:关闭应用重新打开,表单自动填充之前的数据,实现真正的持久化存储。
  • 跨平台兼容shared_preferences是 Flutter 官方推荐的轻量级存储方案,可直接兼容 OpenHarmony 平台。

📱 调试与运行完整流程

  1. 安装第三方依赖:在项目根目录执行flutter pub get,确保shared_preferences依赖安装成功。
  2. 启动调试服务:在 DevEco Studio 内置终端输入flutter run -d web-server,启动本地调试服务。
  3. 访问应用首页:复制终端输出的本地地址,在浏览器中打开,进入用户信息录入页面。
  4. 测试数据存储:填写用户信息,点击 “保存并下一步”,数据同步保存到本地。
  5. 测试数据持久化:关闭浏览器并重新打开,表单会自动填充之前填写的数据,验证持久化效果。
  6. 全流程功能测试:页面跳转、表单校验、数据存储与读取均正常运行,无报错。

🔐 跨平台适配说明

本次开发的数据持久化代码,依托shared_preferences第三方库的跨平台特性,一套代码可支持多端运行:

平台运行方式适配说明
Chrome 浏览器端flutter run -d web-server调试便捷,可直接验证数据存储与读取逻辑
OpenHarmony 模拟器 / 真机配置 OH 环境后直接编译依赖无兼容问题,可直接打包生成.hap安装包

🧩 超全错误排查与解决方案

错误场景现象解决方案
依赖安装失败执行flutter pub get报错检查pubspec.yaml中依赖版本是否正确,网络环境是否正常
数据无法保存提交数据后本地数据为空检查保存逻辑是否正确调用,SharedPreferences.getInstance()是否获取成功
页面空白报错运行后页面无法加载检查pubspec.yaml依赖是否安装成功,执行flutter clean清理缓存后重新运行
数据读取为空应用启动表单无数据检查initState中读取数据的逻辑是否正确,键名是否与保存时一致

🎨 项目后续规划

Day6 已经完成数据本地持久化功能,后续依次开发:

  • Day7:数据可视化展示与页面样式优化
  • Day8+:对接 OpenHarmony 适配、项目整理与打包

📌 项目总结

本篇笔记完整记录了在 DevEco Studio 中实现 Flutter 数据本地持久化的全过程,从第三方库集成、数据存储读取逻辑到调试运行,全程只用一款开发工具,熟悉了shared_preferences第三方库的用法、跨平台数据存储方案与 Web 端调试方式,同时和 Day1-Day5 的跨平台适配逻辑无缝衔接,为后续健康数据管理模块的开发打下了坚实基础。

✅ 结尾小贴士

  • 文中所有代码可以直接复制替换到自己项目中使用
  • 不用额外安装其他编辑器,全程 DevEco Studio 就能完成所有开发
  • 点赞 + 收藏,后续 Day7 数据可视化开发笔记更新不迷路!
http://www.jsqmd.com/news/763587/

相关文章:

  • 拯救你的Minecraft世界:Region Fixer存档修复工具完全指南
  • 德州亚太风机厂家电话
  • 保姆级避坑指南:用PX4 v1.12.3 + Gazebo搞定Offboard模式,解决‘Vehicle armed’失败问题
  • Cursor Free VIP:5步解决Cursor AI试用限制的终极方案
  • 第八部分-周边生态与工具——38. 模型工具
  • 使用mybatis查询所有用户报错,JUnit版本冲突
  • 告别Pyinstaller默认羽毛图标:一个临时ICO文件搞定Python GUI打包三件套
  • Mac本地运行多模态大模型:mlx-vlm环境搭建与性能优化指南
  • 提升网盘开发效率:用快马AI一键生成分片上传与断点续传功能模块
  • 前端调试 - 获取下拉框元素 F12 延时断点操作记录 - 秒杀其他所谓的F8和手速快操作
  • 2026 饮料代理加盟口碑推荐榜|:阿尔卑斯饮品厂家优选指南,饮品批发招商渠道加盟合作怎么选更靠谱 - 海棠依旧大
  • 终极NS模拟器管理指南:如何用NsEmuTools一键搞定Switch游戏环境
  • 第八部分-周边生态与工具——39. 框架集成
  • 正点原子IMX6ULL SR04模块+Qt使用
  • 别再只调参了!深入解读YOLOv8中BiFPN与P2层的协同作用,让你的模型真正‘看懂’小物体
  • 3大核心策略彻底解决腾讯游戏反作弊进程资源占用问题
  • 别再重复造轮子了!Power Apps组件库保姆级教程,从创建到团队共享一次搞定
  • ollama国内镜像源不可用时的替代方案,使用Taotoken快速接入多模型
  • 从扫地机器人到自动驾驶:聊聊移动机器人规划里那些‘前端搜索’与‘后端优化’的实战门道
  • 创业团队如何用Taotoken低成本试错多个大模型接口
  • 深入MBUS电流调制:用普通运放搭建稳定主站接收电路(含Multisim仿真文件)
  • 2026 年 5 月国内外超声波液位计十大品牌排名 - 仪表人小余
  • 2025届最火的五大AI写作神器横评
  • 免费解锁网盘下载速度:开源直链解析工具完整指南
  • 从Matlab仿真到C代码:PMSM FOC位置环S曲线算法(恒定Jerk)的完整实现流程
  • 5分钟快速上手:明日方舟智能基建管理完整指南
  • 别再用pip install paddle了!手把手教你用conda搞定PaddlePaddle环境(附CUDA版本选择指南)
  • 【Matlab】MATLAB教程:LaTeX与MATLAB结合实操(LaTeX公式生成案例+学术论文专业排版核心应用)
  • 3个技巧让网盘文件下载速度提升5倍:LinkSwift直链解析工具深度解析
  • 别再死磕协议文档了!用Python模拟FiRa UWB测距调度,5分钟搞懂Controller和Controlee怎么对话