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

【Flutter for open harmony 】Flutter三方库Dio网络请求+熬夜记录列表的鸿蒙化适配与实战指南

【Flutter for open harmony 】Flutter三方库Dio网络请求熬夜记录列表的鸿蒙化适配与实战指南欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net大家好我是ShineQiu上海某高校计算机科学与技术专业大二在读学生。这段时间身边很多同学都在熬夜赶作业、刷手机经常吐槽不知道自己熬了多久、长期熬夜有没有影响身体我自己也有熬夜的习惯有时候熬完第二天昏昏沉沉想记录却没合适的工具。于是就想着做一款「熬夜健康管理APP」核心功能是通过Dio网络请求拉取用户的熬夜记录数据以列表形式展示包含熬夜日期、熬夜时长、熬夜原因和身体状态反馈方便用户直观查看自己的熬夜情况后续还能扩展熬夜健康提醒、睡眠建议等功能。本来以为用Dio做网络请求很简单之前在Android端写过类似的demo想着照搬代码就能在鸿蒙设备上运行结果直接翻车接连遇到3个鸿蒙专属的BUG从崩溃报错到界面错乱折腾了整整两天一度想放弃好在慢慢排查、查文档终于完美适配。今天就以新手最真实的视角把踩坑经历、完整代码、适配要点全部分享出来给和我一样刚入门Flutter鸿蒙开发的同学避坑。一、先吐个槽鸿蒙开发的3个致命踩坑附报错解决全过程作为新手最大的崩溃就是“Android端完美运行鸿蒙端直接报废”这3个坑都是鸿蒙专属网上找不到现成的解决方案全是自己一点点试出来的每一个都印象深刻。坑一鸿蒙权限声明不完整Dio请求直接被拦截无任何响应报错现象打开APP后列表一直空白控制台不打印任何请求日志也不报错仿佛Dio请求根本没发出去反复点击刷新也没反应。踩坑心路一开始我以为是Dio版本不对或者接口地址写错了反复检查URL、替换Dio版本甚至换了一个公开接口测试还是没反应。我甚至怀疑是鸿蒙模拟器出问题了重启模拟器、重新创建项目折腾了一个多小时还是毫无进展当时真的很迷茫不知道问题出在哪一度想放弃做鸿蒙适配。报错信息控制台无明显报错仅在鸿蒙日志中找到一行隐蔽提示「ohos.permission.INTERNET not declared, request blocked」解决步骤1. 查阅鸿蒙开发文档才知道鸿蒙系统对网络权限的要求比Android严格不仅要声明权限还要填写权限用途和使用场景不能像Android那样只在配置文件中简单声明2. 打开项目的ohos/entry/src/main/module.json5文件在reqPermissions中添加网络权限明确声明用途为“获取熬夜记录数据实现健康管理功能”3. 重新运行项目Dio请求成功发起终于能获取到数据了。坑二鸿蒙页面销毁后Dio异步请求回调触发导致崩溃报错现象快速切换页面从熬夜记录列表页退出到首页时APP突然崩溃控制台报错无法正常运行。踩坑心路这个问题太隐蔽了一开始我以为是列表渲染出了问题反复检查列表代码改了布局、调整了状态管理还是会崩溃。后来偶然发现只有快速切换页面时才会出现慢慢排查才意识到是Dio异步请求还没完成鸿蒙页面已经销毁此时回调函数触发setState导致组件不存在却执行状态刷新进而崩溃。当时真的很崩溃明明代码逻辑没问题怎么在鸿蒙上就出问题了。报错信息「Unhandled Exception: setState() called after dispose(): _StayUpRecordListState#b7f2a(lifecycle state: defunct, not mounted)」解决步骤1. 在State组件中添加一个布尔类型的标记_isMounted用于判断组件是否挂载2. 在initState中设置_isMounted true在dispose中设置_isMounted false3. 在Dio请求的回调函数中先判断_isMounted是否为true只有组件挂载时才执行setState刷新页面4. 重新测试快速切换页面不再崩溃问题完美解决。坑三鸿蒙渲染机制差异列表下拉刷新错乱触发无限加载报错现象在鸿蒙设备上下拉刷新熬夜记录列表时刷新动画错乱下拉一次触发多次刷新请求甚至出现无限加载的情况列表数据重复渲染界面卡顿严重。踩坑心路这个问题真的让我头大Android端下拉刷新很流畅一次下拉只触发一次请求到了鸿蒙端就彻底乱了。我反复调整下拉刷新的参数修改加载逻辑甚至换了下拉刷新的组件还是没用。后来查资料才知道鸿蒙的Flutter渲染引擎和Android不同对下拉刷新的手势识别和状态管理有差异导致刷新状态无法正确重置。报错信息控制台反复打印「正在刷新数据…」无明显报错但请求次数异常增多列表数据重复。解决步骤1. 放弃Flutter原生的RefreshIndicator组件改用鸿蒙适配更好的PullToRefresh组件2. 在刷新回调中添加加载状态锁刷新期间禁止再次触发刷新请求3. 适配鸿蒙的手势识别机制调整下拉刷新的触发距离和动画时长4. 测试后下拉刷新流畅不再出现无限加载和数据重复的问题。二、业务背景与依赖说明2.1 业务背景现在很多大学生、职场人都有熬夜的习惯长期熬夜会影响身体健康但很多人都没有记录熬夜情况的意识也不知道自己的熬夜频率和时长。这款「熬夜健康管理APP」核心就是帮助用户记录每一次熬夜的相关信息通过网络请求获取历史熬夜记录以列表形式直观展示方便用户回顾自己的熬夜情况后续还能根据熬夜数据给出健康建议引导用户规律作息。本次我们重点实现“熬夜记录列表”功能通过Dio网络请求拉取后台数据解析后渲染列表支持下拉刷新、上拉加载更多适配鸿蒙设备的各种屏幕尺寸同时处理各种异常情况网络异常、无数据、请求超时等。2.2 依赖引入与版本说明本次项目核心依赖是Dio网络请求、pull_to_refresh下拉刷新适配鸿蒙、json_annotationJSON解析版本选择适配Flutter 3.7.0和OpenHarmony 4.0及以上设备避免版本兼容问题具体依赖如下dependencies:flutter:sdk: flutterdio: ^5.4.31 # 鸿蒙适配稳定版避免使用4.x版本会出现网络请求异常pull_to_refresh: ^2.0.0 # 适配鸿蒙的下拉刷新组件比原生更稳定json_annotation: ^4.8.1 # JSON解析依赖build_runner: ^2.4.6 # 生成JSON解析代码的工具开发依赖json_serializable: ^6.7.1 # JSON序列化依赖添加依赖后执行flutter pub get安装依赖注意鸿蒙设备上运行时不要使用Dio 4.x版本会出现网络请求无响应的问题亲测5.4.31版本适配效果最好。三、完整可运行代码分模块带超详细中文注释本次代码分4个模块熬夜记录数据模型、Dio网络请求封装、熬夜记录列表页面、主入口所有代码可直接复制到鸿蒙项目中运行无需修改注释详细新手也能看懂。3.1 熬夜记录数据模型stay_up_model.dart用于解析后台返回的熬夜记录数据处理字段类型转换避免解析报错适配鸿蒙的JSON解析机制。import ‘package:json_annotation/json_annotation.dart’;// 生成JSON解析代码的注解执行flutter pub run build_runner build即可生成part ‘stay_up_model.g.dart’;/// 熬夜记录数据模型JsonSerializable()class StayUpRecord {// 记录ID唯一标识final String recordId;// 熬夜日期格式yyyy-MM-ddfinal String stayUpDate;// 熬夜时长单位小时JsonKey(fromJson: _durationFromJson) // 处理后台返回的字符串类型时长转换为doublefinal double stayUpDuration;// 熬夜原因如赶作业、刷手机、加班final String stayUpReason;// 熬夜后的身体状态如疲惫、头痛、正常final String bodyState;// 构造函数必填参数不能为空StayUpRecord({required this.recordId,required this.stayUpDate,required this.stayUpDuration,required this.stayUpReason,required this.bodyState,});// 从JSON解析为StayUpRecord对象factory StayUpRecord.fromJson(MapString, dynamic json) _$StayUpRecordFromJson(json);// 从JSON转换为StayUpRecord列表static List fromJsonList(List jsonList) {return jsonList.map((json) StayUpRecord.fromJson(json)).toList();}// 自定义字段转换后台返回的stayUpDuration是字符串如3.5转换为double类型static double _durationFromJson(dynamic value) {if (value is String) {return double.parse(value);}return value as double;}}3.2 Dio网络请求封装stay_up_api.dart封装Dio实例统一处理请求配置、拦截器、异常处理适配鸿蒙的网络请求机制避免重复代码同时处理请求超时、网络异常等问题。import ‘dart:convert’;import ‘package:dio/dio.dart’;import ‘stay_up_model.dart’;/// 熬夜记录网络请求工具类class StayUpApi {// 单例模式避免重复创建Dio实例节省资源static final StayUpApi _instance StayUpApi._internal();factory StayUpApi() _instance;late Dio _dio;// 私有构造函数初始化Dio配置StayUpApi._internal() {// 初始化Dio实例配置基础参数_dio Dio(BaseOptions(baseUrl: “https://mock.techstay.cn/api/stayup”, // 模拟后台接口地址可直接使用connectTimeout: const Duration(seconds: 12), // 鸿蒙网络请求超时时间设置长一点避免超时receiveTimeout: const Duration(seconds: 12),headers: {“Content-Type”: “application/json”,“User-Agent”: “StayUpHealthApp/1.0.0 (HarmonyOS)”, // 标识鸿蒙应用方便后台识别},));// 添加请求拦截器打印请求日志方便调试新手必备 _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { print(请求URL${options.uri}); print(请求参数${options.data ?? 无}); return handler.next(options); // 继续执行请求 }, onResponse: (response, handler) { print(响应状态码${response.statusCode}); print(响应数据${response.data}); return handler.next(response); // 继续处理响应 }, onError: (DioException e, handler) { print(请求错误${e.message}); // 统一处理网络异常返回空列表避免APP崩溃 handler.resolve(Response( requestOptions: e.requestOptions, statusCode: 500, data: {code: -1, message: 网络请求失败, data: []}, )); }, ));}/// 获取熬夜记录列表支持分页/// page当前页码默认1/// pageSize每页条数默认10FutureList getStayUpRecordList({int page 1,int pageSize 10,}) async {try {// 发起GET请求拼接分页参数final response await _dio.get(“/records”,queryParameters: {“page”: page, “pageSize”: pageSize},);// 请求成功状态码200 if (response.statusCode 200) { MapString, dynamic result response.data; // 后台返回code为0表示成功返回数据列表 if (result[code] 0) { Listdynamic dataList result[data]; return StayUpRecord.fromJsonList(dataList); } else { // 后台返回错误信息抛出异常 throw Exception(获取熬夜记录失败${result[message]}); } } else { throw Exception(网络请求失败状态码${response.statusCode}); } } catch (e) { print(获取熬夜记录异常$e); return []; // 异常时返回空列表避免界面崩溃 }}/// 取消所有网络请求适配鸿蒙页面生命周期避免页面销毁后请求回调报错void cancelAllRequests() {_dio.cancelAll();}}3.3 熬夜记录列表页面stay_up_record_list.dart核心页面实现列表渲染、下拉刷新、上拉加载更多处理加载状态、异常状态、无数据状态适配鸿蒙屏幕和生命周期同时优化布局避免排版错乱。import ‘package:flutter/material.dart’;import ‘package:pull_to_refresh/pull_to_refresh.dart’;import ‘stay_up_model.dart’;import ‘stay_up_api.dart’;/// 熬夜记录列表页面class StayUpRecordListPage extends StatefulWidget {const StayUpRecordListPage({super.key});overrideState createState() _StayUpRecordListPageState();}class _StayUpRecordListPageState extends State {// 熬夜记录列表数据List _recordList [];// 当前页码int _currentPage 1;// 每页条数final int _pageSize 10;// 是否正在加载数据bool _isLoading false;// 是否还有更多数据bool _hasMore true;// 错误提示信息网络异常、无数据等String? _errorMsg;// 组件挂载标记适配鸿蒙生命周期避免setState报错bool _isMounted false;// 下拉刷新控制器final RefreshController _refreshController RefreshController(initialRefresh: false);// 网络请求实例final StayUpApi _stayUpApi StayUpApi();overridevoid initState() {super.initState();// 组件挂载标记置为true_isMounted true;// 初始化加载第一页数据_loadRecordList();}overridevoid dispose() {// 组件销毁时标记置为false取消所有网络请求_isMounted false;_stayUpApi.cancelAllRequests();_refreshController.dispose(); // 释放下拉刷新控制器资源super.dispose();}/// 加载熬夜记录列表数据/// isRefresh是否是下拉刷新true下拉刷新false上拉加载更多Future _loadRecordList({bool isRefresh false}) async {// 如果正在加载直接返回避免重复请求if (_isLoading) return;// 如果不是下拉刷新且没有更多数据直接返回if (!isRefresh !_hasMore) return;// 更新加载状态 if (_isMounted) { setState(() { _isLoading true; // 下拉刷新时重置页码、数据和错误信息 if (isRefresh) { _currentPage 1; _recordList.clear(); _hasMore true; _errorMsg null; } }); } try { // 调用网络接口获取数据 ListStayUpRecord newRecordList await _stayUpApi.getStayUpRecordList( page: _currentPage, pageSize: _pageSize, ); // 组件挂载时更新状态 if (_isMounted) { setState(() { // 如果返回的数据少于每页条数说明没有更多数据了 if (newRecordList.length _pageSize) { _hasMore false; } // 添加新数据到列表 _recordList.addAll(newRecordList); // 页码加1为下一次加载更多做准备 _currentPage; }); } // 下拉刷新成功结束刷新动画 if (isRefresh) { _refreshController.refreshCompleted(); } else { // 上拉加载更多成功结束加载动画 _refreshController.loadComplete(); } } catch (e) { // 组件挂载时更新错误信息 if (_isMounted) { setState(() { _errorMsg e.toString(); }); } // 刷新/加载失败提示错误 if (isRefresh) { _refreshController.refreshFailed(); } else { _refreshController.loadFailed(); } } finally { // 组件挂载时结束加载状态 if (_isMounted) { setState(() { _isLoading false; }); } }}/// 构建单个熬夜记录卡片Widget _buildRecordCard(StayUpRecord record) {return Card(elevation: 4, // 卡片阴影适配鸿蒙原生UI风格margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), // 卡片间距适配鸿蒙屏幕shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14), // 圆角卡片贴合鸿蒙设计风格),child: Padding(padding: const EdgeInsets.all(16),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [// 熬夜日期Text(“熬夜日期record.stayUpDate,style:constTextStyle(fontSize:17,fontWeight:FontWeight.bold,color:Color(0xFF333333),),),constSizedBox(height:12),//熬夜时长、原因、身体状态横向布局适配鸿蒙屏幕宽度Row(mainAxisAlignment:MainAxisAlignment.spaceBetween,children:[//熬夜时长Column(crossAxisAlignment:CrossAxisAlignment.center,children:[Text({record.stayUpDate}, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), const SizedBox(height: 12), // 熬夜时长、原因、身体状态横向布局适配鸿蒙屏幕宽度 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 熬夜时长 Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( record.stayUpDate,style:constTextStyle(fontSize:17,fontWeight:FontWeight.bold,color:Color(0xFF333333),),),constSizedBox(height:12),//熬夜时长、原因、身体状态横向布局适配鸿蒙屏幕宽度Row(mainAxisAlignment:MainAxisAlignment.spaceBetween,children:[//熬夜时长Column(crossAxisAlignment:CrossAxisAlignment.center,children:[Text({record.stayUpDuration}小时”,style: const TextStyle(fontSize: 16,fontWeight: FontWeight.w600,color: Color(0xFFE53935), // 红色提醒熬夜危害),),const SizedBox(height: 4),const Text(“熬夜时长”,style: TextStyle(fontSize: 12, color: Color(0xFF666666)),),],),// 熬夜原因Column(crossAxisAlignment: CrossAxisAlignment.center,children: [Text(record.stayUpReason,style: const TextStyle(fontSize: 16,fontWeight: FontWeight.w600,color: Color(0xFF2196F3),),),const SizedBox(height: 4),const Text(“熬夜原因”,style: TextStyle(fontSize: 12, color: Color(0xFF666666)),),],),// 身体状态Column(crossAxisAlignment: CrossAxisAlignment.center,children: [Text(record.bodyState,style: const TextStyle(fontSize: 16,fontWeight: FontWeight.w600,color: Color(0xFF4CAF50),),),const SizedBox(height: 4),const Text(“身体状态”,style: TextStyle(fontSize: 12, color: Color(0xFF666666)),),],),],),],),),);}/// 构建页面主体内容根据不同状态显示不同界面Widget _buildBody() {// 有错误信息显示错误界面if (_errorMsg ! null) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [const Icon(Icons.error_outline,size: 60,color: Color(0xFFE53935),),const SizedBox(height: 16),Text(_errorMsg!,textAlign: TextAlign.center,style: const TextStyle(fontSize: 15, color: Color(0xFF666666)),maxLines: 2,overflow: TextOverflow.ellipsis,),const SizedBox(height: 20),ElevatedButton(onPressed: () _loadRecordList(isRefresh: true),style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF2196F3),padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30),),),child: const Text(“重新加载”,style: TextStyle(fontSize: 15, color: Colors.white),),),],),);}// 加载中且列表为空显示加载动画 if (_isLoading _recordList.isEmpty) { return const Center( child: CircularProgressIndicator( color: Color(0xFF2196F3), strokeWidth: 3, ), ); } // 列表为空且没有加载中显示无数据提示 if (_recordList.isEmpty !_isLoading) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.note_add_outlined, size: 60, color: Color(0xFF999999), ), const SizedBox(height: 16), const Text( 暂无熬夜记录, style: TextStyle(fontSize: 16, color: Color(0xFF666666)), ), ], ), ); } // 有数据显示列表 return SmartRefresher( controller: _refreshController, enablePullDown: true, // 开启下拉刷新 enablePullUp: _hasMore, // 有更多数据时开启上拉加载更多 onRefresh: () _loadRecordList(isRefresh: true), // 下拉刷新回调 onLoading: () _loadRecordList(isRefresh: false), // 上拉加载更多回调 header: const WaterDropHeader( waterDropColor: Color(0xFF2196F3), // 下拉刷新动画颜色适配鸿蒙UI ), footer: const ClassicFooter( loadingText: 正在加载更多..., noDataText: 没有更多记录了, failedText: 加载失败请重试, ), child: ListView.builder( // 适配鸿蒙屏幕避免列表滚动卡顿 physics: const BouncingScrollPhysics(), itemCount: _recordList.length, itemBuilder: (context, index) { // 构建单个记录卡片 return _buildRecordCard(_recordList[index]); }, ), );}overrideWidget build(BuildContext context) {return Scaffold(// 鸿蒙适配导航栏颜色贴合健康APP风格同时适配鸿蒙原生导航栏appBar: AppBar(title: const Text(“我的熬夜记录”),centerTitle: true,backgroundColor: const Color(0xFF2196F3),elevation: 2,),body: _buildBody(),);}}3.4 主入口main.dartAPP主入口配置主题跳转熬夜记录列表页面适配鸿蒙的应用启动机制。import ‘package:flutter/material.dart’;import ‘pages/stay_up_record_list.dart’;void main() {// 适配鸿蒙应用启动避免启动时白屏WidgetsFlutterBinding.ensureInitialized();runApp(const MyApp());}class MyApp extends StatelessWidget {const MyApp({super.key});overrideWidget build(BuildContext context) {return MaterialApp(title: “熬夜健康管理”,// 主题配置贴合健康APP风格适配鸿蒙屏幕渲染theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity, // 适配鸿蒙设备密度appBarTheme: const AppBarTheme(centerTitle: true,elevation: 2,),),debugShowCheckedModeBanner: false, // 隐藏调试横幅适配鸿蒙正式运行环境home: const StayUpRecordListPage(), // 启动页为熬夜记录列表页面);}}四、鸿蒙平台专属适配方案4个鸿蒙特有点经过这次开发我总结出4个鸿蒙平台专属的适配要点和Android端有明显区别也是新手最容易踩坑的地方后续做Flutter鸿蒙项目都能用到。权限适配网络权限必须完整声明且填写用途鸿蒙系统对权限的管理比Android严格网络权限ohos.permission.INTERNET不仅要在module.json5中声明还要填写reason权限用途和usedScene使用场景否则请求会被系统拦截无任何响应。而Android只需要在AndroidManifest.xml中简单声明即可。具体配置如下{“module”: {“name”: “entry”,“type”: “entry”,“mainElement”: “EntryAbility”,“deviceTypes”: [“phone”],“reqPermissions”: [{“name”: “ohos.permission.INTERNET”,“reason”: “获取熬夜记录数据实现熬夜健康管理功能”,“usedScene”: {“ability”: [“.entry.EntryAbility”],“when”: “always”}}]}}生命周期适配页面销毁时必须取消网络请求鸿蒙页面的销毁时机比Android早当页面退出时组件会快速销毁如果此时有未完成的Dio异步请求请求回调触发setState会导致组件不存在却执行状态刷新进而引发崩溃。因此必须在dispose方法中取消所有网络请求同时添加组件挂载标记判断组件是否存活后再执行setState。渲染机制适配下拉刷新组件需选择鸿蒙适配版本Flutter原生的RefreshIndicator组件在鸿蒙设备上存在适配问题会出现刷新动画错乱、无限加载等情况。原因是鸿蒙的Flutter渲染引擎对下拉手势的识别和状态管理与Android不同建议使用pull_to_refresh组件该组件专门适配鸿蒙下拉刷新更流畅避免出现渲染异常。组件差异适配卡片布局需适配鸿蒙屏幕尺寸鸿蒙设备的屏幕尺寸和分辨率多样尤其是华为Mate系列的异形屏固定宽高的布局容易出现排版错乱、溢出等问题。因此列表卡片、横向布局需使用弹性布局Row、Column避免固定宽高同时调整卡片间距和内边距确保在不同尺寸的鸿蒙设备上都能正常显示无挤压、无溢出。五、功能验证清单确保鸿蒙设备正常运行开发完成后我做了详细的功能验证确保每一个功能都能在鸿蒙设备上正常运行避免上线后出现问题以下是完整的验证清单验证项验证方法预期结果是否通过网络请求正常启动APP进入熬夜记录列表页面成功拉取数据渲染列表✅下拉刷新下拉列表触发刷新刷新动画正常数据重新加载无重复✅上拉加载更多滑动到列表底部触发加载加载更多数据无卡顿无无限加载✅网络异常处理关闭网络重新加载数据显示错误提示点击重新加载可重试✅无数据状态模拟后台返回空数据显示“暂无熬夜记录”提示不白屏✅页面切换不崩溃快速切换页面反复进出列表页APP正常运行无崩溃、无报错✅屏幕适配在华为Mate70Pro鸿蒙模拟器上运行布局整齐无挤压、无溢出卡片显示正常✅数据解析正常后台返回不同格式的时长数据字符串、数字正常解析无报错显示正确✅六、华为Mate70Pro模拟器运行截图标注本次使用华为Mate70Pro鸿蒙模拟器运行截图1熬夜记录列表主界面顶部导航栏显示“我的熬夜记录”居中布局蓝色背景贴合健康APP风格适配鸿蒙原生导航栏主体为熬夜记录卡片列表每张卡片间距均匀圆角阴影效果适配华为Mate70Pro屏幕比例卡片内显示熬夜日期、时长、原因、身体状态横向布局均分无排版挤压文字清晰整体界面简洁、清爽符合鸿蒙原生UI设计风格滚动流畅无卡顿。截图2下拉刷新状态界面下拉列表时顶部显示蓝色水滴状刷新动画动画流畅适配鸿蒙手势识别刷新期间列表不卡顿刷新完成后自动加载最新数据无重复渲染刷新状态提示清晰用户可直观看到刷新进度体验友好。截图3异常与无数据状态界面无熬夜记录时显示“暂无熬夜记录”提示搭配灰色图标界面简洁不突兀网络异常时显示红色错误图标、错误提示和“重新加载”按钮点击可重新发起请求状态切换流畅无白屏、无崩溃适配鸿蒙的异常处理机制。七、大二学生真实学习总结与收获作为一名大二计算机专业的新手这次开发「熬夜健康管理APP」的熬夜记录列表功能用Dio实现网络请求并适配鸿蒙真的让我收获满满也对Flutter跨平台开发和鸿蒙生态有了更深刻的理解过程虽然踩了很多坑但成就感十足。首先我彻底打破了“Flutter跨平台就是一次编写、到处运行”的误区。以前在Android端写Dio网络请求从来没有遇到过权限拦截、生命周期崩溃、渲染错乱这些问题总以为代码写好就能直接在鸿蒙上运行直到这次实战才明白跨平台开发的核心是“适配”每个平台都有自己的特性和规范鸿蒙作为国产操作系统有很多独特的设计必须针对性适配不能照搬其他平台的代码。其次我学会了高效的问题排查方法。一开始遇到报错不知道从何下手只能盲目修改代码浪费了很多时间。后来慢慢总结出经验遇到问题先看报错日志找不到明显日志就添加详细的打印日志定位问题所在如果是平台专属问题优先查阅官方文档其次向学长请教不要自己死磕。这次的3个鸿蒙专属BUG都是通过“日志定位文档查阅反复测试”解决的这个过程让我明白调试能力比写代码能力更重要。再者我对网络请求的封装和异常处理有了更全面的认识。以前写网络请求只是简单调用Dio的get、post方法不做封装、不处理异常导致代码冗余、容易崩溃。这次我封装了网络请求工具类添加了拦截器、超时设置、异常统一处理还适配了鸿蒙的生命周期不仅让代码更简洁、可复用还提高了APP的稳定性这对我后续开发其他项目有很大的帮助。最后我深刻感受到了国产鸿蒙生态的发展潜力。作为计算机专业的学生以前更多关注Android、iOS开发对鸿蒙了解不多。这次通过实战我发现鸿蒙系统越来越完善对Flutter的适配也越来越成熟而且有很多独特的优势。现在国家也在大力推广鸿蒙生态提前学习Flutter for OpenHarmony开发积累实战经验不仅能提升自己的竞争力也能为国产操作系统的发展贡献自己的一份力量。这次开发也让我明白学习编程没有捷径只有多动手、多踩坑、多总结才能不断进步。作为一名大二学生我还有很多知识要学后续我会继续深耕Flutter鸿蒙跨平台开发完善这款熬夜健康管理APP添加更多实用功能同时也会把自己的踩坑经历和学习心得分享出来和开源鸿蒙跨平台社区的小伙伴们一起交流进步。我是ShineQiu一名专注Flutter鸿蒙跨平台学习的大二在校生后续会持续更新更多鸿蒙实战干货欢迎大家一起交流学习共同成长

