文章目录
11.3 Http请求库-dio
通过上一节介绍,我们可以发现直接使用HttpClient发起网络请求是比较麻烦的,很多事情得我们手动处理,如果再涉及到文件上传/下载、Cookie管理等就会非常繁琐。幸运的是,Dart社区有一些第三方http请求库,用它们来发起http请求将会简单的多,本节我们介绍一下目前人气较高的dio (opens new window)库。
dio是笔者维护的一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等。dio的使用方式随着其版本升级可能会发生变化,如果本节所述内容和最新dio功能有差异,请以最新的dio文档为准。
11.3.1 引入dio
引入dio:
dependencies:
dio: ^x.x.x #请使用pub上的最新版本
导入并创建dio实例:
import 'package:dio/dio.dart';
Dio dio = Dio();
接下来就可以通过 dio实例来发起网络请求了,注意,一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。
11.3.2 通过dio发起请求
发起 GET 请求 :
Response response;
response=await dio.get("/test?id=12&name=wendu")
print(response.data.toString());
对于GET请求我们可以将query参数通过对象来传递,上面的代码等同于:
response=await dio.get("/test",queryParameters:{"id":12,"name":"wendu"})
print(response);
发起一个 POST 请求:
response=await dio.post("/test",data:{"id":12,"name":"wendu"})
发起多个并发请求:
response= await Future.wait([dio.post("/info"),dio.get("/token")]);
下载文件:
response=await dio.download("https://www.google.com/",_savePath);
发送 FormData:
FormData formData = FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData)
如果发送的数据是FormData,则dio会将请求header的contentType设为“multipart/form-data”。
通过FormData上传多个文件:
FormData formData = FormData.from({
"name": "wendux",
"age": 25,
"file1": UploadFileInfo(File("./upload.txt"), "upload1.txt"),
"file2": UploadFileInfo(File("./upload.txt"), "upload2.txt"),
// 支持文件数组上传
"files": [
UploadFileInfo(File("./example/upload.txt"), "upload.txt"),
UploadFileInfo(File("./example/upload.txt"), "upload.txt")
]
});
response = await dio.post("/info", data: formData)
值得一提的是,dio内部仍然使用HttpClient发起的请求,所以代理、请求认证、证书校验等和HttpClient是相同的,我们可以在onHttpClientCreate回调中设置,例如:
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
//设置代理
client.findProxy = (uri) {
return "PROXY 192.168.1.2:8888";
};
//校验证书
httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==PEM){
return true; //证书一致,则允许发送数据
}
return false;
};
};
注意,onHttpClientCreate会在当前dio实例内部需要创建HttpClient时调用,所以通过此回调配置HttpClient会对整个dio实例生效,如果应用需要多种代理或证书校验策略,可以创建不同的dio实例来分别实现。
怎么样,是不是很简单,除了这些基本的用法,dio还支持请求配置、拦截器等,官方资料比较详细,故本书不再赘述,详情可以参考dio主页:https://github.com/flutterchina/dio 。 下一节我们将使用dio实现一个分块下载器。
11.3.3 实例
我们通过Github开放的API来请求flutterchina组织下的所有公开的开源项目,实现:
在请求阶段弹出loading
请求结束后,如果请求失败,则展示错误信息;如果成功,则将项目名称列表展示出来。
代码如下:
class _FutureBuilderRouteState extends State<FutureBuilderRoute> {
Dio _dio = Dio();
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: FutureBuilder(
future: _dio.get("https://api.github.com/orgs/flutterchina/repos"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//请求完成
if (snapshot.connectionState == ConnectionState.done) {
Response response = snapshot.data;
//发生错误
if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
//请求成功,通过项目信息构建用于显示项目名称的ListView
return ListView(
children: response.data.map<Widget>((e) =>
ListTile(title: Text(e["full_name"]))
).toList(),
);
}
//请求未完成时弹出loading
return CircularProgressIndicator();
}
),
);
}
}
实战:注意点
实战以https://api.codelife.cc/todayShici?lang=cn接口为例,大家可以直接访问该接口
接口返回格式如下(直接Android Studio的devTools可查看):
1.声明返回参数model
示例返回的model声明如下
// 返回的类字段
class PoetryData {
final String author;
final String content;
final String dynasty;
final String preface;
final String quotes;
final String reviews;
final String title;
final String translate;
final String annotation;
final String updateTime;
final String createTime;
PoetryData({
required this.author,
required this.content,
required this.dynasty,
required this.preface,
required this.quotes,
required this.reviews,
required this.title,
required this.translate,
required this.annotation,
required this.updateTime,
required this.createTime,
});
factory PoetryData.fromJson(Map<String, dynamic> json) {
return PoetryData(
author: json['author'],
content: json['content'],
dynasty: json['dynasty'],
preface: json['preface'],
quotes: json['quotes'],
reviews: json['reviews'],
title: json['title'],
translate: json['translate'],
annotation: json['annotation'],
updateTime: json['updateTime'],
createTime: json['createTime'],
);
}
}
// 我的 - 诗词API测试
class PoetryResponse {
final int code;
final String msg;
final PoetryData data;
PoetryResponse({
required this.code,
required this.msg,
required this.data,
});
factory PoetryResponse.fromJson(Map<String, dynamic> json) {
return PoetryResponse(
code: json['code'],
msg: json['msg'],
data: PoetryData.fromJson(json['data']),
);
}
}
2.请求接口
const apiUrl = 'https://api.codelife.cc/todayShici?lang=cn';
final response = await dio.get(apiUrl);
3.渲染组件
主页面的全部代码如下:
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import '/utils/platform_check.dart';
import '/components/custom/tabBarFree.dart';
import '/model/mine/poetry.dart';
import 'package:intl/intl.dart';
// 我的 - 随机诗词 - iTab诗词源
class ITabPoetryPage extends StatefulWidget {
const ITabPoetryPage({super.key});
@override
State<ITabPoetryPage> createState() => _ITabPoetryPageState();
}
class _ITabPoetryPageState extends State<ITabPoetryPage> {
PoetryResponse? myResult;
// 如果是web端,则不设置请求头,否则设置
final dio = Dio(BaseOptions(
headers: {
if(!PlatformCheck.isWeb)
'Content-Type': 'application/json',
if(!PlatformCheck.isWeb)
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}
));
@override
void initState() {
super.initState();
debugPrint('init初始化,是否为web端:${PlatformCheck.isWeb}');
getHttp();
}
Future<void> getHttp() async {
try {
// const apiUrl = 'https://v1.jinrishici.com/all.json';
const apiUrl = 'https://api.codelife.cc/todayShici?lang=cn';
final response = await dio.get(apiUrl);
debugPrint('接口返回值: $response');
setState(() {
myResult = PoetryResponse.fromJson(response.data);
});
} catch (e) {
debugPrint('报错啦啦啦啦:$e');
}
}
Widget paddingWidget(Widget child) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: child,
);
}
// 构建内容
Widget buildText(String? data) {
return data != null ? paddingWidget(Text(data)) : const Text('暂无数据');
}
// 刷新按钮
Widget refreshBtn() {
return OutlinedButton(
onPressed: getHttp,
child: const Text('今日诗词刷新')
);
}
// 格式化时间
String formatDateTime(String? dateTimeString) {
if (dateTimeString == null) return '暂无时间数据';
try {
final dateTime = DateTime.parse(dateTimeString);
final formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
return formatter.format(dateTime);
} catch (e) {
debugPrint('日期格式化错误: $e');
return '日期格式化错误';
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: FreeTabBar(
topWidget: myResult != null
? Column(
children: [
refreshBtn(),
const SizedBox(height: 10,),
// 标题
Text(myResult!.data.title),
// 朝代 - 作者
Text('${myResult!.data.dynasty} · ${myResult!.data.author}',),
// 内容
Text(myResult!.data.content),
],
) : refreshBtn(),
bottomWidget: Column(
children: [
// 创建时间
Text(formatDateTime(myResult?.data.createTime)),
],
),
tabs: const ['译文', '注释', '引言', '评语', '引言'],
children: [
// 译文
buildText(myResult?.data.translate),
// 注释
buildText(myResult?.data.annotation),
// 引言
buildText(myResult?.data.preface),
// 评语
buildText(myResult?.data.reviews),
// 引语
buildText(myResult?.data.quotes),
],
),
);
}
}
效果图如下: