【Flutter for OpenHarmony】开源鸿蒙跨平台训练营Day3-Flutter请求网络,实现数据清单列表
核心任务
为开源鸿蒙跨平台工程集成网络请求能力,实现数据清单列表的完整构建与开源鸿蒙设备运行验证。
前言
随着开源鸿蒙(OpenHarmony)生态的持续发展,跨平台技术在鸿蒙设备上的应用场景日益广泛。本文以 "上海著名景点清单列表" 为实际需求,无需复杂代码堆砌,聚焦开发思路与核心流程,详解如何通过 Flutter 技术栈快速实现网络数据拉取、列表展示及鸿蒙设备适配,帮助开发者高效完成跨平台应用落地。
一、环境与权限配置
打开 VS Code,点击顶部 “终端”→“新建终端”
首先在终端输入以下命令,将http package添加到依赖中:
flutter pub add http
出现如下问题
这个报错的意思是Flutter 没识别到当前目录是有效的工程根目录,导致无法执行flutter pub add命令,重新用flutter create创建,解决如下
现在工程已经成功创建完成。
在当前终端中执行命令,用 VS Code 打开工程:
code shanghai_scenic_list
在 VS Code 左侧「资源管理器」中,找到工程根目录下的pubspec.yaml文件,双击打开;
替换文件内容为以下代码
name: shanghai_scenic_list
description: A new Flutter project for OpenHarmony.
version: 1.0.0+1
environment:
sdk: '>=3.10.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
dio: ^5.4.3 # 网络请求库
json_annotation: ^4.9.0 # JSON解析库
cached_network_image: ^3.3.0 # 图片加载库
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable: ^6.8.1 # JSON解析代码生成
build_runner: ^2.4.8 # 代码生成工具
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
如下
然后在终端执行命令加载依赖
flutter pub get
我出现了这个问题
这是一个不兼容错误,需要修改 pubspec.yaml 里的 Dart SDK 版本限制,才能让依赖正常加载。
解决步骤
-
打开
pubspec.yaml文件在 VS Code 的左侧资源管理器中,找到工程根目录下的pubspec.yaml并双击打开。 -
找到文件开头的
environment部分:把它改成当前 SDK 匹配的版本:
environment:
sdk: '>=3.4.0 <4.0.0'
保存文件。
又出现了以下问题
这是依赖版本冲突错误,需要调整 json_serializable 的版本来解决
在终端里输入这条命令,把 json_serializable 降级到兼容 3.4.0 的版本
flutter pub add dev:json_serializable:^6.8.0
如下则运行成功

