【开源鸿蒙跨平台开发先锋训练营】使用Flutter请求网络接口并渲染页面
学习目标:
这里和大家一起来学习针对鸿蒙系统如何使用Flutter框架进行服务器数据的网络请求并渲染到页面中。
Flutter网络请求库 Dio
Dio 是 Flutter 中一个强大的网络请求库,支持 RESTful API、文件上传/下载、拦截器、请求取消等功能。相比原生 http 包,Dio 提供了更简洁的 API 和更丰富的特性。
1. 添加依赖
~/pubspec.yaml 项目根目录下添加dio依赖
dio: ^5.4.0

2. 创建dio服务类
项目很多地方都需要用到网络请求,故创建了一个全局单例的服务类,专门处理数据请求的。
import 'package:dio/dio.dart';
import 'dart:convert';
/// API 服务类
/// 用于处理网络请求和数据获取
class ApiService {
// 单例模式
static final ApiService _instance = ApiService._internal();
factory ApiService() => _instance;
ApiService._internal();
// Dio 实例
late final Dio _dio;
// 静态资源接口地址
static const String baseUrl = 'https://hanfucn.com';
static const String mockDataPath = '/assets/mockData-DfdWDX7N.js';
/// 初始化 Dio
void init() {
_dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*',
},
));
// 添加拦截器(可选)
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
));
}
/// 获取 Dio 实例
Dio get dio => _dio;
}
数据接口这里从一个远程js文件中获取数据,除了获取数据外,还多了一步解析数据, 额外需要一个方法来处理从 JavaScript 文件中提取 JSON 数据
/// 获取 Mock 数据
/// 从 JavaScript 文件中提取 JSON 数据
///
/// 返回解析后的 JSON 数据(Map 或 List)
/// 如果请求失败,抛出 DioException
Future<dynamic> getMockData() async {
try {
final response = await _dio.get(mockDataPath);
if (response.statusCode == 200) {
final data = response.data;
// 如果返回的是字符串(JavaScript 文件)
if (data is String) {
// 尝试提取 JSON 数据
// 方法1: 如果文件包含 JSON 对象,尝试直接解析
try {
// 查找 JSON 对象(可能是 export default {...} 或 module.exports = {...})
final jsonMatch = RegExp(r'\{[\s\S]*\}').firstMatch(data);
if (jsonMatch != null) {
final jsonString = jsonMatch.group(0);
return json.decode(jsonString!);
}
// 方法2: 如果包含 JSON 数组
final arrayMatch = RegExp(r'\[[\s\S]*\]').firstMatch(data);
if (arrayMatch != null) {
final jsonString = arrayMatch.group(0);
return json.decode(jsonString!);
}
// 方法3: 尝试直接解析整个字符串
return json.decode(data);
} catch (e) {
// 如果解析失败,返回原始字符串
return data;
}
}
// 如果已经是 Map 或 List,直接返回
return data;
} else {
throw DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
message: '请求失败,状态码: ${response.statusCode}',
);
}
} on DioException catch (e) {
// 处理网络错误
throw _handleError(e);
} catch (e) {
// 处理其他错误
throw Exception('获取数据失败: $e');
}
}
扩展GET和POST通用接口
/// 通用 GET 请求
Future<dynamic> get(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
try {
final response = await _dio.get(
path,
queryParameters: queryParameters,
options: options,
);
return response.data;
} on DioException catch (e) {
throw _handleError(e);
}
}
/// 通用 POST 请求
Future<dynamic> post(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
try {
final response = await _dio.post(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
return response.data;
} on DioException catch (e) {
throw _handleError(e);
}
}
另外还有一步比较重要的就是错误处理,针对服务端返回的错误信息,进行页面本地化状态显示
/// 错误处理
DioException _handleError(DioException error) {
String message = '请求失败';
// 根据 HTTP 状态码进行错误匹配
final statusCode = error.response?.statusCode;
if (statusCode != null) {
if (statusCode >= 400 && statusCode < 500) {
// 客户端错误
message = '请求错误: $statusCode';
} else if (statusCode >= 500 && statusCode < 600) {
// 服务端错误
message = '服务器错误: $statusCode';
}
} else {
// 如果没有状态码则根据 Dio 的异常类型判断
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
message = '连接超时,请检查网络';
break;
case DioExceptionType.cancel:
message = '请求已取消';
break;
case DioExceptionType.unknown:
message = '网络连接失败,请检查网络设置';
break;
default:
message = error.message ?? '未知错误';
}
}
return DioException(
requestOptions: error.requestOptions,
response: error.response,
type: error.type,
message: message,
);
}
不同的statusCode对应不同的提示,需要给用户看得懂的提示,这里简单定义了几种状态码,后续可以新增。如token过期等等。
数据请求处理

