Flutter 鸿蒙跨端开发:使用 Dio 库实现简单的网络请求架构
本文详细介绍了Flutter鸿蒙跨平台开发中实现网络请求的完整流程。
本文参考Flutter鸿蒙开发指南(三):使用dio库实现网络请求-CSDN博客
本文记录操作过程以及遇到的问题
一、项目初始化
1.1 Android Studio创建项目

1.1.1 在终端创建osho项目
代码如下:
flutter create --platform ohos .
说明:命令中“.”表示在当前目录创建项目,--platform ohos指定适配鸿蒙平台,需确保本地已配置Flutter鸿蒙开发环境。
1.1.2 规范目录结构
整理文件目录结构,按功能模块分包文件,便于后续开发维护:

目录详解表
|
目录名 |
作用说明 |
|
api |
存放所有网络请求相关代码,如接口封装、请求拦截、响应解析等 |
|
assets |
存放静态资源,如图片、字体、JSON 配置文件等 |
|
components |
存放可复用的公共 UI 组件,如按钮、卡片、输入框等 |
|
constants |
存放全局常量,如接口域名、路由名称、枚举值、主题色等 |
|
pages |
存放页面级组件,每个页面为一个独立文件或子目录 |
|
routes |
存放路由配置,如路由表、路由拦截、页面跳转逻辑 |
|
stores |
存放全局状态管理代码,如Provider、GetX、Bloc等 |
|
utils |
存放工具类与通用函数,如日期格式化、加密、存储工具等 |
|
viewmodels |
存放页面的视图模型(MVVM 模式),处理页面逻辑与数据交互 |
|
main.dart |
项目入口文件 |
1.2 配置访问令牌、创建仓库
在这个网址申请新的令牌
然后在首页创建仓库