二、创建数据模型文件
在 VS Code 左侧资源管理器中,找到 lib 文件夹
右键 lib → 新建文件夹→ 命名为 models(小写,无空格)
右键 models 文件夹 → 新建文件→ 命名为 scenic_spot_model.dart
双击打开文件,粘贴以下代码
import 'package:json_annotation/json_annotation.dart';
// 这行是自动生成代码的关联,必须保留
part 'scenic_spot_model.g.dart';
// JSON解析模型类
@JsonSerializable()
class ScenicSpotModel {
final int id;
final String name;
final String address;
final double rating;
final String intro;
@JsonKey(name: 'imageUrl') // 适配接口字段名
final String imageUrl;
// 构造函数
ScenicSpotModel({
required this.id,
required this.name,
required this.address,
required this.rating,
required this.intro,
required this.imageUrl,
});
// 从JSON转模型
factory ScenicSpotModel.fromJson(Map<String, dynamic> json) = _$ScenicSpotModelFromJson;
// 模型转JSON
Map<String, dynamic> toJson() => _$ScenicSpotModelToJson(this);
}
保存文件。
接着生成 JSON 解析代码,在 VS Code 终端中,输入命令
flutter pub run build_runner build
此时 models 文件夹下会多出 scenic_spot_model.g.dart 文件
三、创建网络请求工具
右键 lib → 新建文件夹→ 命名为 api
右键 api → 新建文件→ 命名为 api_service.dart
打开文件,粘贴以下代码
import 'package:dio/dio.dart';
import '../models/scenic_spot_model.dart';
// 单例模式的网络请求类
class ApiService {
static final ApiService _instance = ApiService._internal();
factory ApiService() => _instance;
late Dio _dio;
// 私有构造函数
ApiService._internal() {
_dio = Dio()
..options.baseUrl = "https://mock.apifox.cn/m1/2986939-0-default" // 测试接口
..options.connectTimeout = const Duration(seconds: 15) // 超时时间
..interceptors.add(LogInterceptor(responseBody: true)); // 打印日志
}
// 获取上海景点列表
Future<List<ScenicSpotModel>> getShanghaiScenicSpots() async {
try {
final response = await _dio.get("/shanghai/scenic-spots");
if (response.statusCode == 200) {
List<dynamic> dataList = response.data;
return dataList.map((e) => ScenicSpotModel.fromJson(e)).toList();
} else {
throw Exception("请求失败,状态码:${response.statusCode}");
}
} on DioException catch (e) {
String errorMsg = "网络请求失败:";
if (e.type == DioExceptionType.connectionTimeout) errorMsg += "连接超时";
else if (e.type == DioExceptionType.badResponse) errorMsg += "服务器错误";
else errorMsg += e.message ?? "未知错误";
throw Exception(errorMsg);
} catch (e) {
throw Exception("获取数据失败:$e");
}
}
}
四、创建景点列表页面
在 VS Code 左侧资源管理器中,右键 lib → 新建文件夹→ 命名为 pages
右键 pages → 新建文件→ 命名为 scenic_list_page.dart
打开文件,粘贴以下的代码
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../api/api_service.dart';
import '../models/scenic_spot_model.dart';
class ScenicListPage extends StatefulWidget {
const ScenicListPage({super.key});
@override
State<ScenicListPage> createState() => _ScenicListPageState();
}
class _ScenicListPageState extends State<ScenicListPage> {
List<ScenicSpotModel> _spotList = []; // 景点列表数据
bool _isLoading = true; // 加载状态
String? _errorMsg; // 错误信息
final ApiService _api = ApiService(); // 网络请求实例
// 页面初始化时加载数据
@override
void initState() {
super.initState();
_loadScenicData();
}
// 加载景点数据(封装成独立方法)
Future<void> _loadScenicData() async {
setState(() {
_isLoading = true;
_errorMsg = null;
});
try {
final data = await _api.getShanghaiScenicSpots();
setState(() => _spotList = data);
} catch (e) {
setState(() => _errorMsg = e.toString());
} finally {
setState(() => _isLoading = false);
}
}
// 构建单个景点卡片(独立方法,代码更清晰)
Widget _buildScenicItem(ScenicSpotModel spot) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
elevation: 2, // 增加卡片阴影,更美观
child: InkWell(
onTap: () {
// 点击卡片弹出提示
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("${spot.name} - 评分:${spot.rating}")),
);
},
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 景点图片(带加载/错误占位)
CachedNetworkImage(
imageUrl: spot.imageUrl,
width: 80,
height: 80,
fit: BoxFit.cover,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
errorWidget: (context, url, error) => const Icon(
Icons.broken_image,
size: 80,
color: Colors.grey,
),
),
const SizedBox(width: 12),
// 景点信息(占满剩余宽度)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
spot.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
spot.address,
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
const SizedBox(height: 4),
Row(
children: [
const Icon(
Icons.star,
color: Colors.amber,
size: 14,
),
const SizedBox(width: 4),
Text(
"${spot.rating}",
style: const TextStyle(fontSize: 12),
),
],
),
const SizedBox(height: 4),
Text(
spot.intro,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
],
),
),
],
),
),
),
);
}
// 构建页面主体(根据状态切换显示内容)
Widget _buildPageContent() {
if (_isLoading) {
// 加载中:居中显示加载圈
return const Center(child: CircularProgressIndicator());
} else if (_errorMsg != null) {
// 加载失败:显示错误+重试按钮
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_errorMsg!,
style: const TextStyle(color: Colors.red, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadScenicData,
child: const Text("重新加载"),
),
],
),
);
} else if (_spotList.isEmpty) {
// 无数据:显示空提示
return const Center(child: Text("暂无景点数据"));
} else {
// 加载成功:显示列表
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _spotList.length,
itemBuilder: (context, index) => _buildScenicItem(_spotList[index]),
);
}
}
// 页面主构建方法
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("上海著名景点"),
centerTitle: true, // 标题居中
),
body: _buildPageContent(),
// 刷新按钮
floatingActionButton: FloatingActionButton(
onPressed: _loadScenicData,
child: const Icon(Icons.refresh),
),
);
}
}
保存文件。
接着在 lib 文件夹下找到 main.dart
将原有内容替换为以下代码:
import 'package:flutter/material.dart';
import 'pages/scenic_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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ScenicListPage(), // 设置首页为景点列表
debugShowCheckedModeBanner: false, // 隐藏调试标签
);
}
}
保存文件。
打开DevEco Studio→File→Open→打开文件下的ohos,如下
运行如下

总结
- 网络能力与权限配置:熟练掌握开源鸿蒙跨平台工程使用原生网络请求API的规范,完成网络权限的声明与配置,确保工程具备合法且稳定的网络访问能力。
- 数据清单列表构建:基于网络请求返回的数据,完成清单列表的完整开发,包括数据解析、列表渲染、空数据/异常数据兜底展示,确保列表功能完整、交互流畅。
- 可选拓展(三方库接入):可选用适配开源鸿蒙跨平台技术栈网络请求三方库实现上述能力,需掌握不同技术栈三方库的集成流程、版本适配规则及差异化使用要点。
- React Native技术栈:推荐使用axios(通用型HTTP请求库,支持拦截器、请求取消、响应转换)。OpenHarmony已兼容三方库清单。
- Flutter技术栈:推荐使用dio(Flutter生态主流请求库,支持拦截器、FormData、Cookie管理)、http(Flutter官方轻量库,无冗余依赖)、chopper(基于注解生成请求代码,便于大型项目维护);接入时需重点关注三方库与开源鸿蒙SDK版本的兼容性、跨平台请求稳定性,以及鸿蒙权限体系对三方库网络访问的限制适配。OpenHarmony已兼容三方库清单。
- 开源鸿蒙终端运行验证代码提交规范:确保添加网络请求能力的工程在开源鸿蒙真机/开发板/模拟器上能正常运行,数据清单列表可正确加载、展示网络请求返回的数据。将完整工程代码(含工程配置文件、源码、资源文件、调试日志)按 Git 提交规范(清晰的 commit message、合理的提交粒度)推送到 AtomGit 公开仓库,确保仓库代码可直接拉取并复现运行效果。
欢迎加入开源鸿蒙跨平台社区 https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)