【开源鸿蒙跨平台开发先锋训练营】Day3 Flutter集成Dio网络请求,本地美食数据清单列表功能开发

目录

【开源鸿蒙跨平台开发先锋训练营】Day3 Flutter集成Dio网络请求,本地美食数据清单列表功能开发

摘  要

1 引言

1.1 项目背景

1.2 项目目标

1.3 技术选型依据

2 项目功能与开发准备

2.1 功能核心定位

2.2 开发环境与技术栈配置

2.2.1 基础开发环境

2.2.2 核心依赖配置

3 项目总体设计

3.1 架构设计

3.2 工程目录结构

4 网络请求能力集成

4.1 鸿蒙网络权限配置

4.2 美食接口配置与网络工具复用

4.2.1 配置本地模拟美食API

4.2.2 美食接口地址配置

4.2.3 统一响应模型

4.2.4 网络请求封装

4.2.5 美食清单接口请求实现

5 数据清单列表页面实现

5.1 美食数据模型定义

5.2 美食清单页面UI构建

5.3 页面入口配置(工程集成)

6 测试与验证

6.1 测试环境

6.2 美食清单功能验证

6.3 问题与解决方案

7 总结与展望

7.1 总结

7.2 未来展望


摘  要

为适配 OpenHarmony 生态的跨平台应用开发需求,本文目标设计实现一款本地美食清单应用。该应用基于 Flutter 框架构建,集成 http 网络请求库、图片缓存、下拉刷新及星级评分等核心功能模块,实现了美食数据的获取、解析、展示全流程。开发过程中完成了 OpenHarmony SDK 与 Flutter 环境的兼容配置,解决了第三方依赖的空安全适配问题,最终实现应用在DevEco Studio上的稳定运行。

1 引言

1.1 项目背景

Flutter是Google开发的跨平台UI框架,用于通过一套代码库高效构建跨平台应用。‌它采用Dart 编程语言驱动,支持将应用编译为原生机器代码或 JavaScript,从而实现高性能渲染和多端一致性。Flutter 的核心优势在于‌一次开发、多端部署‌,支持移动(iOS/Android)、Web、桌面(Windows/macOS/Linux)及嵌入式设备。OpenHarmony版Flutter 是Flutter针对OpenHarmony系统的适配版本,可以让你用Flutter开发HarmonyOS/OpenHarmony应用。

而本地美食清单作为高频生活类应用场景,需实现网络数据请求、图片加载优化、列表交互等核心功能,适合作为 Flutter 与 OpenHarmony 集成开发的实践载体。

1.2 项目目标

本项目旨在开发一款基于 Flutter+OpenHarmony 的跨平台本地美食清单应用,具体目标包括:

1. 完成 Flutter 与 OpenHarmony 开发环境的搭建与兼容配置;

2. 实现美食数据的网络请求、JSON 解析与本地模型映射;

3. 开发具备图片缓存、下拉刷新、星级评分功能的交互界面;

4. 确保应用在DevEco Studio(模拟器)上稳定运行。

1.3 技术选型依据

1. 框架选择:Flutter 3.27.4(鸿蒙适配版),支持跨平台 UI 一致性渲染,适配 OpenHarmony 系统特性;

2. 网络请求:选用 http 库(轻量高效,适配 Dart 空安全特性,满足基础数据请求需求);

3. 功能增强:集成 cached_network_image 优化图片加载性能,pull_to_refresh 实现交互刷新,flutter_rating_bar 完成评分展示(解决原 rating_bar 库空安全不兼容问题);

4. 开发工具:VS Code 1.108.1(Flutter 代码编写)与 DevEco Studio 6.0.0(鸿蒙权限配置与部署)协同工作。

2 项目功能与开发准备

2.1 功能核心定位

1. 明确页面目标:

基于开源鸿蒙跨平台工程(Flutter+OpenHarmony) 开发“本地美食数据清单”页面。