1.3 使用Git命令托管代码
1.3.1 Git Bash 常用命令
#1、仓库初始化
git init # 在当前目录初始化 Git 仓库
git clone <仓库地址> # 克隆远程仓库到本地
#2、配置身份
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
git config --global --list # 查看全局配置
#3、提交代码
git add . # 将所有修改文件加入暂存区
git add 文件名 # 单个文件加入暂存区
git commit -m "提交说明" # 提交暂存区到本地仓库
#4、同步远程仓库
git pull origin 分支名 # 拉取远程分支代码到本地
git push origin 分支名 # 推送本地分支到远程仓库
#5、分支管理
git branch # 查看本地所有分支
git branch 分支名 # 创建新分支
git checkout 分支名 # 切换到目标分支
git checkout -b 分支名 # 创建并切换到新分支
git merge 分支名 # 将目标分支合并到当前分支
#6、删除操作
git reset HEAD -- .# 清空暂存区所有文件的跟踪状态(回到add之前的状态)
rm -rf 文件名 # 彻底删除文件夹(含所有子文件)
rm -rf 文件名/包名 # 彻底删除包
#如果想删掉当前仓库(Desktop)下的所有文件(谨慎操作,建议先备份重要文件):
rm -rf * .[!.]* # 递归删除当前目录下所有文件/文件夹(Git Bash执行)
#7、验证操作结果
git status # 查看Git状态,正常会显示“working tree clean”(工作区干净)
#如果你不仅执行了git add,还执行了git commit(把文件提交到版本库),想撤销这次提交并删除文件:
git reset --soft HEAD~1 # 撤销最近一次提交(保留本地文件)
git reset HEAD -- . # 再执行清空暂存区
git reset --soft HEAD~1 # 撤销最近一次提交(保留本地文件)
git reset HEAD -- . # 再执行第一步清空暂存区
# 最后按需删除本地文件(参考删除操作)
1.3.2 用到的命令
git init
git add .
git commit -m
git remote add origin <远程仓库地址> #<远程仓库地址>改成自己实际代码仓库,<>是不需要写
git push -u origin master
git log
注意:文章开篇引用的文章中写的命令是:
git remote add origin master <远程仓库地址>
有错误,master 是分支名,不能出现在 git remote add 命令中,仅在「推送 / 拉取 / 切换」分支时使用。
正确写法:
#先关联远程仓库(用 git remote add)
git remote add origin 你的远程仓库地址
#再推送 master 分支到远程 origin 别名对应的仓库
git push -u origin master
Git命令使用说明表
|
Git命令 |
核心作用(适配Flutter鸿蒙跨端开发场景) |
补充说明(开发实操要点) |
|
git init |
初始化本地Git仓库,生成隐藏的.git文件夹,让当前Flutter鸿蒙项目目录被Git版本管理 |
仅在新建项目时执行一次,比如你创建Flutter鸿蒙跨端项目后,先执行该命令初始化仓库,后续所有代码变更都可被Git追踪 |
|
git add . |
将本地工作目录中所有变更(新增 / 修改的Flutter页面、鸿蒙适配配置文件、组件代码等)添加到Git暂存区 |
开发中写完一个功能(比如抽离完无状态组件)后执行,. 代表全部文件,也可指定文件(如git add components/02_无状态组件.dart),仅暂存区文件能被commit |
|
git commit -m "初始化仓库" |
将暂存区的代码提交到本地仓库,生成版本记录;""内的备注可自定义,需清晰说明本次提交的任务(适配Flutter鸿蒙场景) |
示例备注:
备注要简洁,便于后续追溯版本变更 |
|
git remote add origin <远程仓库地址> |
将本地Flutter鸿蒙项目仓库与远程Git仓库(如Gitee/GitHub)建立关联,origin是远程仓库的默认别名 |
执行前需先获取远程仓库地址(如HTTPS/SSH链接),关联后可通过origin快速操作远程仓库,无需重复输入完整地址;可执行git remote -v验证关联是否成功 |
|
git push -u origin master |
将本地master分支的Flutter鸿蒙项目代码推送到远程仓库的master分支;-u建立本地与远程分支的跟踪关系 |
首次推送跨端项目代码时执行,后续推送只需输入git push;若远程仓库有初始文件(如README),需先git pull origin master拉取合并后再推送 |
|
git log |
查看本地仓库的提交日志,包括每次提交的版本号、作者、时间、备注信息等 |
开发中可通过该命令追溯Flutter鸿蒙项目的变更记录,比如:
常用参数:git log --oneline(精简日志)、git log -p(查看提交的代码差异) |
1.3.3 输入命令
先输入命令:
git init
git add .
git commit -m

这里遇到第一个问题,问题报错如下:
PS F:\Android\pros\xiuhu_mall> git commit -m "初始化项目"
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address
这个报错是是 Git 提交代码时的身份认证错误,核心原因是还没给 Git 配置用户名和邮箱,导致 Git 无法识别提交者身份,所以拒绝执行 commit 操作。在https://atomgit.com/setting/绑定邮箱。
也可以通过桌面右键菜单里的Open Git Bash here命令行工具绑定

绑定后继续尝试,发现还是不行
打开项目文件位置,在项目文件位置处进入cmd,在cmd输入以下内容:
git config user.name "填你的用户名"
git config user.email "填你的邮箱"
重新提交git commit -m "初始化项目",成功解决

再输入命令:
git log #查看推送代码的日志
git remote add origin <远程仓库地址>
#地址可以在仓库复制
#修改已存在的远程仓库的地址(更新绑定令牌)
git remote set-url origin https://用户名:令牌@仓库地址/用户名/创仓库时的项目路径名.仓库后缀
#例子:git remote set-url origin https://D:123456789@gitlab.com/D/Project.git
git push -u origin master #最后执行推送仓库的命令
完成推送

二、使用Dio实现网络请求
2.1 添加依赖
在pubspec.yaml文件添加以下内容:
dio: ^5.5.0+1
provider: ^6.1.2

