Flutter 跨平台实战:OpenHarmony 健康管理应用 Day6|基于 SharedPreferences 的数据本地持久化实现
🎯Flutter 跨平台实战:OpenHarmony 健康管理应用 Day6|基于 SharedPreferences 的数据本地持久化实现
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🚀 前言
大家好,本篇是我真实完成Flutter + OpenHarmony 健康管理应用的 Day6 开发笔记,基于 Day1-Day5 已实现的多页面数据闭环,继续实现数据本地持久化功能,通过引入第三方库shared_preferences完成用户信息与健康数据的本地存储与读取,实现关闭应用数据不丢失的完整功能,全程只用 DevEco Studio 开发,一步不漏、全部写细,新手照着做就能一次成功!
我会把第三方库集成、数据存储与读取逻辑、跨平台适配的全过程记录得特别详细,照着做就能实现 Flutter 应用本地数据持久化,为后续完整应用开发打下坚实基础。
💥 本文你能学到!!!!!!全部都是干货!!!!!!
- Flutter 第三方库
shared_preferences的完整集成方法 - 数据本地持久化的实现逻辑(存储 / 读取 / 更新)
- 应用启动自动读取本地数据的方法
- 表单数据提交与本地存储的联动逻辑
- 适配 OpenHarmony 的数据存储规范写法
- Web 端本地存储调试与踩坑解决方案
- 跨平台应用数据持久化的通用实现思路
🥝 开发环境与项目准备
1. 开发环境
- 开发工具:DevEco Studio(全程仅使用这一个工具,不使用其他编辑器)
- 开发语言:Dart
- 框架:Flutter(OpenHarmony 适配版本)
- 调试方式:Web 浏览器端调试(避开模拟器配置繁琐问题,完整验证数据持久化功能)
- 后续适配:OpenHarmony 模拟器及真机,当前数据存储代码可直接兼容
.hap打包发布
2. 项目准备
基于 Day5 已实现多页面数据闭环的项目,无需额外新建工程,直接在原项目中迭代开发,确保:
- 已完成用户信息录入、表单校验、多页面数据传递功能(Day1-Day5)
- 项目依赖完整、无报错,可正常启动运行
- 保留完整 ohos 跨平台适配目录,不破坏原有工程结构
- 已添加
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 平台。
📱 调试与运行完整流程
- 安装第三方依赖:在项目根目录执行
flutter pub get,确保shared_preferences依赖安装成功。 - 启动调试服务:在 DevEco Studio 内置终端输入
flutter run -d web-server,启动本地调试服务。 - 访问应用首页:复制终端输出的本地地址,在浏览器中打开,进入用户信息录入页面。
- 测试数据存储:填写用户信息,点击 “保存并下一步”,数据同步保存到本地。
- 测试数据持久化:关闭浏览器并重新打开,表单会自动填充之前填写的数据,验证持久化效果。
- 全流程功能测试:页面跳转、表单校验、数据存储与读取均正常运行,无报错。
🔐 跨平台适配说明
本次开发的数据持久化代码,依托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 数据可视化开发笔记更新不迷路!