2. 核心目标:

完成工程网络请求能力的标准化集成、实现美食类数据清单的UI构建+数据绑定+交互逻辑开发、最终在DevEco Studio 6.0.0模拟器完成“网络请求→数据解析→列表渲染→交互验证”全流程运行验证,达成跨平台工程的“网络+列表”核心能力落地。

2.2 开发环境与技术栈配置

2.2.1 基础开发环境

开发工具/环境 版本规格 用途说明
操作系统 Windows 10 64 位 开发主机运行环境
VS Code 1.108.1(user setup) Flutter 业务代码编写与依赖管理
DevEco Studio 6.0.0 Release OpenHarmony 应用配置与部署
OpenHarmony SDK API Version 20(6.0.0.47) 鸿蒙应用开发核心依赖
Flutter 3.27.4(鸿蒙适配版) 跨平台 UI 框架

2.2.2 核心依赖配置

通过VS Code软件完成pubspec.yaml 新增美食场景专属依赖。

1. 首先在终端输入以下命令,将http package添加到依赖中:

flutter pub add http

http package:Dart 内置的 http 包。

这是 Dart 语言标准库的一部分,提供了一个简单的 HTTP 客户端。

在 Flutter 中使用时,需要在 pubspec.yaml 文件中添加依赖:http: ^1.6.0(博主使用版本)。

该包提供了基本的 GET、POST 等请求方法,但功能相对基础。

2. 添加“美食图片缓存”依赖(解决鸿蒙设备图片重复加载卡顿)

Vs code终端中运行以下命令:

flutter pub add cached_network_image

3. 添加“下拉刷新”依赖(适配鸿蒙触控交互的下拉刷新功能)

flutter pub add pull_to_refresh

4. 添加“美食评分组件”依赖(实现鸿蒙 UI 适配的星级评分展示)

支持空安全的评分组件:

flutter pub add flutter_rating_bar

以上四条命令执行完成后,这些依赖会自动添加到你的pubspec.yaml的dependencies部分,同时 Flutter 会自动下载这些依赖包,无需额外执行flutter pub get(flutter pub add命令会自动触发依赖安装)。

最终你的pubspec.yaml的dependencies区域会变成这样:

依赖生效:执行 flutter pub get 命令确保无鸿蒙适配报错。

flutter pub get

3 项目总体设计

3.1 架构设计

应用采用模块化分层架构,按功能职责划分为五层,确保代码的可维护性与扩展性:

1. 表现层(UI 层):包含美食清单页面、列表项组件,负责用户界面渲染;

2. 接口层(API 层):封装美食数据请求接口,统一数据获取入口;

3. 网络层:实现 http 请求封装、响应处理、异常捕获,适配 OpenHarmony 网络特性;

4. 数据层:定义美食数据模型,完成 JSON 与 Dart 对象的序列化 / 反序列化;

5. 配置层:存储接口地址、超时时间等全局配置参数。

3.2 工程目录结构

项目根目录/
├── core/            # 核心封装层
│   └── http/        # 网络请求封装
├── api/             # 接口层
├── models/          # 数据模型层
├── pages/           # 页面层
├── module.json5     # 鸿蒙权限/编译配置
└── pubspec.yaml     # 依赖配置
(详细)项目根目录/
├─ ohos/                  # OpenHarmony 原生工程目录(鸿蒙侧配置/原生代码,保持鸿蒙标准结构)
│  ├─ entry/              # 鸿蒙应用入口模块(当前的ohos/entry)
│  │  ├─ src/main/ets/    # 鸿蒙ETS代码(原生页面/能力)
│  │  │      ├─ module.json5     # 鸿蒙权限/配置文件(当前的配置)
│  │  └─ ...(鸿蒙其他原生配置)
│  │─ ...(鸿蒙其他模块)
│  │
├─ lib/                   # Flutter 业务代码目录(核心分层,重点优化)
│  ├─ core/               # 核心基础封装(复用性强的底层能力)
│  │  ├─ http/            # 网络请求封装(当前的core/http)
│  │     ├─ http_client.dart  # 网络请求工具类
│  │     └─ api_config.dart   # 网络配置(baseUrl、超时等)
│  ├─ api/                # 业务接口层(你当前的api)
│  │  └─ food_api.dart    # 美食相关接口(getFoodList)
│  │
│  ├─ models/             # 数据模型层(你当前的models)
│  │  ├─ food_model.dart  # 美食实体类
│  │  └─ food_model.g.dart # (若用json_serializable,自动生成的模型)
│  │
│  ├─ pages/              # 页面层(按业务模块划分,你当前的pages)
│  │  └─ food/            # 美食业务模块
│  │     └─ food_list_page.dart # 美食列表页面
│  └─ main.dart           # Flutter入口文件
├─ pubspec.yaml           # Flutter依赖配置

