本文已参与「新人创作礼」活动,一起开启掘金创作之路。
大家好,我是小黑,一个还没秃头的程序员~~~
人生的路无需苛求,只要你迈步,路就在你的脚下延伸。
今天分享的内容是Flutter中关于网络数据的请求--Http/Dio的使用,源码地址:gitee.com/fjjxxy/flut…,效果如下:
不管是开发pc端还是移动端,都免不了请求服务器接口,今天介绍的就是两种网络访问的库,访问pub.dev/搜索即可,数据接口使用的是玩Android 开放API
- Http
- Dio
(一)Http的使用
1.添加依赖,在pubspec.yaml文件中配置库的依赖
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: ^0.1.3
http: ^0.13.3
2.引入
import 'package:http/http.dart' as http;
3.get请求
get方法的参数如下:
| 参数 | 说明 |
|---|---|
| url | 访问路径 |
| headers | 请求头 |
以下是按钮点击进行get请求,代码如下:
TextButton(
onPressed:() async {
var url = Uri.parse(Api.MP_WECHAT_NAMES);
var response = await http.get(url,headers: {"token":""});
Toast.toast(context,
msg: jsonDecode(response.body.toString())["errorCode"] == 0
? "请求成功"
: "请求失败");
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white)),
child: Text("get请求"),
)
注:返回的数据是Json字符串,需要使用jsonDecode转换成Map
4.post请求
post方法的参数如下:
| 参数 | 说明 |
|---|---|
| url | 访问路径 |
| headers | 请求头 |
| body | 请求参数 |
| encoding | 编码格式 |
这里按钮点击之后请求登录的接口,代码如下:
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white)),
onPressed: () async {
var url = Uri.parse(Api.LOGIN);
var response = await http.post(url,
body: {"username": "xiaohei", "password": "123456"});
Toast.toast(context,
msg: jsonDecode(response.body.toString())["errorCode"] == 0
? "请求成功"
: "请求失败");
},
child: Text("post请求"),
)
Http的使用就介绍到这里,接下来我们介绍Dio的使用以及封装
(二)Dio的使用
dio是一个自主的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义延迟等...
1.添加依赖
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: ^0.1.3
dio: ^4.0.0
2.引入
import 'package:dio/dio.dart';
3.get请求
get方法的参数如下:
| 参数 | 说明 |
|---|---|
| path | 请求路径 |
| queryParameters | 请求体参数,类型是Map |
| options | 可进行一些值的配置,比如连接超时时间,基本路径等 |
| cancelToken | 用于取消请求,类型是CancelToken,同一个cancelToken的请求会都被取消掉 |
| onReceiveProgress | 接收进度的监听 |
简单使用的代码如下:
response = await dio.get(url,
queryParameters: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
4.post请求
post方法的参数如下:
| 参数 | 说明 |
|---|---|
| path | 请求路径 |
| data | 请求体参数,类型是Map |
| queryParameters | 请求体参数,类型是Map |
| options | 可进行一些值的配置,比如连接超时时间,基本路径等 |
| cancelToken | 用于取消请求,类型是CancelToken,同一个cancelToken的请求会都被取消掉 |
| onReceiveProgress | 接收进度的监听 |
| onSendProgress | 上传进度的监听 |
基本使用的代码如下:
response = await dio.post(url,
data: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
5.上传多个文件
上传文件用的也是post方法,基本使用的代码如下:onSendProgress是上传进度的监听
uploadFiles(String url, List<String> list) async {
var formData = FormData.fromMap({
'files': list.map((e) {
return MultipartFile.fromFileSync(e,
filename: e.substring(e.indexOf("/")));
})
});
var response = await dio.post(url, data: formData,onSendProgress: (int sent, int total){
print('$sent $total');
});
return response.data;
}
上面使用FormData.fromMap创建文件数组会为key添加"[]",即文件数组的key为"files[]",这就是服务器端接收所判断的key,要想不自动加"[]",可以通过往FormData中添加MapEntry创建文件数组
uploadFiles2(String url, List<String> list,onSuccess, onError) async {
var formData = FormData();
list.map((e) {
formData.files.add(MapEntry(
'files',
MultipartFile.fromFileSync('./example/upload.txt',
filename: 'upload.txt'),
));
});
var response = await dio.post(url, data: formData,onSendProgress: (int sent, int total){
print('$sent $total');
});
return response.data;
}
6.下载文件
下载文件使用的是Dio的download方法,部分参数如下:
| 参数 | 说明 |
|---|---|
| urlPath | 下载的路径 |
| savePath | 保存的路径 |
| onReceiveProgress | 下载进度的监听 |
基本的使用代码如下:
downloadFile(urlPath, savePath, onReceiveProgress) async {
Response response;
try {
response = await dio.download(urlPath, savePath,
onReceiveProgress: onReceiveProgress);
} on DioError catch (e) {
//具体的错误类型可以自己处理
print(e.message);
}
return response.data;
}
Dio还提供了以下几个Api:
- Future put(...)
- Future delete(...)
- Future head(...)
- Future put(...)
- Future path(...)
- Future fetch(RequestOptions)
篇幅原因大家可以举一反三自己试试,以上那些Api都是一些Restful API,都是Future request() Api的别名
7.讲完了Api, 再介绍一下Dio怎么设置拦截器和公共配置的
已打印出请求信息为例,先定义一个拦截器,代码如下:
static InterceptorsWrapper interceptorsWrapper() {
return InterceptorsWrapper(onRequest: (options, handler) {
// Do something before request is sent
print(options.path);
print(options.queryParameters);
print(options.headers.toString());
return handler.next(options); //continue
}, onResponse: (response, handler) {
// Do something with response data
return handler.next(response); // continue
}, onError: (DioError e, handler) {
// Do something with response error
return handler.next(e); //continue
});
}
添加拦截器,代码如下:
mDio.interceptors.add(interceptorsWrapper());
添加公共配置,BaseOptions用来配置公共配置,而上面所列举出来的Api中的options参数是用来单独配置每次请求时的配置的,单独的配置可以覆盖公共配置,这里配置一些时间和默认的请求头,添加拦截器和配置的完整代码如下:
static Dio getDio() {
options = BaseOptions(
//请求基地址,可以包含子路径
baseUrl: Api.BASE_URL,
//连接服务器超时时间,单位是毫秒.
connectTimeout: 5000,
//2.x中为接收数据的最长时限
receiveTimeout: 3000,
//Http请求头.
headers: {"token": ""},
///// 请求的Content-Type,默认值是"application/json; charset=utf-8".
// /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
// /// 可以设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio]
// /// 就会自动编码请求体.
contentType: Headers.jsonContentType,
/// [responseType] 表示期望以那种格式(方式)接受响应数据。
/// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
///
/// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。
/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.
///
/// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.
responseType: ResponseType.json,
);
mDio = Dio(options);
mDio.interceptors.add(interceptorsWrapper());
return mDio;
}
8.别忘了还要统一管理接口路径,代码如下:
class Api {
static final String BASE_URL = "https://www.wanandroid.com/";
static final String MP_WECHAT_NAMES = BASE_URL + "wxarticle/chapters/json";
static final String LOGIN = BASE_URL + "user/login";
}
(三)Dio的封装
上面Dio介绍的所有的Api都会返回一个Map类型或者字符串类型的response,但是每次都有自己去解析就很费事了,所以这里的封装就是为了将返回的数据自动转成自己想要的类型,方便直接调用里面的字段进行界面绘制,也要让这个请求自动进行成功回调以及失败回调。
1.json解析成dart class类(实体类)
这里的解析使用到的是FlutterJsonBeanFactory插件,在AndroidStudio-Settings-Plugins-Marketplace中下载插件后,在你想要创建文件的文件夹上右键-new-JsonToDartBeanAction,输入json数据和类名,点击"make",即可创建具有相应字段的类,如下图所示:
创建一个最外层的实体类,里面的data定义为泛型,可以将数据放进泛型中,最终解析成自己想要的实体类型,后面就方便调用字段了,BaseBean的代码如下:
class BaseBean<T> {
T data;
int errorCode;
String errorMsg;
BaseBean({this.data, this.errorCode, this.errorMsg});
BaseBean.fromJson(Map<String, dynamic> json) {
if (json['data'] != null && json['data'] != 'null') {
data = JsonConvert.fromJsonAsT<T>(json['data']);
}
errorCode = json['errorCode'];
errorMsg = json['errorMsg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.data != null) {
data['data'] = this.data;
}
data['errorCode'] = this.errorCode;
data['errorMsg'] = this.errorMsg;
print(data is Map);
return data;
}
}
泛型解析的代码如下:
if (response.statusCode == 200) {
/// 将后台的data字段转成自己想要的数据/数据集,code根据后端实际返回进行判断访问结果
BaseBean<T> bean = BaseBean.fromJson(response.data);
if (bean.errorCode == 0 && onSuccess != null) {
/// 返回BaseBean,里面的data是自己想要的数据
onSuccess(bean);
} else {
onError(bean.errorMsg);
}
} else {
throw Exception('${response.statusCode}+${response.statusMessage}');
}
BaseBean.fromJson(Map<String, dynamic> json) {
if (json['data'] != null && json['data'] != 'null') {
data = JsonConvert.fromJsonAsT<T>(json['data']);
}
errorCode = json['errorCode'];
errorMsg = json['errorMsg'];
}
2.泛型解析介绍完了,封装工具类中其他的代码根据上面讲的那些Api的内容,相信大家都看得懂,这里就不再详细说明了,封装类HttpHelper 的完整代码如下:
import 'package:dio/dio.dart';
import 'Api.dart';
import 'BaseBean.dart';
class HttpHelper {
static Dio mDio;
static BaseOptions options;
static HttpHelper httpHelper;
CancelToken cancelToken = CancelToken();
static const String GET = 'get';
static const String POST = 'post';
static const String PUT = 'put';
static const String PATCH = 'patch';
static const String DELETE = 'delete';
static HttpHelper get instance => getInstance();
static Dio get dio => getDio();
static HttpHelper getInstance() {
if (null == httpHelper) httpHelper = HttpHelper();
return httpHelper;
}
static Dio getDio() {
options = BaseOptions(
//请求基地址,可以包含子路径
baseUrl: Api.BASE_URL,
//连接服务器超时时间,单位是毫秒.
connectTimeout: 10000,
//2.x中为接收数据的最长时限
receiveTimeout: 5000,
//Http请求头.
headers: {"token": ""},
///// 请求的Content-Type,默认值是"application/json; charset=utf-8".
// /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
// /// 可以设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio]
// /// 就会自动编码请求体.
contentType: Headers.jsonContentType,
/// [responseType] 表示期望以那种格式(方式)接受响应数据。
/// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
///
/// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。
/// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.
///
/// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.
responseType: ResponseType.json,
);
mDio = Dio(options);
mDio.interceptors.add(interceptorsWrapper());
return mDio;
}
static InterceptorsWrapper interceptorsWrapper() {
return InterceptorsWrapper(onRequest: (options, handler) {
// Do something before request is sent
print(options.path);
print(options.queryParameters);
print(options.headers.toString());
return handler.next(options); //continue
// 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。
// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
//
// 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,如`handler.reject(error)`,
// 这样请求将被中止并触发异常,上层catchError会被调用。
}, onResponse: (response, handler) {
// Do something with response data
return handler.next(response); // continue
// 如果你想终止请求并触发一个错误,你可以 reject 一个`DioError`对象,如`handler.reject(error)`,
// 这样请求将被中止并触发异常,上层catchError会被调用。
}, onError: (DioError e, handler) {
// Do something with response error
return handler.next(e); //continue
// 如果你想完成请求并返回一些自定义数据,可以resolve 一个`Response`,如`handler.resolve(response)`。
// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
});
}
///Get请求
void getHttp<T>(
String url, {
parameters,
cancelToken,
Function(BaseBean<T> t) onSuccess,
Function(String error) onError,
}) async {
try {
getResponse<T>(url,
method: GET,
cancelToken: cancelToken,
parameters: parameters,
onSuccess: onSuccess,
onError: onError);
} catch (e) {
print(e);
}
}
void postHttp<T>(
String url, {
String method,
parameters,
Function(BaseBean t) onSuccess,
Function(String error) onError,
}) async {
///定义请求参数
parameters = parameters ?? Map<String, dynamic>();
getResponse<T>(url,
method: method ?? POST,
parameters: parameters,
onSuccess: onSuccess,
onError: onError);
}
/*
* 下载文件
*/
downloadFile(urlPath, savePath, onReceiveProgress) async {
Response response;
try {
response = await dio.download(urlPath, savePath,
onReceiveProgress: onReceiveProgress);
} on DioError catch (e) {
formatError(e);
}
return response.data;
}
/*
* 上传多个文件,key为files[]
* v3.0.0 以后通过Formdata.fromMap()创建的Formdata,如果有文件数组,是默认会给key加上“[]”的
*/
uploadFiles(String url, List<String> list, onSuccess, onError) async {
var formData = FormData.fromMap({
'files': list.map((e) {
return MultipartFile.fromFileSync(e,
filename: e.substring(e.indexOf("/")));
})
});
getResponse(url,
method: POST,
parameters: formData,
onSuccess: onSuccess,
onError: onError);
}
uploadFiles2(String url, List<String> list, onSuccess, onError) async {
var formData = FormData();
list.map((e) {
formData.files.add(MapEntry(
'files',
MultipartFile.fromFileSync(e,
filename: e.substring(e.indexOf("/")))));
});
getResponse(url,
method: POST,
parameters: formData,
onSuccess: onSuccess,
onError: onError);
}
void getResponse<T>(
String url, {
String method,
parameters,
cancelToken,
Function(BaseBean<T> t) onSuccess,
Function(String error) onError,
}) async {
try {
//这里指定response自动转成map,不指定的话有可能是String类型
Response<Map<String, dynamic>> response;
switch (method) {
case GET:
response = await dio.get(url,
queryParameters: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
break;
case PUT:
response = await dio.put(url,
queryParameters: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
break;
case PATCH:
response = await dio.patch(url,
queryParameters: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
break;
case DELETE:
response = await dio.delete(url,
queryParameters: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
break;
default:
response = await dio.post(url,
data: parameters ?? new Map<String, dynamic>(),
cancelToken: cancelToken);
break;
}
//200代表网络请求成功
if (response.statusCode == 200) {
/// 将后台的data字段转成自己想要的数据/数据集,code根据后端实际返回进行判断访问结果
BaseBean<T> bean = BaseBean.fromJson(response.data);
if (bean.errorCode == 0 && onSuccess != null) {
/// 返回BaseBean,里面的data是自己想要的数据
onSuccess(bean);
} else {
onError(bean.errorMsg);
}
} else {
throw Exception('${response.statusCode}+${response.statusMessage}');
}
} catch (e) {
print(e.toString());
onError(e.toString());
}
}
void formatError(DioError e) {
print(e.message);
}
/*
* 取消请求
* 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
*/
void cancelRequests(CancelToken token) {
token.cancel("cancelled");
}
}
3.调用
get请求,注意这里data解析的类型是List<>
void getData(BuildContext context) {
CancelToken cancelToken = CancelToken();
HttpHelper.instance.getHttp<List<WxOfficeBeanEntity>>(Api.MP_WECHAT_NAMES,
cancelToken: cancelToken, onSuccess: (data) {
setState(() {
mList.clear();
mList.addAll(data.data);
Toast.toast(context, msg: "请求成功");
});
}, onError: (message) {
Toast.toast(context, msg: message);
print("onError" + message);
});
}
post请求
/// post请求
_postData(BuildContext context) {
Map<String, dynamic> map = Map();
map.putIfAbsent("username", () => "123456");
map.putIfAbsent("password", () => "123456");
HttpHelper.instance.postHttp(Api.LOGIN,
parameters: map, method: HttpHelper.POST, onSuccess: (data) {
Toast.toast(context, msg: data.toString());
}, onError: (message) {
Toast.toast(context, msg: message);
});
}
到这里,Flutter中关于Http&Dio网络请求的内容就介绍完了,多写才能熟能生巧,感兴趣的小伙伴可以下载源码看一下,希望大家可以点个Star,支持一下小白的flutter学习经历,最后,希望喜欢我文章的朋友们可以帮忙点赞、收藏、评论,也可以关注一下,如果有问题可以在评论区提出,后面我会持续更新Flutter的学习记录,与大家分享,谢谢大家的支持与阅读!