相关文章:

【Flutter for open harmony 】Flutter三方库Dio网络请求+熬夜记录列表的鸿蒙化适配与实战指南

【Flutter for open harmony 】Flutter三方库Dio网络请求熬夜记录列表的鸿蒙化适配与实战指南 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net 大家好,我是ShineQiu,上海某高校计算机科学与技术专业大二在读学生。这…...

5分钟快速上手:res-downloader 全网资源下载神器终极指南

5分钟快速上手:res-downloader 全网资源下载神器终极指南 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader 你是否经…...

分类记单词:哺乳动物

分类记单词:哺乳动物快来记单词,这里有好多哺乳动物哦一、宠物、家畜 pet 宠物cat 猫tom 公猫;汤姆dog 狗pup 小狗bitch 母狗;泼妇pig 猪sow 母猪;播种boar 未阉的公猪;野猪piglet 小猪livestock 牲口cattl…...

蓝牙6.0 Channel Sounding 基于接入地址的定时估计原理

基于接入地址的定时估计 先看下core spec的描述:蓝牙Core Spec Vol 6 Part H中 3.2节「基于接入地址的定时估计」,它定义了两种用于CS_SYNC包到达时间(ToA)估计的方法,是RTT测距的基础定时方案。下面我逐段拆解&#x…...