4 网络请求能力集成

4.1 鸿蒙网络权限配置

在工程ohos/entry/src/main/module.json5 的 reqPermissions 节点中声明网络权限:配置ohos.permission.INTERNET权限,确保已配置网络权限。说明鸿蒙权限机制对跨平台工程的约束逻辑。

    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]

4.2 美食接口配置与网络工具复用

完成工程目录创建(在 VSCode 的lib/下):

首先,通过VS Code软件在工程根目录的lib/文件夹下(例如博主的:D:\Flutter_HmProject\flutter_harmonyos\lib)依次创建core/、api/、models/、pages/这些目录。具体步骤如下:

在lib/目录上右键 → 点击 “新建文件夹...”,依次创建以下目录:

core/目录,再在core/下创建http/子目录;

api/目录;

models/目录;

pages/目录(后续可在pages/下再建food/子目录存放美食清单页面)。

VS Code安装Flutter插件:

打开 VS Code,点击左侧边栏的“扩展”图标;

在搜索框输入“Flutter”,找到官方的“Flutter”插件,点击“安装”;

同样在扩展商店搜索“Dart”,找到“Dart Code”插件,点击“安装”(博主在安装完Flutter插件后会自动提示安装Dart Code,确认安装即可);

安装完成后,重启 VS Code 使插件生效。

在对应目录下创建业务文件:

在刚创建的目录中,分别新建对应的 Dart 文件(右键目录→“新建文件”):

目录 新建文件名称 作用
core/http/ api_config.dart 配置接口地址、超时等
core/http/ api_response.dart 统一网络响应模型
core/http/ http_client.dart 网络请求工具封装
api/ food_api.dart 美食列表接口请求实现
models/ food_model.dart 美食数据模型(JSON 解析)
pages/food/ food_list_page.dart 美食清单页面 UI + 逻辑

4.2.1 配置本地模拟美食API

博主使用Node.js + Express实现本地模拟美食 API。

1. 安装 Node.js(本地服务运行环境)

下载 Node.js:打开Node.js 官网,下载对应系统的 LTS 版本(Windows/Mac 都支持);

验证安装:打开终端 / 命令提示符,输入node -v和npm -v,能显示版本号即安装成功。

node -v

npm -v

2. 创建本地接口服务项目

新建一个文件夹(比如命名为local_food_api),打开终端并进入该文件夹;

初始化项目:执行命令

npm init -y

安装 Express(轻量 HTTP 服务框架)/安装本地接口服务需要的框架:

npm install express

3. 编写本地接口代码

在local_food_api文件夹中新建server.js文件,编写以下代码:

// 顶部引入cors
const cors = require('cors');
// 导入Express
const express = require('express');
const app = express();
// 配置静态资源目录:让public文件夹里的文件可以通过HTTP访问
app.use(express.static('public'));
// 启用CORS(允许所有域名访问,测试用)
app.use(cors());
// 定义端口(可自定义,比如3000)
const port = 3000;

