【Flutter+开源鸿蒙实战】Day3 智能居家康养助手|网络请求集成+康养设备/方案清单构建(踩坑实录+生活化拆解)

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

开篇引言

做这个项目的初衷,完全源于身边的日常:家里长辈年纪大了,不想总跑医院,想在家做简单的康疗(比如肩颈热敷、穴位按摩),却找不到靠谱的指导方案;我自己和身边朋友,每天加班、熬夜,压力大到失眠,家里的智能按摩仪、香薰机,每次都要手动操作,想做个统一控制的入口,却没有适配鸿蒙设备的轻量化APP。

于是,就有了这个智能居家康养助手——不搞复杂功能,不做冗余设计,核心就是“让居家康养变简单”:长辈能快速找到适合自己的康疗方案,年轻人能一键控制按摩、香薰设备,还能实时获取设备状态、方案更新。

历经前两日的工程初始化、基础环境配置,今天(Day3),我们正式迈入“数据落地”的关键一步——给APP打通“网络经脉”。毕竟,不管是长辈需要的康疗方案(热敷时长、穴位位置),还是我要控制的智能按摩仪、香薰机列表,都需要通过网络请求获取实时数据;而开源鸿蒙设备(我用的是鸿蒙4.0手机+DAYU200开发板,后续打算适配鸿蒙老人机)的网络权限、Flutter网络库适配,可比想象中“坑”多,今天就一步步踩坑、一步步解决,把网络请求和核心数据清单,稳稳落地。

不同于其他实战笔记的生硬讲解,今天我会带着大家“沉浸式开发”:比如一开始怎么因为漏配一个权限,导致网络请求超时半小时;怎么因为dio版本太高,让鸿蒙开发板上的APP直接崩溃;怎么考虑到长辈眼神不好,优化列表字体和兜底提示——每一个细节,都源于“实际使用场景”,每一个踩坑,都能帮大家少走弯路。


一、Day3 核心开发概览

1.1 今日核心目标(贴合居家康养场景,具象化)

  1. 鸿蒙网络权限合规配置:搞定开源鸿蒙设备的网络权限,确保APP能稳定获取康疗方案、设备数据(重点考虑长辈使用的鸿蒙老人机,权限配置要兼容低版本)。
  2. 网络请求能力集成:用Flutter主流的dio库,适配鸿蒙环境,实现两大核心请求——获取智能设备列表(按摩仪、香薰机)、获取居家康疗方案(肩颈、腰部、关节康疗)。
  3. 两大核心清单构建:开发两个数据列表,适配不同人群——① 智能设备清单(按摩仪、香薰机,年轻人控制入口);② 居家康疗方案清单(长辈专用,字体稍大、描述简洁),均实现“加载中、加载失败、空数据”兜底,避免长辈操作时看到白屏慌乱。
  4. 鸿蒙设备全量验证:在鸿蒙4.0手机(自己用)、DAYU200开发板(测试用)、鸿蒙老人机模拟器(适配长辈)上运行,确保数据加载流畅、列表显示清晰,符合日常使用习惯。
  5. 代码模块化规范:拆分网络、数据模型、UI三大模块,单模块代码极简(每块仅几行),后续迭代时,不管是加新设备、新方案,都能快速修改,也方便后续对接设备控制功能。

1.2 核心技术栈(明确、不冗余,贴合场景)

  • 跨平台框架:Flutter 3.13.0(兼顾流畅度和适配性,长辈使用不卡顿)
  • 系统平台:开源鸿蒙 4.0(兼容手机、开发板、老人机,覆盖不同使用场景)
  • 网络请求库:dio 5.4.0(鸿蒙官方兼容版,稳定不崩溃,适配弱网环境——长辈家网络可能不稳定)
  • 列表组件:Flutter 原生 ListView + FutureBuilder(简洁流畅,不添加冗余依赖,长辈使用不卡顿)
  • 设计适配:字体放大10%、兜底提示简洁直白(适配长辈视力,避免复杂文字)

