dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等...
网址在右边 → dio
一.引入插件
在 pubspec.yaml 文件下新增 dio(注意空格问题)
dependencies:
dio: ^4.0.6
二. 封装DIO
1.创建DioClient单例模式,实现访问方法
// 必须是顶层函数
_parseAndDecode(String response) {
return jsonDecode(response);
}
parseJson(String text) {
return compute(_parseAndDecode, text);
}
//继承DioForNavigator(详情见官方文档)
class DioClient extends DioForNative {
//单例模式
static DioClient? _instance;
factory DioClient() => _instance ??= DioClient._init();
//初始化方法
DioClient._init() {
(transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
options = BaseOptions(
//设定一些基础的东西
connectTimeout: 60*1000,//连接超时间
receiveTimeout: 60*1000,//接收超时时间
//除了在这里定义还可以到拦截器中定义
);
//处理访问前的拦截器
interceptors.add(OptionInterceptor());
//处理回来的数据
interceptors.add(RequestInterceptor());
//代理抓包(开发阶段可能用到,正式上线建议关闭)
proxy();
}
///get请求
doGet<T>(path, {queryParameters, options, cancelToken, onReceiveProgress}) {
return get<T>(path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onReceiveProgress: onReceiveProgress);
}
///post请求 为了不和继承的DioMixin里面的post方法名冲突所以起名叫doPost
doPost<T>(path,
{queryParameters,
options,
cancelToken,
onSendProgress,
onReceiveProgress}) {
return post<T>(path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress);
}
///上传文件
uploadFile(formData) {
var uploadOptions = Options(contentType: "multipart/form-data");
return doPost(Api.uploadURL, options: uploadOptions, data: formData);
}
///代理抓包测试用
void proxy() {
if (NetworkConfig.proxyEnable) {
if (NetworkConfig.caughtAddress != "") {
(httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (Uri uri) {
return 'PROXY ' + NetworkConfig.caughtAddress;
};
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
return true;
};
};
}
}
}
}
2.封装拦截器
dio的请求流程是 请求拦截器 >> 请求转换器 >> 发起请求 >> 响应转换器 >> 响应拦截器 >> 最终结果。
请求拦截器
//Option拦截器可以用来统一处理Option信息 可以在这里添加
class OptionInterceptor extends InterceptorsWrapper {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
//在请求发起前修改头部
// options.headers["token"] = "11111";
///请求的Content-Type,默认值是"application/json; charset=utf-8".
/// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
options.contentType=Headers.formUrlEncodedContentType;
///如果你的headers是固定的你可以在BaseOption中设置,如果不固定可以在这里进行根据条件设置
options.headers["apiToken"] = "111222154546";
options.headers["user-token"]=CacheUtil().getJson(SPName.userInfo)!["userToken"];
String? mainUrl = CacheUtil().get<String>(SPName.mainUrl);
//修改地址
//如果需要改变主地址可以在这里设置
if (StringUtil.isNotEmpty(mainUrl)) {
options.baseUrl = mainUrl!;
} else {
options.baseUrl = NetworkConfig.baseUrl;
}
//开发阶段可以把地址带参数打印出来方便校验结果
debugPrint(
"request:${options.method}\t url-->${options.baseUrl}${options.path}?${FormatUtil.formattedUrl(options.queryParameters)}");
if (options.queryParameters["hideLoading"] != true) {
EasyLoading.show();
}
// 一定要加上这句话 否则进入不了下一步
return handler.next(options);
}
}
///格式化url,将post和get请求以get链接输出
static String formattedUrl(params) {
var urlParamsStr = "";
if (params?.isNotEmpty ?? false) {
var tempArr = [];
params.forEach((k, v) {
tempArr.add(k + '=' + v.toString());
});
urlParamsStr = tempArr.join('&');
}
return urlParamsStr;
}
响应拦截器
这一部分需要和实际相结合,根据每个后端返回的数据不同灵活配置
///拦截器 数据初步处理
class RequestInterceptor extends InterceptorsWrapper {
//请求后 成功走这里
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
EasyLoading.dismiss();
if (response.statusCode == 200) {
//访问正确有返回值的情况
if (response.data is Map) {
//将数据脱壳需要返回自己的数据
ResponseData responseData = ResponseData.fromJson(response.data);
if (responseData.success) {
response.data = responseData.data;
response.statusCode = responseData.respCode;
response.statusMessage = responseData.respDesc;
return handler.resolve(response);
}
return handler.resolve(response);
} else if (response.data is String) {
// {"respCode":403,"respDesc":"非法访问"}
ResponseError model = ResponseError.fromJson(jsonDecode(response.data));
response.statusCode = model.respCode;
if (model.respCode == 403 || model.respCode == 402) {
//做些什么
throwUnauthorizedError(response);
}else{
throwError(response);
}
} else {
throwError(response);
}
} else {
throwError(response);
}
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
EasyLoading.dismiss();
throw DioError(
requestOptions: err.requestOptions,
type: err.type,
error: err,
response: err.response);
}
///抛出异常 留给baseModel去统一处理
void throwError(Response<dynamic> response) {
throw DioError(
requestOptions: response.requestOptions,
error: ResponseException(errCode: response.statusCode));
}
}
///鉴权错误
void throwUnauthorizedError(Response<dynamic> response) {
throw DioError(
requestOptions: response.requestOptions,
error: UnauthorizedError(errCode: response.statusCode));
}
上述中用到的类
abstract class BaseResponseData{
int? respCode;
String? respDesc;
dynamic attribute;
dynamic data;
bool get success;
BaseResponseData({this.respCode, this.respDesc, this.attribute, this.data});
@override
String toString() {
return 'BaseRespData{code: $respCode, message: $respDesc, data: $attribute}';
}
}
class ResponseData extends BaseResponseData {
@override
bool get success => respCode != null || data != null;
ResponseData.fromJson(Map<String, dynamic> json) {
if (json['respCode'] != null && json['respCode'] is String) {
json['respCode'] = int.parse(json['respCode']);
}
respCode = json['respCode'] ?? json['code'];
respDesc = json['respDesc'] ?? json['message'] ?? json['msg'];
attribute = json['attribute'] ?? json["data"];
if (attribute != null) {
if (attribute is Map && attribute.containsKey("data")) {
data = attribute['data'];
} else {
data = attribute;
}
} else {
data = json;
}
}
}
class ResponseError extends BaseResponseData {
ResponseError.fromJson(Map<String, dynamic> json) {
respDesc = json["respDesc"];
respCode = json["respCode"];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = {};
data["respDesc"] = respDesc;
data["respCode"] = respCode;
return data;
}
@override
// TODO: implement success
bool get success => false;
}
class ResponseException implements Exception {
int? errCode;
String? errMsg;
ResponseException({this.errCode});
int? get errorCode => errCode;
//statusCode==200时候返回的data中存在的respCode
String? get errorMessage {
String msg = errMsg ?? "";
switch (errCode) {
default:
}
return msg;
}
@override
String toString() {
return 'RequestException{errorCode: $errorCode, errorMessage: $errorMessage}';
}
}
捕获错误并提示
DioErrorType 分六种 connectTimeout,sendTimeout,receiveTimeout,response,cancel,other,
其实加上刚才我们自定义type 总共可以分成四类 超时的 返回错误的 取消的 和其他
///格式化Dio返回的Error
///[e] catch到的error
static ErrorMessageModel dioErrorFormat(e) {
int? errorCode;
StateErrorType errorType = StateErrorType.defaultError;
String errMsg = "网络离家出走了~";
//判断一下抛出的异常是不是DIO包裹的异常
if (e is DioError) {
//是不是各种超时
if (e.type == DioErrorType.receiveTimeout ||
e.type == DioErrorType.sendTimeout ||
e.type == DioErrorType.receiveTimeout) {
errorType = StateErrorType.networkTimeoutError;
errMsg = "连接超时了";
} else if (e.type == DioErrorType.response) {
//访问出的各种错 访问中statusCode是400/500代码都会走到这里 如果想详细展示具体是什么错误可以继续细分
errorType = StateErrorType.responseException;
errMsg = _getNumberMeans(e);
} else if (e.type == DioErrorType.cancel) {
//如果是取消访问了走这里
errMsg = e.message;
} else {
//这里是刚才DIOerror包裹的自定义错误
// 这里由于没有定义error.type所以用error的类型判断
dynamic otherError = e.error;
dynamic otherE;
if (otherError is DioError) {
otherE = otherError.error;
}
if (otherE is ResponseException) {
errorType = StateErrorType.responseException;
errMsg = otherE.errorMessage ?? "";
errorCode = otherE.errorCode;
} else if (otherE is SocketException) {
errorType = StateErrorType.networkTimeoutError;
errMsg = "网络无连接,请检查网络设置";
} else {
errorType = StateErrorType.defaultError;
errMsg = "网络无连接,请检查网络设置";
}
}
} else {
errorType = StateErrorType.defaultError;
errMsg = "出问题了~~~";
}
return ErrorMessageModel(
errorType: errorType, message: errMsg, errorCode: errorCode);
}
将获取到的状态码转成中文提示
///获取到的数值转换成文字
static String _getNumberMeans(DioError e) {
String str;
if (e.response?.statusCode != null) {
switch (e.response?.statusCode) {
case 400:
str = "[${e.response?.statusCode}] 参数有误";
break;
case 402:
str = "[${e.response?.statusCode}] 啊 这是一个非法请求呢";
break;
case 403:
str = "[${e.response?.statusCode}] 服务器拒绝请求";
break;
case 404:
str = "[${e.response?.statusCode}] 访问地址不存在";
break;
case 405:
str = "[${e.response?.statusCode}] 请求方式错误";
break;
case 500:
str = "[${e.response?.statusCode}] 服务器内部出错了";
break;
case 502:
str = "[${e.response?.statusCode}] 无效的请求哦";
break;
case 503:
str = "[${e.response?.statusCode}] 服务器说他在忙";
break;
case 505:
str = "[${e.response?.statusCode}] 不支持的HTTP协议";
break;
default:
str = "[${e.response?.statusCode}] 未知错误";
break;
}
} else {
str = e.message;
}
return str;
}
上面用到的一个类
class ErrorMessageModel {
StateErrorType? errorType;
String? message;
int? errorCode;
ErrorMessageModel({
this.errorType = StateErrorType.defaultError,
this.message = "出错啦,请稍后重试~",
this.errorCode,
});
ErrorMessageModel.fromJson(Map<String, dynamic> json) {
errorType = json['errorType'];
message = json['message'];
errorCode = json['errorCode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {};
data['errorType'] = errorType;
data['message'] = message;
data['errorCode'] = errorCode;
return data;
}
}
配合provider 根据错误不同提示不同页面即可
三 使用
Future<LoginModel> login(Map<String, dynamic> param) async {
Response<dynamic> response =
await DioClient().doPost(Api.login, queryParameters: param);
LoginModel model =LoginModel.fromJson(response.data);
return model;
}