Flutter Dio 网络请求
本来一直看书上,但是这部分讲的比较模糊不怎么好理解,所以就准备自己整理一下关于Dio网络请求的知识点。
Dio Github地址:https://github.com/flutterchina/dio
Github上列出了大多数使用场景,先好好看好好学。
有人搬运并翻译的官方文档:
https://blog.csdn.net/mqdxiaoxiao/article/details/102859897
dio: ^3.0.9
一 Json数据实体类
类比于Android原生,我们的网络请求 如果是服务器返回JSON数据首先要有一个实体类来保存数据,Android Studio上有JAVA和Kotlin的根据Json数据生成实体类插件,当然也有Dart的生成实体类插件:
FlutterJsonBeanFactory
直接搜(直接搜“ FlutterJson”就可以)
生成实体类:
https://blog.csdn.net/yuzhiqiang_1993/article/details/88533166
二 Dio请求的基本使用
2.1 get 请求百度首页“http://www.baidu.com”,获取其内容:
这就是最简单的一个Dio使用例子
Future<String> getBaiduContent() async {
try {
Response response = await Dio().get("http://www.baidu.com");
print(response);
return response.toString();
} catch (e) {
print(e);
}
}
可以运行看一下:
百度首页
2.2 get 有Json数据的请求返回
这里我采用的是极速数据的一个免费开放api:笑话接口
get/post均可
https://api.jisuapi.com/xiaohua/text?pagenum=1&pagesize=1&sort=addtime&appkey=******
它返回的Json数据内容如下:
{
"status": 0,
"msg": "ok",
"result": {
"total": 79630,
"pagenum": 1,
"pagesize": 1,
"list": [
{
"content": "王自健在节目中调侃,对于老婆打自己这件事没有任何不满,没有任何抱怨。 这反映了一个问题,在中国: 老婆就是用来爱的, 老公就是用来打的。 中国妇女的地位真的提高了,可喜可贺!",
"addtime": "2020-03-28 03:20:02",
"url": "http://m.kaixinhui.com/detail-128412.html"
}
]
}
}
根据这个数据使用FlutterJsonBeanFactory 生成数据实体类:
实体类
创建接口请求方法:
Future<void> getJiSuJoke() async {
Dio dio = Dio();
try {
Response response = await Dio()
.get("https://api.jisuapi.com/xiaohua/text", queryParameters: {
"pagenum": 1,
"pagesize": 1,
"sort": "rand",
"appkey": "你的APPKEY"
});
print(response.data.toString());
} catch (e) {
print(e);
}
}
调用一下这个方法就可以看到请求结果了。
这里我们可以看到使用了queryParameters属性,类似于Retrofit中的@Query,将get方法“?”后边的值以map的形式传入,这样的好处是可以动态修改请求参数,灵活的修改请求方法传入的参数针对不同情况的接口调用.稍微修改一下之前的方法:
Future<void> getJiSuJoke(int pagesize) async {
Dio dio = Dio();
int pagenum=1;
Map<String,dynamic> mData = {
"pagenum": pagenum,
"pagesize": pagesize,
"sort": "rand",
"appkey": "35dc30ebaa5940ce"
};
try {
Response response = await Dio()
.get("https://api.jisuapi.com/xiaohua/text", queryParameters: mData);
print(response.data.toString());
} catch (e) {
print(e);
}
}
到这里其实我们还没有用到Json数据转化的实体类,请看:
JisuJokeEntity jokeEntity;
Future<void> getJiSuJoke(int pagesize) async {
Dio dio = Dio(); //创建dio对象
int pagenum = 1; //设置请求参数
Map<String, dynamic> mData = {
"pagenum": pagenum,
"pagesize": pagesize,
"sort": "rand",
"appkey": "你的APPKEY"
};
try {
//开始请求
Response response = await dio
.get("https://api.jisuapi.com/xiaohua/text",
queryParameters: mData);
//请求体结果response,将数据转化为实体类
jokeEntity =
JisuJokeEntity().fromJson(json.decode(response.data.toString()));
print(response);
print(jokeEntity.result.xList[0].content);
} catch (e) {
print(e);
}
}
想要在日志里看到请求过程,只需要添加打印日志拦截即可:
Dio dio = Dio(); //创建dio对象
//添加请求拦截 LogInterceptor内 想看什么将什么传入ture
dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //开启请求日志
post的用法和post传输数据官网上写的很清楚,后续补充
二 Dio的单例模式和封装
建议在项目中使用Dio单例,这样便可对同一个dio实例发起的所有请求进行一些统一的配置,比如设置公共header、请求基地址、超时时间等;
之前我们在每个请求方法中都新建了一个Dio对象,这样其实是不推荐的,因为我们需要在整个项目中统一配置添加header,或者配置BaseUrl这些,所以推荐在一个项目中只使用一个Dio对象,方便统一管理。既然是这样,这就要求我们对Dio进行封装。
这是我在网上找到了一个封装类,自己稍微修改了一下下,内置了get/post/downloadFile三个方法,待完善
import 'package:dio/dio.dart';
import 'api.dart';
class HttpUtil {
static HttpUtil instance;
Dio dio;
BaseOptions options;
CancelToken cancelToken = CancelToken();
static HttpUtil getInstance() {
if (null == instance) instance = HttpUtil();
return instance;
}
/*
* config it and create
*/
HttpUtil() {
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
options = BaseOptions(
//请求基地址,可以包含子路径
baseUrl: Api.BASE_URL,
//连接服务器超时时间,单位是毫秒.
connectTimeout: 10000,
//响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: 5000,
//Http请求头.
headers: {
//do something
"version": "1.0.0"
},
//请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体.
contentType: Headers.formUrlEncodedContentType,
//表示期望以那种格式(方式)接受响应数据。接受四种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
responseType: ResponseType.plain,
);
dio = Dio(options);
//添加日志请求拦截 显示日志
dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //开启请求日志
//Cookie管理 这个暂时不清楚
//dio.interceptors.add(CookieManager(CookieJar()));
//添加拦截器
dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) {
//print("请求之前");
// Do something before request is sent
return options; //continue
}, onResponse: (Response response) {
// print("响应之前");
// Do something with response data
return response; // continue
}, onError: (DioError e) {
// print("错误之前");
// Do something with response error
return e; //continue
}));
}
/*
* get请求
* options:单个请求自定义配置
* data:query ?后的数据
*
*/
get(url, {data, options, cancelToken}) async {
Response response;
try {
response = await dio.get(url,
queryParameters: data, options: options, cancelToken: cancelToken);
// print('get success---------${response.statusCode}');
// print('get success---------${response.data}');
// response.data; 响应体
// response.headers; 响应头
// response.request; 请求体
// response.statusCode; 状态码
} on DioError catch (e) {
print('get error---------$e');
formatError(e);
}
return response;
}
/*
* post请求
*
* formData:POST传递form表单
*/
post(url, {queryData,formData, options, cancelToken}) async {
Response response;
try {
response = await dio.post(url,data: formData,
queryParameters: queryData, options: options, cancelToken: cancelToken);
print('post success---------${response.data}');
} on DioError catch (e) {
print('post error---------$e');
formatError(e);
}
return response;
}
/*
* 下载文件
*/
downloadFile(urlPath, savePath) async {
Response response;
try {
response = await dio.download(urlPath, savePath,
onReceiveProgress: (int count, int total) {
//进度
print("$count $total");
});
print('downloadFile success---------${response.data}');
} on DioError catch (e) {
print('downloadFile error---------$e');
formatError(e);
}
return response.data;
}
/*
* error统一处理
*/
void formatError(DioError e) {
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
// It occurs when url is opened timeout.
print("连接超时");
} else if (e.type == DioErrorType.SEND_TIMEOUT) {
// It occurs when url is sent timeout.
print("请求超时");
} else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
//It occurs when receiving timeout
print("响应超时");
} else if (e.type == DioErrorType.RESPONSE) {
// When the server response, but with a incorrect status, such as 404, 503...
print("出现异常");
} else if (e.type == DioErrorType.CANCEL) {
// When the request is cancelled, dio will throw a error with this type.
print("请求取消");
} else {
//DEFAULT Default error type, Some other Error. In this case, you can read the DioError.error if it is not null.
print("未知错误");
}
}
/*
* 取消请求
*
* 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
* 所以参数可选
*/
void cancelRequests(CancelToken token) {
token.cancel("cancelled");
}
}
然后我们需要一个统一管理URL的API
class Api {
//应该根据当前的编译环境确定BASE_URL
static const String BASE_URL = "https://api.jisuapi.com/";
static const String JISUJOKE = "xiaohua/text";
}
然后就可以用了,还是刚刚的笑话接口:
Future<void> getJiSuJoke2() async {
Map<String, dynamic> mData = {
"pagenum": 1,
"pagesize": 1,
"sort": "rand",
"appkey": APPKEY
};
try {
//开始请求
var response =await HttpUtil().post(Api.JISUJOKE,
queryData: mData);
//请求体结果response,将数据转化为实体类
jokeEntity =
JisuJokeEntity().fromJson(json.decode(response.data.toString()));
//print(response);
print(jokeEntity.result.xList[0].content);
} catch (e) {
print(e);
}
}
这里有点Android MVP的那种感觉了 不过目前只是demo项目 没有完全分开,之后尝试自己搭建一套Flutter的MVP或者MVVM架构。
在封装的HttpUtil中,预留了许多空,比如请求拦截的操作呀,请求成功之后服务器的返回码判断呀,header的添加呀,各个项目都有不同所以暂时先预留空位。
HttpUtil中的请求方法中都保留了一个参数options
可以为每个请求单独配置请求参数