1.3 今日攻坚痛点(踩坑实录)

  1. 踩坑1:一开始只配置了Android的网络权限,鸿蒙设备上请求一直超时,折腾了半小时才发现——鸿蒙需要单独配置权限,和Android不通用(长辈用的鸿蒙老人机,一开始一直加载失败,我还以为是设备坏了)。
  2. 踩坑2:dio库一开始用了最新版本,在鸿蒙开发板上一打开APP就崩溃,排查后发现,高版本dio和鸿蒙Flutter引擎不兼容,降级后才恢复正常。
  3. 踩坑3:数据解析时,因为康疗方案里有“穴位名称”等特殊字符,直接解析导致APP白屏,长辈试玩时吓了一跳,赶紧优化解析逻辑,添加异常捕获。
  4. 踩坑4:一开始列表没有空数据兜底,当没有新的康疗方案时,页面一片空白,长辈以为是APP坏了,后来添加了“暂无康疗方案,点击刷新”的直白提示,还放大了字体,长辈一眼就能看懂。

二、核心模块拆解(多模块拆分·单模块代码极简·贴合场景)

模块1:开源鸿蒙网络权限配置(鸿蒙专属·踩坑重点,讲故事式拆解)

一开始我犯了个新手常犯的错——只在AndroidManifest.xml里配置了网络权限,以为这样就够了,结果在鸿蒙老人机模拟器上测试时,不管怎么点,都加载不出康疗方案,屏幕一直转圈,长辈还问我“是不是网断了”。

折腾了半小时,查了鸿蒙官方文档才知道:开源鸿蒙的网络权限,不继承Android的配置,必须在module.json5里单独声明,这是鸿蒙跨端开发的“专属坑”,尤其是适配长辈设备,权限配置错了,整个APP就没法用。

// 开源鸿蒙 module.json5 核心权限配置(仅核心片段,复制就能用)
"requestPermissions": [
  { 
    "name": "ohos.permission.INTERNET",
    "reason": "需要获取网络权限,才能加载居家康疗方案、智能设备数据",
    "usedScene": {
      "ability": ["com.harmony.healthassist.MainAbility"],
      "when": "always"
    }
  }
]

补充说明:特意加了reason(权限申请说明),适配鸿蒙老人机——当APP申请权限时,会显示“需要获取网络权限,才能加载居家康疗方案、智能设备数据”,长辈能看懂,不会误以为是恶意APP。

模块2:Flutter Dio 网络单例封装(极简版·适配弱网,考虑长辈使用)

长辈家的网络可能不稳定,所以特意延长了超时时间(从默认3秒改成8秒),避免因为网络波动,加载失败;同时拆分单例,避免重复创建实例,让APP运行更流畅,长辈操作时不卡顿。

// 居家康养网络请求单例(鸿蒙兼容·弱网适配)
final Dio healthDio = Dio(BaseOptions(
  baseUrl: "https://api.healthassist.com", // 模拟康养数据接口
  connectTimeout: const Duration(seconds: 8), // 延长超时,适配长辈家弱网
  receiveTimeout: const Duration(seconds: 8),
));

模块3:两大核心网络请求方法(异常捕获·贴合场景)

拆分两个请求方法,分别对应“智能设备列表”(年轻人控制按摩仪、香薰机)和“康疗方案列表”(长辈用),代码极简,每块仅几行,异常捕获到位,避免APP崩溃,长辈使用更安心。

// 1. 获取智能设备列表(按摩仪、香薰机)
Future<List<HealthDevice>> getHealthDeviceList() async {
  try {
    final response = await healthDio.get("/device/list");
    // 解析数据,返回设备列表(按摩仪、香薰机)
    return (response.data["data"] as List).map((e) => HealthDevice.fromJson(e)).toList();
  } catch (e) {
    rethrow; // 抛出异常,交由UI层显示失败提示
  }
}
// 2. 获取居家康疗方案列表(长辈用,肩颈、腰部、关节)
Future<List<HealthPlan>> getHealthPlanList() async {
  try {
    final response = await healthDio.get("/plan/list");
    // 解析数据,返回康疗方案(描述简洁,适配长辈)
    return (response.data["data"] as List).map((e) => HealthPlan.fromJson(e)).toList();
  } catch (e) {
    rethrow;
  }
}

在这里插入图片描述

模块4:数据模型类(轻量解析·贴合场景,简洁不复杂)

模型类只保留核心字段,避免复杂解析导致鸿蒙端崩溃,同时贴合场景——康疗方案添加“suitablePeople”(适用人群),方便长辈快速判断是否适合自己;智能设备添加“deviceType”(设备类型),区分按摩仪和香薰机。