然后点击右上角的Pub get
2.2 接口说明
这里我举两个例子,一个是高德地图开放平台的天气接口,一个是IP 地址查询接口
2.2.1 高德地图开放平台的天气接
接口说明:
|
项 |
详情 |
|
接口地址 |
https://restapi.amap.com/v3/weather/weatherInfo |
|
请求方式 |
GET |
|
返回格式 |
JSON |
|
请求示例 |
https://restapi.amap.com/v3/weather/weatherInfo?city=110101&key=e262********** |
|
接口说明 |
高德地图免费天气查询接口,支持按城市编码 / 名称查询实时天气,无需申请密钥(使用公开测试 key) |
请求参数说明:
|
名称 |
类型 |
必填 |
说明 |
|
city |
string |
是 |
城市编码 / 城市名称(示例:110101 = 北京市东城区,310101 = 上海市黄浦区) |
|
key |
string |
是 |
高德开放平台 key(测试用公开 key:e262e59365f367738699546094269989) |
|
extensions |
string |
选填 |
返回信息类型(base = 实时天气,all = 实时 + 预报,默认 base) |
返回参数说明:
|
名称 |
类型 |
说明 |
|
status |
string |
请求状态(1 = 成功,0 = 失败) |
|
lives |
array |
实时天气数组(单城市返回 1 条) |
|
province |
string |
省份名称 |
|
city |
string |
城市名称 |
|
weather |
string |
天气状况(如:晴、多云) |
|
temperature |
string |
实时气温(单位:℃) |
|
winddirection |
string |
风向(如:东风) |
|
windpower |
string |
风力(如:3 级) |
完整的 Dio 请求示例:
#步骤 1:添加 Dio 依赖
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # 保持最新稳定版
#执行 flutter pub get 安装依赖。
#步骤 2:编写请求代码
#创建 weather_api_demo.dart 文件
import 'package:dio/dio.dart';
void main() async {
// 1. 初始化 Dio 实例,添加基础配置
Dio dio = Dio()
..options.baseUrl = 'https://restapi.amap.com/v3/weather' // 配置baseUrl,简化请求地址
..options.headers = {'Content-Type': 'application/json'}; // 设置请求头
try {
// 2. 构造请求参数(查询北京市东城区实时天气)
Map<String, dynamic> params = {
'city': '110101', // 北京市东城区编码
'key': '1234567890', // 公开测试key(可自行申请)
'extensions': 'base', // 只返回实时天气
};
// 3. 发送 GET 请求(使用baseUrl后,只需写接口路径)
Response response = await dio.get(
'/weatherInfo',
queryParameters: params,
);
// 4. 处理响应数据
if (response.statusCode == 200 && response.data['status'] == '1') {
print('接口返回完整数据:${response.data}');
// 解析实时天气数据(lives数组的第一个元素)
Map<String, dynamic> weatherData = response.data['lives'][0];
String province = weatherData['province'];
String city = weatherData['city'];
String weather = weatherData['weather'];
String temp = weatherData['temperature'];
String windDir = weatherData['winddirection'];
String windPower = weatherData['windpower'];
print('\n===== 解析后的天气信息 =====');
print('地区:$province - $city');
print('天气:$weather');
print('气温:$temp ℃');
print('风向/风力:$windDir $windPower');
} else {
print('请求失败:${response.data['info'] ?? '未知错误'}');
}
} on DioException catch (e) {
// 5. 异常处理
print('请求异常:');
if (e.type == DioExceptionType.badResponse) {
print('接口返回错误:${e.response?.data['info']}');
} else if (e.type == DioExceptionType.connectionError) {
print('网络连接错误,请检查网络');
} else {
print('错误详情:${e.message}');
}
}
}
#步骤 3:运行代码
#控制台会输出类似内容:
接口返回完整数据:{status: 1, count: 1, info: OK, infocode: 10000, lives: [{province: 北京, city: 东城区, weather: 晴, temperature: 18, winddirection: 北风, windpower: 3}]}
===== 解析后的天气信息 =====
地区:北京 - 东城区
天气:晴
气温:18 ℃
风向/风力:北风 3级
2.2.2 IP 地址查询接口
接口说明:
|
项 |
详情 |
|
接口地址 |
https://api.ipify.org?format=json(基础版)/ http://ip-api.com/json(详情版) |
|
请求方式 |
GET |
|
返回格式 |
JSON |
|
请求示例 |
http://ip-api.com/json?lang=zh-CN&fields=query,country,regionName,city,isp |
|
接口说明 |
免费的 IP 地址信息查询接口,无需密钥,可查询 IP 所属国家 / 地区 / 运营商等信息 |
请求参数说明:
|
名称 |
类型 |
必填 |
说明 |
|
lang |
string |
选填 |
返回语言(zh - CN = 中文,en = 英文,默认英文) |
|
fields |
string |
选填 |
指定返回字段(用逗号分隔,减少冗余数据) |
|
ip |
string |
选填 |
要查询的 IP 地址(不填则查询本机 IP) |
返回参数说明:
|
名称 |
类型 |
说明 |
|
query |
string |
查询的 IP 地址 |
|
country |
string |
所属国家 |
|
regionName |
string |
所属省份 / 地区 |
|
city |
string |
所属城市 |
|
isp |
string |
网络运营商 |
|
status |
string |
请求状态(success = 成功) |
完整的 Dio 请求示例:
#步骤 1:添加 Dio 依赖(同之前)
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # 保持最新稳定版
#步骤 2:编写请求代码
#创建 ip_api_demo.dart 文件
import 'package:dio/dio.dart';
void main() async {
// 1. 初始化 Dio 实例(可配置超时时间等)
Dio dio = Dio()
..options.connectTimeout = const Duration(seconds: 5) // 连接超时5秒
..options.receiveTimeout = const Duration(seconds: 3); // 接收超时3秒
try {
// 2. 构造请求参数(指定返回中文+核心字段)
Map<String, dynamic> params = {
'lang': 'zh-CN', // 返回中文结果
'fields': 'query,country,regionName,city,isp', // 只返回需要的字段
// 可选:指定查询的IP,比如 'ip': '8.8.8.8'(谷歌DNS)
};
// 3. 发送 GET 请求
Response response = await dio.get(
'http://ip-api.com/json',
queryParameters: params,
);
// 4. 处理响应数据
if (response.statusCode == 200 && response.data['status'] == 'success') {
print('接口返回完整数据:${response.data}');
// 解析核心字段
String ip = response.data['query'];
String country = response.data['country'];
String province = response.data['regionName'];
String city = response.data['city'];
String isp = response.data['isp'];
print('\n===== 解析后的IP地址信息 =====');
print('查询IP:$ip');
print('所属地区:$country > $province > $city');
print('网络运营商:$isp');
} else {
print('请求成功但返回状态异常:${response.data}');
}
} on DioException catch (e) {
// 5. 精细化异常捕获
print('请求失败:');
switch (e.type) {
case DioExceptionType.connectionTimeout:
print('错误类型:连接超时');
break;
case DioExceptionType.receiveTimeout:
print('错误类型:接收超时');
break;
case DioExceptionType.badResponse:
print('错误类型:响应异常');
print('状态码:${e.response?.statusCode}');
print('错误信息:${e.response?.data}');
break;
default:
print('错误类型:${e.type}');
print('错误描述:${e.message}');
}
}
}
#步骤 3:运行代码
#控制台会输出类似内容:
接口返回完整数据:{query: 114.114.114.114, country: 中国, regionName: 江苏省, city: 南京市, isp: 南京xxx有限公司, status: success}
===== 解析后的IP地址信息 =====
查询IP:114.114.114.114
所属地区:中国 > 江苏省 > 南京市
网络运营商:南京xxx有限公司
2.3 使用Dio进行网络请求并渲染UI
2.3.1 猫咪图片数据模型
文件路径:lib/viewmodels/cat_model.dart
作用:定义API返回的数据结构
代码如下:
class CatModel {
final String id;
final String url;
final int width;
final int height;
CatModel({
required this.id,
required this.url,
required this.width,
required this.height,
});
factory CatModel.fromJson(Map<String, dynamic> json) {
return CatModel(
id: json['id'] ?? '',
url: json['url'] ?? '',
width: json['width'] ?? 0,
height: json['height'] ?? 0,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'url': url,
'width': width,
'height': height,
};
}
2.3.2 猫咪API服务
文件路径:lib/api/cat_api.dart
作用:封装具体的网络请求方法
代码如下:
import 'package:qing_mall/utils/http_util.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/contents/api_constants.dart';
class CatApi {
static final HttpUtil _http = HttpUtil();
// 获取猫咪图片列表(基础方法)
static Future<List<CatModel>> getCatImages({
int limit = 1, // 默认只获取1张
int? page,
String? order,
bool? hasBreeds,
String? breedIds,
String? categoryIds,
int? subId,
}) async {
try {
final Map<String, dynamic> queryParams = {
'limit': limit,
};
// 添加可选参数
if (page != null) queryParams['page'] = page;
if (order != null) queryParams['order'] = order;
if (hasBreeds != null) queryParams['has_breeds'] = hasBreeds ? 1 : 0;
if (breedIds != null) queryParams['breed_ids'] = breedIds;
if (categoryIds != null) queryParams['category_ids'] = categoryIds;
if (subId != null) queryParams['sub_id'] = subId;
final data = await _http.get(
'${ApiConstants.catBaseUrl}${ApiConstants.catImagesEndpoint}',
queryParams: queryParams,
);
if (data is List) {
return data.map((json) => CatModel.fromJson(json)).toList();
} else {
throw Exception('返回数据格式错误');
}
} catch (e) {
rethrow;
}
}
// 获取单张猫咪图片(简化方法)
static Future<CatModel> getSingleCat() async {
final cats = await getCatImages(limit: 1); // ✅ 调用上面定义的方法
return cats.first;
}
// 可选:获取多张猫咪图片
static Future<List<CatModel>> getMultipleCats(int count) async {
return await getCatImages(limit: count);
}
}
2.3.3 猫咪页面调用
文件:lib/pages/Cats/index.dart
作用:界面调用API并显示数据
代码如下:
import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
import 'package:qing_mall/components/cat_card.dart';
class CatsPage extends StatefulWidget {
const CatsPage({super.key});
@override
State<CatsPage> createState() => _CatsPageState();
}
class _CatsPageState extends State<CatsPage> {
CatModel? _cat; // ✅ 只保存一张图片
bool _isLoading = false;
String? _error;
@override
void initState() {
super.initState();
_fetchCat(); // ✅ 只获取一张
}
Future<void> _fetchCat() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final cat = await CatApi.getSingleCat(); // ✅ 调用获取单张的方法
setState(() {
_cat = cat;
});
} catch (e) {
setState(() {
_error = e.toString();
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('随机猫咪 🐱'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _fetchCat, // ✅ 刷新时也只获取一张
),
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text('加载失败: $_error'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _fetchCat,
child: const Text('重新获取'),
),
],
),
);
}
if (_cat == null) {
return const Center(child: Text('暂无猫咪图片'));
}
// ✅ 只显示一张图片,居中显示
return SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CatCard(cat: _cat!),
const SizedBox(height: 20),
// 添加一些操作按钮
Wrap(
spacing: 12,
children: [
ElevatedButton.icon(
onPressed: _fetchCat,
icon: const Icon(Icons.refresh),
label: const Text('换一张'),
),
OutlinedButton.icon(
onPressed: () {
// 可以添加保存或分享功能
},
icon: const Icon(Icons.download),
label: const Text('保存'),
),
],
),
const SizedBox(height: 20),
// 显示详细信息
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'图片信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text('ID: ${_cat!.id}'),
Text('宽度: ${_cat!.width} 像素'),
Text('高度: ${_cat!.height} 像素'),
Text('URL: ${_cat!.url}'),
],
),
),
],
),
),
),
);
}
}
2.3.4 猫咪卡片组件
文件:lib/components/cat_card.dart
作用:专门显示猫咪图片的UI组件
代码如下:
import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
class CatCard extends StatelessWidget {
final CatModel cat;
const CatCard({
super.key,
required this.cat,
});
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 2,
child: Column(
children: [
// 猫咪图片
ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
child: SizedBox(
width: double.infinity,
height: 200,
child: Image.network(
cat.url,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey[100],
child: const Center(
child: Icon(
Icons.image_not_supported,
size: 50,
color: Colors.grey,
),
),
);
},
),
),
),
// 猫咪信息
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'尺寸: ${cat.width} × ${cat.height}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
'ID: ${cat.id}', // ✅ 修复:直接显示完整ID,不截取
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
],
),
);
}
}
2.3.5 路由配置文件
文件:lib/routes/index.dart
作用:管理页面之间的跳转关系
代码如下:
import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';
// 管理路由
import 'package:flutter/material.dart';
import 'package:qing_mall/pages/Login/index.dart';
import 'package:qing_mall/pages/Main/index.dart';
import 'package:qing_mall/pages/Cats/index.dart';
// 返回App根组件
Widget getRootWidget() {
return MaterialApp(
// 命名路由
initialRoute: '/', // 默认首页
routes: getRootRoutes(),
);
}
// 返回该App的路由配置
Map<String, Widget Function(BuildContext)> getRootRoutes() {
return {
'/': (context) => MainPage(), // 主页路由
'/login': (context) => LoginPage(), // 登录路由
'/cats': (context) => CatsPage(), // 猫咪图库路由
};
}
2.3.6 猫咪API常量配置
文件:lib/contents/api_constants.dart
作用:集中管理API配置,避免硬编码,便于统一维护。
代码如下:
class ApiConstants {
// 猫咪API
static const String catBaseUrl = 'https://api.thecatapi.com/v1';
static const String catImagesEndpoint = '/images/search';
// 默认配置
static const int defaultPageSize = 10;
static const int defaultTimeout = 15; // 秒
}
2.3.7 封装Dio网络请求
文件:lib/utils/http_util.dart
作用:Dio网络请求工具类,封装单例和错误处理
代码如下:
import 'package:dio/dio.dart';
class HttpUtil {
static final HttpUtil _instance = HttpUtil._internal();
late Dio _dio;
factory HttpUtil() => _instance;
HttpUtil._internal() {
_dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
));
}
Dio get dio => _dio;
// 通用GET请求
Future<dynamic> get(String endpoint, {Map<String, dynamic>? queryParams}) async {
try {
final response = await _dio.get(endpoint, queryParameters: queryParams);
return response.data;
} on DioException catch (e) {
throw _handleError(e);
}
}
String _handleError(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return '网络连接超时,请检查网络';
case DioExceptionType.badResponse:
return '服务器错误: ${e.response?.statusCode}';
case DioExceptionType.cancel:
return '请求已取消';
default:
return '网络错误: ${e.message}';
}
}
}
2.3.8 状态管理
文件:lib/stores/cat_store.dart
作用:负责获取、管理和更新猫咪图片数据
代码如下:
import 'package:flutter/material.dart';
import 'package:qing_mall/api/cat_api.dart';
import 'package:qing_mall/viewmodels/cat_model.dart';
class CatStore extends ChangeNotifier {
List<CatModel> _cats = [];
bool _isLoading = false;
String? _error;
int _currentPage = 1;
List<CatModel> get cats => _cats;
bool get isLoading => _isLoading;
String? get error => _error;
int get currentPage => _currentPage;
// 获取猫咪图片
Future<void> fetchCats({
int limit = 10,
bool hasBreeds = false,
bool loadMore = false,
}) async {
if (!loadMore) {
_currentPage = 1;
}
_isLoading = true;
_error = null;
notifyListeners();
try {
final newCats = await CatApi.getCatImages(
limit: limit,
page: _currentPage,
hasBreeds: hasBreeds,
);
if (loadMore) {
_cats.addAll(newCats);
} else {
_cats = newCats;
}
_currentPage++;
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
// 刷新数据
Future<void> refresh() async {
await fetchCats(limit: _cats.length);
}
// 清空数据
void clear() {
_cats = [];
_error = null;
_currentPage = 1;
notifyListeners();
}
}
2.3.9 App入口主页
文件:lib/pages/Main/index.dart
作用:App的入口主页,用户从这里选择进入猫咪图库
代码如下:
import 'package:flutter/material.dart';
class MainPage extends StatelessWidget {
const MainPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Qing Mall'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'欢迎使用 Qing Mall',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 40),
// 猫咪图库入口
SizedBox(
width: 200,
child: ElevatedButton.icon(
onPressed: () {
Navigator.pushNamed(context, '/cats');
},
icon: const Icon(Icons.pets),
label: const Text('猫咪图库'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
const SizedBox(height: 16),
// 登录入口
SizedBox(
width: 200,
child: OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, '/login');
},
child: const Text('用户登录'),
),
),
],
),
),
);
}
}
2.3.10 登录页面
文件:lib/pages/Login/index.dart
作用:登录页面的空壳子,有页面结构但需要填充具体登录功能
代码如下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('登录'),),
body: Center(child: Text('登录页面'),),
);
}
}
2.3.11 主程序入口文件
文件:lib/main.dart
作用:应用启动入口,初始化
代码如下:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qing_mall/routes/index.dart';
import 'package:qing_mall/stores/cat_store.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CatStore()),
// 可以添加更多Provider
],
child: getRootWidget(),
),
);
}
2.4 运行项目
2.4.1 鸿蒙端