// 模拟美食列表数据
const mockFoodData = [
  {
    id: 1,
    name: "花生松仁炒蛋",
    desc: "鸡蛋+松仁,鲜嫩可口",
    image: "/food1.jpg", // 对应public里的food1.jpg
    score: 4.8
  },
  {
    id: 2,
    name: "白玉木耳炒西蓝花",
    desc: "白玉木耳滑嫩脆弹,西蓝花清甜脆爽",
    image: "/food2.jpg",
    score: 4.9
  },
  {
    id: 3,
    name: "糖醋排骨",
    desc: "甜甜蜜蜜的糖醋排骨",
    image: "/food3.jpg",
    score: 4.5
  }
];

// 定义美食列表接口:GET请求,路径为/api/localFood/list
app.get('/api/localFood/list', (req, res) => {
  res.json(mockFoodData);
});

// 启动服务
app.listen(port, () => { // 改为用port变量
  console.log(`Server running on http://localhost:${port}`);
});

4. 启动本地接口服务

在local_food_api文件夹的终端中执行:

node server.js

看到 “本地美食 API 服务已启动:http://localhost:3000” 即成功。

浏览器访问http://localhost:3000/api/localFood/list验证接口数据(注意:localhost也可以改为你本地的ipv4/IP地址访问)。

4.2.2 美食接口地址配置

在VS Code中创建core/http/api_config.dart文件,定义基础接口地址、超时时间与请求头,便于全局统一管理。

结合api_config.dart(URL、超时配置),封装http_client.dart:实现请求拦截、统一超时控制、错误码封装;

1. 首先,打开 VS Code 终端,执行以下命令(将json_annotation添加到运行时依赖):

flutter pub add json_annotation

执行后,pubspec.yaml的dependencies会新增json_annotation: ^x.x.x,解决 “json_annotation不是依赖”的错误。

2. 在终端执行build_runner命令,生成 JSON 序列化的代码文件:

flutter pub run build_runner build

3. Flutter 项目中调用这个本地接口

打开 Flutter 项目的api_config.dart,修改配置:

注意:博主的IP地址为192.168.0.108,所以“static const String baseUrl = "http://192.168.0.108:3000";”中要修改为本地的IP地址。

// core/http/api_config.dart
class ApiConfig {
  // 基础接口地址(替换为你电脑的实际IP+Node服务端口)
  // 电脑IP查看:Windows用ipconfig,Mac用ifconfig
  static const String baseUrl = "http://192.168.0.108:3000"; // 测试接口基地址
  // 美食清单数据接口(对应本地Node服务的接口路径)
  static const String food_list_url = "$baseUrl/api/localFood/list";
  // 其他配置(超时、请求头)
  static const int timeout = 10000;   // 超时时间(10s,适配鸿蒙网络特性)
  static Map<String, String> get baseHeaders {
    return {"Content-Type": "application/json;charset=UTF-8"};
  }
}

4.2.3 统一响应模型

编写core/http/api_response.dart(统一响应模型),封装接口响应格式,统一处理成功与失败状态:

基于api_response.dart定义统一响应模型:处理“成功/失败/空数据”的标准化返回格式,说明封装的价值。

// core/http/api_response.dart
class ApiResponse<T> {
  int code;
  String msg;
  T data;
  bool success; // 实例变量

  ApiResponse({
    required this.code,
    required this.msg,
    required this.data,
    required this.success,
  });

  // 成功响应构造方法,静态方法名从“success”改为“successResponse”(避免和实例变量重名)
  static ApiResponse successResponse({dynamic data, String msg = "请求成功"}) {
    return ApiResponse(code: 200, msg: msg, data: data, success: true);
  }

  // 失败响应构造方法,失败响应的方法名也可以统一规范
  static ApiResponse errorResponse({int code = -1, String msg = "请求失败"}) {
    return ApiResponse(code: code, msg: msg, data: null, success: false);
  }
}