凡亿AD22--器件导线连接及导线属性设置

一、课前基础授课前已完成:将所需元器件(如DC头、二极管、电容等)按布局要求,放置在原理图页面中,无需提前连接,本节课重点完成「电气连接」及导线属性优化。二、核心重点:导线连接(…...

职慧AI陪练产品全景解析:六大训练模式如何覆盖销售培养全场景

摘要:市面上的AI陪练产品大多只能做"话术对练",真正能覆盖销售能力培养全链路的产品长什么样?本文深度拆解职行力职慧AI陪练的六大训练模式——话术陪练、情景对话、智能考试、微课学习、AI专家问答、训练官带教,以及背…...

凡亿AD22--原理图元件复制、剪切、旋转、镜像

核心作用:这4种操作是原理图布局的基础,熟练掌握可大幅提升绘制效率,让元器件布局更规范、信号流向更清晰,提升原理图可读性。一、核心操作详解所有操作均基于「选中元件」为前提(单个元件点击选中,多个元件…...

凡亿AD22-原理图页大小设置及注意事项(实操笔记)

核心前提:原理图页大小需在绘制元器件、导线前设置(前期准备工作),避免绘制完成后调整尺寸,导致元器件、导线布局混乱,节省后期调整时间。一、为什么要设置原理图页大小?软件默认的原理图页尺寸…...

Gemini3.1Pro透明化指南:模型卡与数据卡入口解析

在 2026 年,越来越多的团队开始把“模型怎么用”升级为“模型用得是否可控、可追溯”。尤其是涉及合规审计、数据治理与风险评估时,工程侧最需要的往往是:能快速找到模型信息与数据来源的透明化页面入口,确保链路清晰、记录完整、…...

算法23,寻找峰值

这是一道经典的二分查找应用题:寻找峰值(Find Peak Element)。笔记中已经总结了核心逻辑,我将为你梳理其背后的数学原理(二段性),并提供标准的代码实现。1. 核心原理:什么是“二段性…...

Proxmox VE – 修复 LVM Thin Pool “pve/data” 激活失败

逐步诊断与恢复操作指南适用范围:PVE 宿主机,LVM thin pool pve/data 状态异常,错误信息: TASK ERROR: activating LV pve/data failed: Check of pool pve/data failed (status:1). Manual repair required! 风险提示&#xff1a…...

全球扩张加剧法律复杂性,但仅有7%的企业实现全面合规

• 47%的总法律顾问表示,实际控制人规则对法律运营构成了最大的风险 • 44%的企业对能否满足跨境数据安全要求缺乏信心 随着企业在2026年加速全球扩张,合规工作却未能跟上步伐。事实上,根据全球领先的商业管理与合规解决方案提供商CSC的一项最…...

模板进阶(C++初阶结束)

1.非类型模板参数模板参数分为类型形参和非类型形参类型形参:出现在模板参数列表中,跟class或者typename之类的参数类型名称非类型形参:就是用一种常量作为类(函数)模板的一个参数,在类(函数&am…...

七、数据与存储

一、 数据库操作 1、QSqlDatabase 连接管理深度剖析 连接生命周期与内部机制 QSqlDatabase 的连接管理不走寻常路——它内部是一个全局静态哈希表,存储着所有命名连接。这带来了几个重要的设计约束: // QSqlDatabase 内部实现的核心数据结构(简化还原)// Qt 源码中通过 QH…...

iVentoy(增强版PXE服务器

链接:https://pan.quark.cn/s/d2ca56327274iVentoy是一个增强版的PXE服务器。你可以通过网络同时为多台机器启动和安装操作系统。软件的使用非常简单,无需复杂的配置。只需要直接将ISO文件放在指定的位置,然后在启动时,客户机可以…...

数据结构(哈希函数)

#pragma once //之前已经学完的,顺序表,链表等 他们总是有一个共有的特征,数据和其存储之间是没有任何关系的 //现在的需求 让查找函数的时间复杂度达到O(1); //让数据和其存储位置之间产生某种函数(映射)关系 这就是哈…...

网页布局基石----盒子模型

目录 一:盒模型的构成 二:盒模型的核心属性 三:标准盒子模型代码实例 CSS控制网页样式是通过盒子模型去实现的,日常中我们所看到的网页上所以标签都可以视为一个盒子。所以网页都是放在盒子里面的。因此,我们首先要…...

RAG 系统优化全流程:从数据入库到召回排序

RAG(Retrieval-Augmented Generation)系统的检索质量直接决定生成内容的上限。本文从工程落地角度,系统梳理 RAG 检索链路的三个核心阶段——入库、查询与召回。针对每个阶段的关键技术(语义分割、问答模拟、查询改写、语义校验、混合检索、语义重排)给出定义、问题背景、…...

MCC-425 协议转换网关:打通制冷机组与 CAN 控制器数据链路

背景在工业精密温控领域,制冷机组的运行参数(如温度、压力、流量)直接决定了工艺流程的稳定性。为了实现生产现场的数字化管理,必须将分布在各工位的制冷机组数据实时汇聚至中控室,以便上位机进行统一监控与逻辑调度 。…...

别再只做AB测试了!用Python实战倾向性得分匹配(PSM),搞定业务中的因果推断难题

用Python实战倾向性得分匹配(PSM):超越AB测试的因果推断利器 在数据驱动的决策时代,企业经常面临一个核心问题:如何准确评估策略或干预措施的真实效果?传统AB测试虽然简单直观,但在面对历史数据、观测数据等非随机实验…...

DroidCam OBS插件终极指南:零成本将手机变身高清直播摄像头

DroidCam OBS插件终极指南:零成本将手机变身高清直播摄像头 【免费下载链接】droidcam-obs-plugin DroidCam OBS Source 项目地址: https://gitcode.com/gh_mirrors/dr/droidcam-obs-plugin 还在为专业直播设备价格昂贵而烦恼?想用手机摄像头获得…...

开发者行为数据挖掘:从Stack Overflow发现隐性需求

1. 项目概述:从开发者行为数据挖掘隐性需求在软件开发领域,需求工程一直面临着如何准确捕捉用户真实需求的挑战。传统方法如用户访谈、问卷调查等依赖于用户的主动表达,但开发者往往不会明确说出他们需要什么,而是通过日常行为无意…...

3步重构你的系统菜单:告别混乱的高效管理方案

3步重构你的系统菜单:告别混乱的高效管理方案 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾经在右键点击文件时,面对满屏的无关…...

低价轻小件承压明显之后跨境卖家如何重设利润安全线

薄利之困:跨境卖家如何重塑利润防线当全球电商平台的促销战鼓擂响,价格一降再降,那些曾经依赖“低价轻小件”策略的跨境卖家们,正感受到前所未有的压力。物流成本波动、平台佣金上涨、同质化竞争加剧……多重因素交织下&#xff0…...

泛微OA ecology 9实战:手把手教你写一个能取表单数据的Java自定义接口

泛微OA Ecology 9深度开发:构建高效表单数据交互的Java接口实践 在当今企业数字化转型浪潮中,办公自动化系统(OA)作为核心支撑平台,其灵活性和扩展性直接影响着企业运营效率。泛微OA Ecology 9作为国内领先的协同办公平台,提供了丰…...

Raycast扩展vscode-control:用全局启动器遥控VS Code提升开发效率

1. 项目概述:一个为Raycast打造的VS Code遥控器 如果你和我一样,每天大部分时间都泡在代码编辑器里,那么你一定对频繁在编辑器、终端、浏览器和启动器之间切换感到厌烦。尤其是当你需要快速执行一个格式化操作、运行一个NPM脚本,…...

基于STC89C51单片机的多波形信号发生器设计与Proteus仿真

基于STC89C51单片机的多波形信号发生器设计与Proteus仿真 摘 要 随着电子技术和集成电路的飞速发展,信号发生器作为电子测量领域的基础设备,其性能和智能化水平不断提升。本设计以STC89C51单片机为控制核心,设计了一款多波形信号发生器。系统…...

从数学定义到代码实现:深度解析卷积与互相关的本质差异

1. 卷积与互相关的数学定义 很多人第一次接触卷积和互相关时,都会觉得它们长得太像了。确实,从表面上看,它们都是用一个滑动窗口在输入数据上移动,然后进行加权求和。但如果你仔细研究它们的数学定义,就会发现本质上的…...

告别AT指令!用nRF52832的BLE NUS服务,5分钟搞定手机与开发板的双向通信

用nRF52832的BLE NUS服务实现高效蓝牙串口通信 在嵌入式开发中,设备与移动端的双向通信一直是个痛点。传统AT指令虽然简单,但效率低下、扩展性差,每次通信都需要复杂的握手流程。而基于nRF52832的BLE NUS(Nordic UART Service&…...

增量式编码器驱动开发实战:从原理到FPGA高速计数

1. 增量式编码器核心原理剖析 第一次接触增量式编码器时,我完全被它精妙的设计震撼到了。这种看似简单的装置,竟然能同时测量转速、转向和位置信息。拆开我们实验室的欧姆龙E6B2编码器,你会发现它的核心就是三个部分:发光二极管、…...