dio是一個強大的Dart Http請求庫攻柠,支持Restful API瑰钮、FormData浪谴、攔截器、請求取消篇恒、Cookie管理凶杖、文件上傳/下載智蝠、超時杈湾、自定義適配器等...
網(wǎng)址在右邊 → dio
一.引入插件
在 pubspec.yaml 文件下新增 dio(注意空格問題)
dependencies:
dio: ^4.0.6
二. 封裝DIO
1.創(chuàng)建DioClient單例模式,實現(xiàn)訪問方法
// 必須是頂層函數(shù)
_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(
//設(shè)定一些基礎(chǔ)的東西
connectTimeout: 60*1000,//連接超時間
receiveTimeout: 60*1000,//接收超時時間
//除了在這里定義還可以到攔截器中定義
);
//處理訪問前的攔截器
interceptors.add(OptionInterceptor());
//處理回來的數(shù)據(jù)
interceptors.add(RequestInterceptor());
//代理抓包(開發(fā)階段可能用到,正式上線建議關(guān)閉)
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的請求流程是 請求攔截器 >> 請求轉(zhuǎn)換器 >> 發(fā)起請求 >> 響應(yīng)轉(zhuǎn)換器 >> 響應(yīng)攔截器 >> 最終結(jié)果殴泰。
請求攔截器
//Option攔截器可以用來統(tǒng)一處理Option信息 可以在這里添加
class OptionInterceptor extends InterceptorsWrapper {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
//在請求發(fā)起前修改頭部
// options.headers["token"] = "11111";
///請求的Content-Type悍汛,默認值是"application/json; charset=utf-8".
/// 如果您想以"application/x-www-form-urlencoded"格式編碼請求數(shù)據(jù),
options.contentType=Headers.formUrlEncodedContentType;
///如果你的headers是固定的你可以在BaseOption中設(shè)置,如果不固定可以在這里進行根據(jù)條件設(shè)置
options.headers["apiToken"] = "111222154546";
options.headers["user-token"]=CacheUtil().getJson(SPName.userInfo)!["userToken"];
String? mainUrl = CacheUtil().get<String>(SPName.mainUrl);
//修改地址
//如果需要改變主地址可以在這里設(shè)置
if (StringUtil.isNotEmpty(mainUrl)) {
options.baseUrl = mainUrl!;
} else {
options.baseUrl = NetworkConfig.baseUrl;
}
//開發(fā)階段可以把地址帶參數(shù)打印出來方便校驗結(jié)果
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;
}
響應(yīng)攔截器
這一部分需要和實際相結(jié)合,根據(jù)每個后端返回的數(shù)據(jù)不同靈活配置
///攔截器 數(shù)據(jù)初步處理
class RequestInterceptor extends InterceptorsWrapper {
//請求后 成功走這里
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
EasyLoading.dismiss();
if (response.statusCode == 200) {
//訪問正確有返回值的情況
if (response.data is Map) {
//將數(shù)據(jù)脫殼需要返回自己的數(shù)據(jù)
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去統(tǒng)一處理
void throwError(Response<dynamic> response) {
throw DioError(
requestOptions: response.requestOptions,
error: ResponseException(errCode: response.statusCode));
}
}
///鑒權(quán)錯誤
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 = "網(wǎng)絡(luò)離家出走了~";
//判斷一下拋出的異常是不是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代碼都會走到這里 如果想詳細展示具體是什么錯誤可以繼續(xù)細分
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 = "網(wǎng)絡(luò)無連接,請檢查網(wǎng)絡(luò)設(shè)置";
} else {
errorType = StateErrorType.defaultError;
errMsg = "網(wǎng)絡(luò)無連接,請檢查網(wǎng)絡(luò)設(shè)置";
}
}
} else {
errorType = StateErrorType.defaultError;
errMsg = "出問題了~~~";
}
return ErrorMessageModel(
errorType: errorType, message: errMsg, errorCode: errorCode);
}
將獲取到的狀態(tài)碼轉(zhuǎn)成中文提示
///獲取到的數(shù)值轉(zhuǎn)換成文字
static String _getNumberMeans(DioError e) {
String str;
if (e.response?.statusCode != null) {
switch (e.response?.statusCode) {
case 400:
str = "[${e.response?.statusCode}] 參數(shù)有誤";
break;
case 402:
str = "[${e.response?.statusCode}] 啊 這是一個非法請求呢";
break;
case 403:
str = "[${e.response?.statusCode}] 服務(wù)器拒絕請求";
break;
case 404:
str = "[${e.response?.statusCode}] 訪問地址不存在";
break;
case 405:
str = "[${e.response?.statusCode}] 請求方式錯誤";
break;
case 500:
str = "[${e.response?.statusCode}] 服務(wù)器內(nèi)部出錯了";
break;
case 502:
str = "[${e.response?.statusCode}] 無效的請求哦";
break;
case 503:
str = "[${e.response?.statusCode}] 服務(wù)器說他在忙";
break;
case 505:
str = "[${e.response?.statusCode}] 不支持的HTTP協(xié)議";
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 根據(jù)錯誤不同提示不同頁面即可
三 使用
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;
}