4.2.4 网络请求封装

编写core/http/http_client.dart(网络请求封装),基于 http 库实现 GET 请求封装,包含超时处理与异常捕获:

// core/http/http_client.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'api_config.dart';
import 'api_response.dart';

class HttpClient {
  // GET请求封装
  static Future<ApiResponse> getRequest(String url) async {
    try {
      final response = await http.get(
        Uri.parse(url),
        headers: ApiConfig.baseHeaders,
      ).timeout(Duration(milliseconds: ApiConfig.timeout));

      return _handleResponse(response);
    } catch (e) {
      // 同步方法名:error → errorResponse
      return ApiResponse.errorResponse(msg: "网络异常: ${e.toString()}");
    }
  }

  // 处理响应结果
  static ApiResponse _handleResponse(http.Response response) {
    // 打印响应详情(调试用,发布时可以去掉)
    print("接口URL: ${response.request?.url}");
    print("状态码: ${response.statusCode}");
    print("响应体: ${response.body}"); // 看服务器返回的具体错误
    if (response.statusCode == 200) {
      List<dynamic> result = json.decode(response.body);
      // 同步方法名:success → successResponse
      return ApiResponse.successResponse(data: result, msg: "请求成功");
    } else {
      // 同步方法名:error → errorResponse
      return ApiResponse.errorResponse(
        code: response.statusCode,
        msg: "接口请求失败,状态码: ${response.statusCode}",
      );
    }
  }
}

4.2.5 美食清单接口请求实现

在api/目录下编写工具列表请求接口(如food_api.dart):调用封装的http_client.dart,传入api_config中的接口地址,完成请求入参、响应解析的关联。

首先,在vs code终端中输入并执行以下命令:

flutter pub add dio:^5.0.0

美食清单接口请求实现(api/food_api.dart):

// food_api.dart
// 先导入依赖包
import 'package:dio/dio.dart';
import '../core/http/api_config.dart'; // 对应api_config的路径

// 在food_api.dart中定义请求方法
Future<dynamic> getFoodList() async {
  try {
    Dio dio = Dio();
    print("请求接口:${ApiConfig.food_list_url}"); // 打印请求地址
    // 调用api_config中配置的接口地址
    Response response = await dio.get(ApiConfig.food_list_url);
    print("接口返回:${response.data}"); // 打印返回数据
    return response.data; // 提取接口返回的美食列表数据
  } catch (e) {
    print("接口请求失败:$e"); // 打印错误
    throw e;
  }
}

5 数据清单列表页面实现

5.1 美食数据模型定义

编写models/food_model.dart,完成美食数据模型定义:

适配美食接口的JSON格式,定义“名称/图片/评分”等核心字段,做空安全+鸿蒙数据解析适配。

先添加json_serializable依赖(VS Code终端执行),采用json_serializable实现 JSON 数据与 Dart 对象的自动转换,解决数据解析效率问题。

flutter pub add dev:json_serializable dev:build_runner

然后编写模型代码:

// food_model.dart
import 'package:json_annotation/json_annotation.dart';
part 'food_model.g.dart'; // 关联自动生成的解析代码文件

@JsonSerializable()
class FoodModel {
  final int? id;
  final String? name; // 对应接口的name
  final String? desc; // 对应接口的desc
  final String? image; // 对应接口的image
  final double? score; // 美食评分(比如4.5、3.8)

  FoodModel({
    this.id,
    this.name,
    this.desc,
    this.image,
    this.score, // 构造函数添加score
  });

  // 自动生成的JSON转模型方法(由g.dart实现)
  factory FoodModel.fromJson(Map<String, dynamic> json) => _$FoodModelFromJson(json);
  // 自动生成的模型转JSON方法(由g.dart实现)
  Map<String, dynamic> toJson() => _$FoodModelToJson(this);
}

// 美食列表模型
class FoodListModel {
  final List<FoodModel> foodList;

