Flutter 學習 之 DIO4.0 的封裝

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;
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窃这,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子祟敛,更是在濱河造成了極大的恐慌,老刑警劉巖跑揉,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件历谍,死亡現(xiàn)場離奇詭異辣垒,居然都是意外死亡勋桶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門岂丘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铜邮,你說我怎么就攤上這事松蒜。” “怎么了召娜?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵玖瘸,是天一觀的道長檀咙。 經(jīng)常有香客問我,道長蔑匣,這世上最難降的妖魔是什么裁良? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任价脾,我火速辦了婚禮,結(jié)果婚禮上彼棍,老公的妹妹穿的比我還像新娘座硕。我一直安慰自己,他們只是感情好映琳,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布萨西。 她就那樣靜靜地躺著旭旭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪源梭。 梳的紋絲不亂的頭發(fā)上废麻,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天模庐,我揣著相機與錄音,去河邊找鬼掂碱。 笑死怜姿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的疼燥。 我是一名探鬼主播社牲,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼悴了!你這毒婦竟也來了搏恤?” 一聲冷哼從身側(cè)響起违寿,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熟空,沒想到半個月后藤巢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體息罗,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡掂咒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迈喉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍刮。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挨摸,靈堂內(nèi)的尸體忽然破棺而出孩革,到底是詐尸還是另有隱情,我是刑警寧澤得运,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布膝蜈,位于F島的核電站,受9級特大地震影響熔掺,放射性物質(zhì)發(fā)生泄漏饱搏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一置逻、第九天 我趴在偏房一處隱蔽的房頂上張望推沸。 院中可真熱鬧,春花似錦券坞、人聲如沸坤学。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至压怠,卻和暖如春眠冈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菌瘫。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工蜗顽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雨让。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓雇盖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親栖忠。 傳聞我的和親對象是個殘疾皇子崔挖,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容