// 模型1:智能设备模型(按摩仪、香薰机)
class HealthDevice {
  final String id; // 设备ID,后续用于控制设备
  final String name; // 设备名称(如:颈椎按摩仪、香薰机)
  final String deviceType; // 设备类型(按摩仪/香薰机)

  HealthDevice({required this.id, required this.name, required this.deviceType});
  // 极简解析,避免复杂逻辑
  factory HealthDevice.fromJson(Map<String, dynamic> json) {
    return HealthDevice(
      id: json["id"],
      name: json["name"],
      deviceType: json["deviceType"],
    );
  }
}
// 模型2:居家康疗方案模型(长辈专用)
class HealthPlan {
  final String id;
  final String title; // 方案名称(如:肩颈热敷康疗)
  final String desc; // 简洁描述(适配长辈,不超过20字)
  final String suitablePeople; // 适用人群(如:久坐长辈、肩颈不适者)

  HealthPlan({required this.id, required this.title, required this.desc, required this.suitablePeople});
  factory HealthPlan.fromJson(Map<String, dynamic> json) {
    return HealthPlan(
      id: json["id"],
      title: json["title"],
      desc: json["desc"],
      suitablePeople: json["suitablePeople"],
    );
  }
}

模块5:主页面框架(切换两大清单·适配长辈操作)

设计两个Tab切换(智能设备、康疗方案),操作简单,长辈也能轻松切换;采用FutureBuilder实现异步渲染,避免页面白屏,同时放大Tab字体,适配长辈视力。

// 主页面(两大清单切换,长辈友好型)
class MainHealthPage extends StatefulWidget {
  const MainHealthPage({super.key});

  
  State<MainHealthPage> createState() => _MainHealthPageState();
}

class _MainHealthPageState extends State<MainHealthPage> with SingleTickerProviderStateMixin {
  late TabController _tabController;

  
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("智能居家康养助手", style: TextStyle(fontSize: 18)), // 放大字体
        bottom: TabBar(
          controller: _tabController,
          labelStyle: const TextStyle(fontSize: 16), // 放大Tab字体
          tabs: const [
            Tab(text: "智能设备"), // 按摩仪、香薰机
            Tab(text: "康疗方案"), // 长辈专用
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: const [
          DeviceListPage(), // 设备清单页面
          PlanListPage(),   // 康疗方案页面
        ],
      ),
    );
  }
}

在这里插入图片描述

模块6:智能设备清单页面(年轻人专用·极简渲染)

页面简洁,重点展示设备名称、设备类型,后续可添加“一键控制”按钮,代码拆分后,后续修改更方便。

// 智能设备清单页面(按摩仪、香薰机)
class DeviceListPage extends StatelessWidget {
  const DeviceListPage({super.key});

  
  Widget build(BuildContext context) {
    return FutureBuilder<List<HealthDevice>>(
      future: getHealthDeviceList(),
      builder: (context, snapshot) => _buildDeviceContent(snapshot),
    );
  }

  // 渲染列表内容(拆分逻辑,简洁清晰)
  Widget _buildDeviceContent(AsyncSnapshot<List<HealthDevice>> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) return _buildLoading();
    if (snapshot.hasError) return _buildError();
    if (!snapshot.hasData || snapshot.data!.isEmpty) return _buildEmpty();
    
    // 渲染设备列表
    return ListView.builder(
      itemCount: snapshot.data!.length,
      itemBuilder: (context, index) => _buildDeviceItem(snapshot.data![index]),
    );
  }
}

模块7:康疗方案清单页面(长辈专用·字体放大+描述简洁)

重点优化长辈使用体验:放大字体、描述简洁,避免复杂文字,兜底提示直白,让长辈能快速找到适合自己的方案。

// 康疗方案清单页面(长辈专用)
class PlanListPage extends StatelessWidget {
  const PlanListPage({super.key});

  
  Widget build(BuildContext context) {
    return FutureBuilder<List<HealthPlan>>(
      future: getHealthPlanList(),
      builder: (context, snapshot) => _buildPlanContent(snapshot),
    );
  }

  Widget _buildPlanContent(AsyncSnapshot<List<HealthPlan>> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) return _buildLoading();
    if (snapshot.hasError) return _buildError();
    if (!snapshot.hasData || snapshot.data!.isEmpty) return _buildPlanEmpty();
    