  FoodListModel({required this.foodList});

  // 列表数据解析(简化格式),从JSON数组构建列表模型
  factory FoodListModel.fromJson(List<dynamic> json) {
    return FoodListModel(
      foodList: json.map((e) => FoodModel.fromJson(e)).toList()
    );
  }
}

在 VS Code 终端执行以下命令,自动生成food_model.g.dart解析文件:

flutter pub run build_runner build

执行后,models/下会自动生成food_model.g.dart(无需手动修改)。

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'food_model.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

FoodModel _$FoodModelFromJson(Map<String, dynamic> json) => FoodModel(
      id: (json['id'] as num?)?.toInt(),
      name: json['name'] as String?,
      desc: json['desc'] as String?,
      image: json['image'] as String?,
      score: (json['score'] as num?)?.toDouble(),
    );

Map<String, dynamic> _$FoodModelToJson(FoodModel instance) => <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'desc': instance.desc,
      'image': instance.image,
      'score': instance.score,
    };

5.2 美食清单页面UI构建

美食清单页面UI构建(pages/food/food_list_page.dart):

重点适配鸿蒙设备的“图片显示/评分组件/列表布局”,避免UI溢出/卡顿。

编写pages/food/food_list_page.dart(页面 UI):

// pages/food/food_list_page.dart
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../api/food_api.dart';
import '../../../models/food_model.dart';

class FoodListPage extends StatefulWidget {
  const FoodListPage({super.key});
  @override
  State<FoodListPage> createState() => _FoodListPageState();
}

class _FoodListPageState extends State<FoodListPage> {
  List<FoodModel> _foodList = [];
  final RefreshController _refreshController = RefreshController(initialRefresh: false);
  @override
  void initState() {
    super.initState();
    _getFoodListData(); // 初始化加载数据
  }

// 获取美食数据
Future<void> _getFoodListData() async {
  try {
    final data = await getFoodList();
    if (mounted) {
      // 先判断data是否是List类型
      if (data is List) {
        final foodListModel = FoodListModel.fromJson(data);
        setState(() {
          _foodList = foodListModel.foodList;
        });
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("美食列表加载成功")),
        );
      } else {
        throw Exception("接口返回数据不是列表");
      }
    }
  } catch (e) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("加载失败:${e.toString()}")),
      );
    }
  } finally {
    _refreshController.refreshCompleted();
  }
}

// 列表项UI构建
Widget _buildFoodItem(FoodModel food) {
  // 步骤1:新增打印,确认图片URL是否正确(复制这个URL到手机浏览器能打开)
  final imageUrl = "http://192.168.0.108:3000${food.image ?? ""}";
  print("图片请求URL:$imageUrl"); // 看Flutter控制台日志,确认URL正确
  return Card(
    margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
    child: Padding(
      padding: const EdgeInsets.all(10),
      child: Row(
        children: [
          // 步骤2:原生Image.network
          Image.network(
            imageUrl, // 用上面拼接好的URL
            width: 80,
            height: 80,
            fit: BoxFit.cover, // 让图片填充容器
            // 加载中显示转圈
            loadingBuilder: (context, child, loadingProgress) {
              if (loadingProgress == null) return child;
              return const CircularProgressIndicator();
            },
            // 加载失败打印错误+显示图标
            errorBuilder: (context, error, stackTrace) {
              print("图片加载失败原因:${error.toString()}");
              return const Icon(Icons.fastfood);
            },
          ),
          const SizedBox(width: 12),
          // 美食信息
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // 美食名称
                Text(
                  food.name ?? "未知美食",
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 4),
                // 美食描述
                Text(
                  food.desc ?? "暂无描述",
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: const TextStyle(fontSize: 12, color: Colors.grey),
                ),
                const SizedBox(height: 4),
                // 评分组件
                _buildNativeRating(food.score ?? 0),
              ],
            ),
          ),
        ],
      ),
    ),
  );
}

