今天来学学flutter的网络请求

Dio插件的使用

dio 是 Flutter 里最常用的 HTTP 网络请求库之一,很多项目都会用它替代原生的 http 包,因为它功能更完整

(话说看到这个Dio我就想起了某个埃及艳妇 狗头狗头狗头)

OK回归正题

首先根目录执行

1
flutter pub add dio

打开你的pubspec.yaml文件

可以看到这个

1
2
3
4
5
6
7
8
dependencies:
flutter:
sdk: flutter

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dio: ^5.9.2 #dio下载完成

基本使用

Dio().get(地址).then().catchError()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {
runApp(MaterialApp(home: HomePage()));
}

class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
String text = "点击获取数据";

void getData() async {
Response response = await Dio().get('https://genshin.hoyoverse.com/en/');

setState(() {
text = response.data.toString();
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("原神网页"), centerTitle: true),

body: Center(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: getData,
child: Text("获取网页", style: TextStyle(fontSize: 18)),
),

SizedBox(height: 20),

Text(text, textAlign: TextAlign.center),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: getData,
child: Icon(Icons.refresh),
),
);
}
}
image-20260517163247875

封装Dio工具

image-20260614195619480

建立连接 → 发送数据 → 等待服务器返回
↑ ↑ ↑
connect send receive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {}

class DioUtils {
final Dio _dio = Dio(); // 创建Dio实例
DioUtils() {
_dio.options.baseUrl = 'https://github.com/'; // 基础地址
_dio.options.connectTimeout = Duration(seconds: 5); // 连接超时,类型为Duration
_dio.options.sendTimeout = Duration(seconds: 5); // 发送超时,类型为Duration
_dio.options.receiveTimeout = Duration(seconds: 5); // 响应超时,类型为Duration
}
}

我们可以创建Dio工具类和实例对象,但是这种写法较为繁琐

使用“..”级操作符

作用是对同一个对象连续调用多个属性或方法时,不需要反复写对象名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {}

class DioUtils {
final Dio _dio = Dio(); // 创建Dio实例
DioUtils() {

_dio.options
..baseUrl =
'https://github.com/' // 基础地址
..connectTimeout =
Duration(seconds: 5) // 连接超时,类型为Duration
..sendTimeout =
Duration(seconds: 5) // 发送超时,类型为Duration
..receiveTimeout = Duration(seconds: 5); // 响应超时,类型为Duration
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 不用级联:重复写对象名
var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(' ');
buffer.write('World');
var result = buffer.toString();

// ✅ 用级联:链式调用,更简洁
var result = (StringBuffer()
..write('Hello')
..write(' ')
..write('World'))
.toString();

使用BaseOptions

flutter 的 BaseOptionsDio 网络库中的核心配置类,用于定义全局请求的基础参数。它本身不是 Flutter SDK 的一部分,而是来自 dio

BaseOptions 封装了所有 HTTP 请求的默认配置,创建 Dio 实例时传入,后续所有请求都会继承这些基础参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {}

class DioUtils {
final Dio _dio = Dio(
BaseOptions(
baseUrl: 'https://github.com/',
connectTimeout: Duration(seconds: 5),
sendTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 5),
),
);
}

Dio拦截器(Interceptors)

拦截器是 Dio 的核心机制,允许你在请求发出前响应返回后插入自定义逻辑,实现统一的日志、鉴权、错误处理、缓存等

Flutter 里的 Dio 拦截器(Interceptors)主要用于统一处理请求、响应、错误,比如:

  • 自动添加 Token
  • 打印网络日志
  • 刷新 Token
  • 统一错误处理
  • 请求加密/解密
  • Loading 状态管理

Dio 的拦截器语法核心是继承 Interceptor,然后重写三个方法:

1
2
3
onRequest()
onResponse()
onError()

三个阶段执行顺序

一次请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
dio.get()
|

onRequest()
|

服务器
|

onResponse()
|

业务代码

如果失败:

1
2
3
4
5
6
7
8
9
10
dio.get()
|

onRequest()
|

服务器
|

onError()

基本结构

先导入:

1
import 'package:dio/dio.dart';

创建 Dio:

1
final dio = Dio();

添加拦截器:

1
2
3
dio.interceptors.add(
MyInterceptor(),
);

定义拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class MyInterceptor extends Interceptor {  

@override
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {

print("请求地址: ${options.uri}");

handler.next(options);
}


@override
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) {

print("响应数据: ${response.data}");

handler.next(response);
}


@override
void onError(
DioException err,
ErrorInterceptorHandler handler,
) {

print("请求失败: ${err.message}");

handler.next(err);
}
}

handler 三种操作

Request
1
handler.next(options);

意思: 处理完了,继续发送请求

Response
1
handler.next(response);

意思: 继续返回数据

Error
1
handler.next(err);

意思: 继续抛错误

我们先试一下在终端输出

其中onRequest() onResponse() onError()函数里的参数可以直接写参数名,无需声明类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import 'package:dio/dio.dart';