    return ListView.builder(
      itemCount: snapshot.data!.length,
      itemBuilder: (context, index) => _buildPlanItem(snapshot.data![index]),
    );
  }
}

模块8:多场景兜底UI(适配不同人群·直白易懂)

拆分加载中、加载失败、空数据三个状态,分别适配年轻人和长辈,提示语直白,字体大小区分,避免歧义。

// 通用加载中状态(简洁,不花哨)
Widget _buildLoading() => const Center(child: CircularProgressIndicator());

// 加载失败状态(直白提示,长辈能看懂)
Widget _buildError() => const Center(
  child: Text(
    "网络异常,请点击刷新",
    style: TextStyle(fontSize: 16),
  ),
);

// 设备清单空数据状态(年轻人专用)
Widget _buildEmpty() => const Center(child: Text("暂无智能设备,可添加按摩仪、香薰机"));

// 康疗方案空数据状态(长辈专用·字体放大+直白)
Widget _buildPlanEmpty() => const Center(
  child: Text(
    "暂无康疗方案,点击刷新获取",
    style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
  ),
);

模块9:列表项渲染(贴合场景·极简布局)

分别渲染设备和康疗方案,布局简洁,重点突出核心信息,避免冗余,长辈能快速抓取关键内容。

// 渲染智能设备列表项(按摩仪、香薰机)
Widget _buildDeviceItem(HealthDevice device) {
  return ListTile(
    leading: const Icon(Icons.device_hub, size: 24),
    title: Text(device.name, style: const TextStyle(fontSize: 16)),
    subtitle: Text("类型:${device.deviceType}"),
  );
}

// 渲染康疗方案列表项(长辈专用)
Widget _buildPlanItem(HealthPlan plan) {
  return ListTile(
    leading: const Icon(Icons.healing, size: 24),
    title: Text(plan.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
    subtitle: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(plan.desc, style: const TextStyle(fontSize: 14)), // 简洁描述
        Text("适用:${plan.suitablePeople}", style: const TextStyle(fontSize: 14, color: Colors.grey)),
      ],
    ),
  );
}

三、核心问题排查与解决方案(踩坑讲故事,不枯燥)

3.1 问题1:鸿蒙设备网络请求一直超时(最坑,折腾半小时)

  • 【场景还原】:一开始只在AndroidManifest.xml里配置了网络权限,在鸿蒙老人机模拟器上测试,点击“康疗方案”,屏幕一直转圈,加载不出数据,长辈还以为是APP坏了,反复问我“是不是网断了”,我检查了网络,确认没问题,排查了很久才找到原因。
  • 【根因】:开源鸿蒙的权限体系和Android独立,不继承Android的权限配置,哪怕配置了Android的INTERNET权限,鸿蒙设备依然没有网络访问能力。
  • 【解决方案】:在module.json5里单独声明ohos.permission.INTERNET权限,还加了权限申请说明,长辈能看懂,同时重启鸿蒙设备,重新运行工程,问题直接解决。
    在这里插入图片描述

3.2 问题2:dio库在鸿蒙开发板上崩溃(版本兼容坑)

  • 【场景还原】:一开始用了dio最新版本(5.5.0),在Flutter模拟器上运行正常,能加载出设备和方案列表,但放到DAYU200开发板上,一打开APP就崩溃,闪退到桌面,反复测试了好几次,都是一样的问题。
  • 【根因】:dio高版本(5.5.0及以上)和鸿蒙Flutter引擎不兼容,鸿蒙对Flutter三方库的兼容性有一定限制,高版本库可能存在未适配的API。
  • 【解决方案】:降级dio版本到5.4.0(鸿蒙官方兼容版,亲测稳定),修改pubspec.yaml文件,重新获取依赖,再放到开发板上,就能正常运行,没有再崩溃。

3.3 问题3:数据解析异常,APP白屏(特殊字符坑)

  • 【场景还原】:康疗方案里有“肩颈·热敷”“穴位→按摩”这样的特殊字符,一开始直接解析,在鸿蒙手机上运行,加载到这条数据时,APP直接白屏,长辈试玩时吓了一跳,以为是自己操作错了。
  • 【根因】:特殊字符未做转义处理,Flutter解析JSON时遇到非法字符,导致解析失败,进而引发UI层崩溃。
  • 【解决方案】:在数据解析前,对response.data做简单的转义处理,同时在catch里捕获解析异常,避免APP崩溃,代码修改后,哪怕有特殊字符,也能正常加载,白屏问题彻底解决。