// 原生星星评分组件(无需第三方包)
Widget _buildNativeRating(double score) {
  const int maxStars = 5; // 满分5星
  final int fullStars = score.floor(); // 全星数量(比如4.5→4)
  final bool hasHalfStar = score - fullStars >= 0.5; // 是否有半星
  final int emptyStars = maxStars - fullStars - (hasHalfStar ? 1 : 0); // 空星数量
  return Row(
    children: [
      // 全星
      ...List.generate(fullStars, (index) => const Icon(
        Icons.star,
        size: 18,
        color: Colors.amber,
      )),
      // 半星(如果有)
      if (hasHalfStar)
        const Icon(
          Icons.star_half,
          size: 18,
          color: Colors.amber,
        ),
      // 空星
      ...List.generate(emptyStars, (index) => const Icon(
        Icons.star_border,
        size: 18,
        color: Colors.amber,
      )),
    ],
  );
}

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("本地美食清单"), centerTitle: true),
      body: SmartRefresher(
        controller: _refreshController,
        onRefresh: _getFoodListData,   // 下拉刷新
        child: _foodList.isEmpty
            ? const Center(child: CircularProgressIndicator())
            : ListView.builder(
                itemCount: _foodList.length,
                itemBuilder: (context, index) => _buildFoodItem(_foodList[index]),
              ),
      ),
    );
  }
}

5.3 页面入口配置(工程集成)

在 main.dart 中配置美食页面为入口(或路由),设置美食清单页面为应用首页,配置主题样式:

// main.dart 美食页面入口
import 'package:flutter/material.dart';
import 'pages/food/food_list_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '本地美食清单',

      theme: ThemeData(primarySwatch: Colors.blue),
      home: const FoodListPage(), // 首页设置,美食清单页面
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

6 测试与验证

6.1 测试环境

测试设备:OpenHarmony 模拟器(API Version 20);

测试工具:DevEco Studio 6.0.0。

6.2 美食清单功能验证

1. 页面启动验证:模拟器启动后,点击图标正常打开“本地美食清单”页面,标题栏/布局无错乱;

2. 网络请求验证:页面自动请求美食数据,无“网络异常”提示(验证 ohos.permission.INTERNET 生效);

3. 列表渲染验证:美食图片/名称/评分完整显示,列表滑动流畅;

5. 交互验证:下拉列表触发刷新。

6.3 问题与解决方案

遇到问题 解决方案
rating_bar 库不支持空安全 替换为 flutter_rating_bar 库
图片加载卡顿 集成 cached_network_image 缓存组件
网络请求无响应 配置 OpenHarmony INTERNET 权限

7 总结与展望

7.1 总结

本项目成功实现了基于 Flutter+OpenHarmony 的本地美食清单跨平台应用开发,完成了从环境搭建、依赖配置、模块封装到功能测试的全流程实践。核心成果包括:

1. 构建了兼容 OpenHarmony 的 Flutter 开发环境,解决了第三方依赖的空安全适配问题,完成了“美食场景网络请求集成+多维度列表渲染+鸿蒙设备适配”;

2. 设计了模块化的工程架构,实现了网络请求、数据解析、UI 展示的分层设计;

3. 集成了图片缓存、下拉刷新等实用功能,提升了应用的用户体验;

4. 验证了 Flutter 框架在 OpenHarmony 平台的兼容性与可行性。

7.2 未来展望

本项目仍有可优化与扩展的方向:

1. 功能扩展:比如可以增加美食的分类、收藏、搜索等功能,丰富应用场景;

2. 性能优化:引入状态管理框架(如 Provider),优化数据流转效率;

3. 安全增强:集成 HTTPS 加密传输,提升数据传输安全性;

4. 多端适配:优化不同尺寸鸿蒙设备的界面适配,实现 "一次开发、多端部署" 的完整跨平台体验。

最后,

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

Logo

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

更多推荐