void main() async {
Dio dio = Dio();

dio.interceptors.add(
InterceptorsWrapper(
// 请求前
onRequest: (options, handler) {
print("===发送请求===");
print("请求地址: ${options.uri}");

handler.next(options);
},

// 成功返回
onResponse: (response, handler) {
print("===收到响应===");
print(response.data);

handler.next(response);
},

// 出错
onError: (error, handler) {
print("===发生错误===");
print(error.message);

handler.next(error);
},
),
);

try {
Response res = await dio.get(
'https://jsonplaceholder.typicode.com/posts/1',
);

print("===最终结果===");
print(res.data);
} catch (e) {
print(e);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {}

class DioUtils {
final Dio _dio = Dio(); // 创建Dio实例

//设置基础地址和超时时间
DioUtils() {
_dio.options
..baseUrl =
'https://jsonplaceholder.typicode.com' // 基础地址
..connectTimeout =
Duration(seconds: 5) // 连接超时,类型为Duration
..sendTimeout =
Duration(seconds: 5) // 发送超时,类型为Duration
..receiveTimeout = Duration(seconds: 5); // 响应超时,类型为Duration

// 添加请求拦截器
_addInterceptors();
}

// 添加拦截器
void _addInterceptors() {
_dio.interceptors.add(
InterceptorsWrapper(
// 请求拦截器
onRequest: (context, handler) {
handler.next(context);
},
// 响应拦截器
onResponse: (context, handler) {
//http状态码为2XX成功,3XX跳转,4XX错误,5XX服务器错误
if (context.statusCode! >= 200 && context.statusCode! < 300) {
handler.next(context);
return;
}
// 其他状态码视为错误,抛出异常
handler.reject(DioException(requestOptions: context.requestOptions));
},
// 错误拦截器
onError: (context, handler) {
handler.reject(context);
},
),
);
}

//向外暴露get方法,封装请求
Future<Response> get(String url, {Map<String, dynamic>? params}) {
return _dio.get(url, queryParameters: params);
}
}

然后与MaterialApp结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

void main() {
runApp(const MaterialApp(home: DioDemoPage()));
}

class DioDemoPage extends StatefulWidget {
const DioDemoPage({super.key});

@override
State<DioDemoPage> createState() => _DioDemoPageState();
}

class _DioDemoPageState extends State<DioDemoPage> {
final Dio dio = Dio();

String result = "点击按钮发送请求";

@override
void initState() {
super.initState();

dio.interceptors.add(
InterceptorsWrapper(
// 请求前
onRequest: (options, handler) {
print("===== 请求拦截器 =====");
print("请求地址: ${options.uri}");

handler.next(options);
},

// 请求成功
onResponse: (response, handler) {
print("===== 响应拦截器 =====");
print("状态码: ${response.statusCode}");

handler.next(response);
},

// 请求失败
onError: (error, handler) {
print("===== 错误拦截器 =====");
print(error.message);

handler.next(error);
},
),
);
}

Future<void> loadData() async {
try {
Response response = await dio.get(
"https://jsonplaceholder.typicode.com/posts/1",
);

setState(() {
result = response.data.toString();
});
} catch (e) {
setState(() {
result = e.toString();
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Dio拦截器演示"), centerTitle: true),
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
children: [
ElevatedButton(onPressed: loadData, child: const Text("发送请求")),
const SizedBox(height: 20),
Expanded(child: SingleChildScrollView(child: Text(result))),
],
),
),
),
);
}
}

初始化获取数据

我们使用测试网站https://jsonplaceholder.typicode.com/

我们可以使用如下后缀来测试数据

路径 数量
/posts 100 posts
/comments 500 comments
/albums 100 albums
/photos 5000 photos
/todos 200 todos
/users 10 users

这里我们使用/posts/1获得第一篇测试文章的数据

GET /posts
GET /posts/1
GET /posts/1/comments
GET /comments?postId=1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1

image-20260718170625979

对比一下网站内容,完全一致

image-20260718170658143

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MaterialApp(home: MainPage()));
}

class MainPage extends StatefulWidget {
const MainPage({super.key});

@override
State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
String result = '点击获取数据';
//初始化
void initState() {
//使用initState()方法初始化
super.initState();
}

void _getChannels() async {
DioUtils util = DioUtils();

try {
// 获取数据
Response<dynamic> response = await util.get('/posts/1');
setState(() {
result = response.data.toString();
});
print(response);
} catch (e) {
setState(() {
result = e.toString();
});
print(e);
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Dio')),

body: Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _getChannels, child: Text("获取数据")),
const SizedBox(height: 20),
Expanded(child: SingleChildScrollView(child: Text(result))),
],
),
),
),
);
}
}

//Dio 工具类
class DioUtils {
final Dio _dio = Dio(); // 创建Dio实例

//设置基础地址和超时时间
DioUtils() {
_dio.options
..baseUrl =
'https://jsonplaceholder.typicode.com' // 基础地址
..connectTimeout =
Duration(seconds: 5) // 连接超时,类型为Duration
// ..sendTimeout =
// Duration(seconds: 5) // 发送超时,类型为Duration
..receiveTimeout = Duration(seconds: 5); // 响应超时,类型为Duration

// 添加请求拦截器
_addInterceptors();
}

// 添加拦截器
void _addInterceptors() {
_dio.interceptors.add(
InterceptorsWrapper(
// 请求拦截器
onRequest: (context, handler) {
handler.next(context);
},
// 响应拦截器
onResponse: (context, handler) {
//http状态码为2XX成功,3XX跳转,4XX错误,5XX服务器错误
if (context.statusCode! >= 200 && context.statusCode! < 300) {
handler.next(context);
return;
}
// 其他状态码视为错误,抛出异常
handler.reject(DioException(requestOptions: context.requestOptions));
},
// 错误拦截器
onError: (context, handler) {
handler.reject(context);
},
),
);
}

//向外暴露get方法,封装请求
Future<Response<dynamic>> get(String url, {Map<String, dynamic>? params}) {
return _dio.get(url, queryParameters: params);
}
}