这是我要的整个数据链,从js中获取到的数据提取图片组成新的数组,我只需要图片,然后页面中显示这些不同的图片,类似抖音图片上下滑动,如果是自己写接口,就后端直接按需要的格式返回数据格式就行。
页面
图片加载这里用到另外一个框架,cached_network_image
所以需要再pubspec.yaml 中新增依赖
页面大概的代码结构
使用了flutter的动态组件StatefulWidget ,首先拆解下这个类似抖音的页面有哪些东西
- 有状态的、动态的widget,类似控制器
build - 要用到请求服务类去获取数据并解析,
_loadData - 页面构建
_buildBody - 单个图片视图构建
_buildImagePage - 然后数据中图片在视图的渲染
- 页面初始化过程:
1. HomePage Widget 被创建
↓
2. _HomePageState 被创建
↓
3. initState() 被调用
├─→ super.initState()
├─→ _initializeApi() // 初始化 API 服务
│ └─→ _apiService.init() // 配置 Dio 实例
└─→ _loadData() // 开始异步加载数据
- 页面数据流如下:
_loadData() 执行流程:
↓
1. setState() - 设置加载状态
├─→ _isLoading = true
└─→ _errorMessage = null
↓
2. 触发 build() 重建(显示加载指示器)
↓
3. 异步执行数据获取:
├─→ _apiService.getMockData() // 网络请求获取 JS 数据
├─→ DataParser.parseWorksAndImages() // 解析数据并提取图片
└─→ 获取 allImages 数组
↓
4. setState() - 更新数据状态
├─→ _images = allImages
└─→ _isLoading = false
↓
5. 触发 build() 重建(显示图片列表)
- 渲染过程
build() 被调用
↓
_buildBody() 被调用
↓
根据状态判断:
├─→ 如果 _isLoading == true
│ └─→ 返回 CircularProgressIndicator(加载指示器)
│
├─→ 如果 _errorMessage != null
│ └─→ 返回错误提示界面(带重试按钮)
│
├─→ 如果 _images.isEmpty
│ └─→ 返回"暂无数据"提示
│
└─→ 如果数据正常
└─→ 返回 PageView.builder
├─→ itemBuilder 被调用(为每个图片创建页面)
│ └─→ _buildImagePage(imageUrl, index) 被调用
│ ├─→ 创建 Stack 布局
│ ├─→ 添加 CachedNetworkImage(图片组件)
│ ├─→ 添加渐变遮罩(底部)
│ └─→ 添加页码指示器(右上角)
│
└─→ onPageChanged 回调(当页面切换时)
└─→ setState() 更新 _currentIndex
这样就构成了一个看起来效果非常好的页面了,然后页面上下滚动是依靠了Column组件的能力,这是一个垂直排列,当内容超出容器高度后,直接上下滚动的这么一个组件,与之对应的是水平滚动组件Row,更详细的组件介绍可以到https://flutter.dev 上学习。
编译和运行
- 拉取依赖
编译前需要拉取下依赖,执行以下命令flutter pub get

- 启动DevEco-Studio 和模拟器
使用DevEco-Studio打开项目ohos目录, 参考【开源鸿蒙跨平台开发先锋训练营】使用Flutter框架搭建鸿蒙App项目及代码管理 这篇帖子 - 搭建项目-运行项目 部分描述的进行运行准备。
这里我使用命令行运行项目,回到我的Cursor,在项目根目录中运行一下命令flutter run或者flutter run -d 127.0.0.1:5555

- 配置调试签名
这里有个报错,就是第一次需要通过DevEco Studio打开ohos工程后配置调试签名(File -> Project Structure -> Signing Configs 勾选Automatically generate signature),按照描述进行操作。
勾选Automatically generate signature后,点击Apply然后ok。
这里配置遇到问题,就是勾选了好多次, DevEco Studio 的配置没有写入build-profile.json5,依然在/ohos/build-profile.json5 中看不到签名配置,不清楚是不是IED的问题还是,之前配置都ok的, 后来想了个办法将其他项目中的build-profile.json5 内容拷贝过来才正常了。
运行正常,App也如愿启动成功,但也遇到了新的问题,启动后App一直图片不显示,无法加载,最终试着把CachedNetworkImage 改成 Image.network 后也不行,后来还是直接使用dio下载图片才解决了图片显示的问题,
参考network_image_widget.dart
Future<void> _loadImage() async {
try {
print('开始下载图片: ${widget.imageUrl}');
final dio = Dio();
final response = await dio.get<Uint8List>(
widget.imageUrl,
options: Options(
responseType: ResponseType.bytes,
headers: {
'User-Agent': 'Mozilla/5.0 (Linux; HarmonyOS) AppleWebKit/537.36',
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
},
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
),
);
if (response.data != null) {
print('图片下载成功: ${widget.imageUrl}, 大小: ${response.data!.length} bytes');
setState(() {
_imageData = response.data;
_isLoading = false;
});
} else {
throw Exception('图片数据为空');
}
} catch (e) {
print('图片下载失败: ${widget.imageUrl}, 错误: $e');
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
总算是达到预期效果了,可上下滑动翻页,每页图片全屏展示了。
项目中已隐藏接口域名,需要下载使用建议更换成其他数据接口,修改下数据解析代码才能使用。
结束语
本文主要是边学习边整理思路,感谢阅读本帖,如对贴中内容有意见和建议的,欢迎与我联系交流,也欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)