2.4.2 安卓端

三、总结与展望
3.1 遇到的问题
3.1.1 Git 提交代码时的身份认证错误
详见1.3.2与1.3.3。
3.1.2 Flutter项目在 Android Studio中无模拟器可选
在 Android Studio中开发 Flutter 项目时,会遇到设备选择列表里找不到 Android 模拟器的情况 ,如图所示,无法选择 Android 模拟器进行调试运行。

原因在于项目Android SDK未正确配置。AS 构建 Flutter 项目运行环境时,若缺失 Android SDK 相关配置,无法识别、关联 Android 模拟器,进而导致模拟器无法在设备列表中显示。
解决办法:
- 顶部菜单栏的 “File” ,选择 “Project Structure...,然后会弹出一个窗口。
- 点击Project Settings 分类下的Project,右侧会有“sdk” 选项,点击其下拉框,选择已安装的 Android SDK(需确保本地已正确安装对应 Android SDK)。
- 配置完成后,点击 Apply应用设置,再点击 OK。
- 回到 主界面,在设备选择区域Refresh一下,选择对应的模拟器即可运行Flutter 项目。
3.1.3 模拟器显示网络连接超时

解决办法:
- 在模拟器中,长按已连接的 WiFi,进入 网络和互联网 → Wi-Fi;
- 点击 WiFi 旁的齿轮图标,进入详情页,选择 Forget(忘记网络);
- 若仍异常,重启模拟器或检查本地防火墙是否拦截了模拟器网络请求。


3.2 展望
本文完成了 Flutter 鸿蒙项目从初始化、Git 版本管理到 Dio 网络请求封装、UI 渲染的实现,内容包括:
- 按功能分层设计,提升代码可维护性与扩展性;
- 单例 Dio 工具类+异常统一处理,适配多场景请求;
- 同时支持鸿蒙、安卓端运行,体现 Flutter 跨平台优势;
- 针对性解决开发中常见的 Git、模拟器、网络问题。
后续可扩展方向:
- 完善登录功能:添加账号密码验证、Token 存储与请求拦截器;
- 扩展猫咪图片功能:支持图片保存、分享、收藏等;
- 优化状态管理:结合 GetX 或 Bloc,提升复杂场景下的状态同步效率;
- 添加缓存机制:使用 SharedPreferences 缓存常用数据,减少网络请求。
最后,欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)