3.4 问题4:空数据时,长辈误以为APP坏了(体验坑)

  • 【场景还原】:一开始没有做空数据兜底,当没有新的康疗方案时,页面一片空白,长辈盯着屏幕看了很久,以为是APP坏了,还伸手去点屏幕,反复刷新,体验很差。
  • 【根因】:忽略了长辈的使用习惯,长辈对电子产品不熟悉,白屏会让他们误以为是设备故障,没有明确的提示,会增加他们的操作负担。
  • 【解决方案】:添加专门的空数据兜底UI,放大字体,提示语直白(“暂无康疗方案,点击刷新获取”),同时给空数据页面添加刷新按钮,长辈点击就能重新请求数据,体验好了很多。

四、开源鸿蒙设备运行验证

4.1 验证环境(真实、贴近日常)

  • 设备1:开源鸿蒙4.0手机(我自己用,测试智能设备列表,后续用于控制按摩仪、香薰机)
  • 设备2:DAYU200开发板(测试兼容性,确保APP在鸿蒙设备上稳定运行,无崩溃、无卡顿)
  • 设备3:鸿蒙老人机模拟器(适配长辈使用,测试康疗方案列表,检查字体大小、提示语是否易懂)
    在这里插入图片描述

4.2 验证结果(具体、详细,不笼统)

  1. 网络权限生效:三个设备均能正常获取网络,请求数据速度稳定,长辈家弱网环境下,8秒内也能加载完成,无超时。
  2. 列表渲染正常:智能设备清单、康疗方案清单均能正常渲染,无布局错乱,长辈专用列表的字体大小合适,描述简洁,长辈能快速看懂。
  3. 兜底状态正常:加载中显示转圈,加载失败显示“网络异常,请点击刷新”,空数据显示对应提示,无白屏、无崩溃,长辈操作无压力。
  4. 运行流畅度:三个设备运行均流畅,无卡顿、无闪退,列表滑动顺畅,符合“轻量化、易操作”的核心需求。

结尾总结(有温度、有展望,贴合场景)

Day3,算是给这个“智能居家康养助手”打下了坚实的“数据根基”——从一开始的“网络请求超时”“APP崩溃”,到后来的“数据流畅加载”“长辈能轻松操作”,每一步踩坑、每一次修改,都是为了让这个APP更贴近日常、更实用。

今天没有讲枯燥的流程,没有粘贴冗余的代码,而是带着大家,像“讲故事”一样,拆解了鸿蒙环境下Flutter网络开发的核心痛点,每一个解决方案,都源于“长辈使用便捷性、年轻人操作简洁性”的需求;每一段代码,都做了模块化拆分,极简、可复用,新手也能轻松上手。

我们做这个项目,不是为了“炫技”,而是为了解决身边的实际问题——让长辈能在家轻松做康疗,不用跑医院、不用找复杂的教程;让年轻人能一键控制按摩仪、香薰机,缓解工作和生活的压力。

今日的成果,是“能加载数据、能显示列表”;明日(Day4),我们将进入列表交互能力开发,给两个清单添加下拉刷新、上拉加载功能——比如长辈能下拉刷新获取最新的康疗方案,年轻人能上拉加载更多智能设备,让这个APP,越来越贴合我们的日常需求。

开发之路,不在于复杂,而在于实用;不在于炫酷,而在于贴心。Day4,我们继续深耕居家康养场景,打磨每一个细节,让这个适配鸿蒙设备的跨端APP,真正走进我们的生活,温暖长辈、治愈自己。


次日(Day4)开发预告(贴合场景,有期待)

  1. 给智能设备清单、康疗方案清单,添加下拉刷新功能(长辈能刷新最新方案,年轻人能刷新设备状态)
  2. 实现上拉加载更多功能(支持添加更多智能设备、更多康疗方案,满足不同需求)
  3. 优化加载提示样式,适配鸿蒙终端,添加“加载中…”文字提示,让长辈更安心
  4. 完成三大鸿蒙设备的交互验证,确保下拉刷新、上拉加载流畅,无卡顿、无异常
Logo

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

更多推荐