Flutter網(wǎng)絡(luò)請求庫Dio的封裝(單例系宜、動態(tài)baseUrl照激、攔截器、日志盹牧、請求loading)

Demo地址 https://github.com/po1arbear/Flutter-Net.git

封裝網(wǎng)絡(luò)請求的幾個好處:

  1. 便于統(tǒng)一配置請求參數(shù)俩垃,如header励幼,公共參數(shù),加密規(guī)則等
  2. 方便調(diào)試口柳,詳細(xì)的日志打印信息
  3. 優(yōu)化代碼性能苹粟,避免到處濫new對象,構(gòu)建全局單例
  4. 簡化請求步驟跃闹,只暴露需要的響應(yīng)數(shù)據(jù)嵌削,而對錯誤的響應(yīng)統(tǒng)一回調(diào)
  5. 對接口數(shù)據(jù)的基類封裝,簡化解析流程
  6. 無侵入的望艺,靈活的請求loading配置

請求loading自動化

只需要傳遞一個參數(shù)苛秕,就可以為請求加上Loading效果,沒有任何的代碼入侵

  var params = DataHelper.getBaseMap();
    params.clear();
    params["apikey"] = "0df993c66c0c636e29ecbb5344252a4a";
    params["start"] = "0";
    params["count"] = "10";
//withLoading也可以省略找默,默認(rèn)就加上艇劫,會更簡潔
    ResultData res = await HttpManager.getInstance()
        .get(Address.TEST_API, params: params, withLoading: true);
請求時loading

清晰全面的日志打印

再也不需要額外地配置抓包了,接口調(diào)試效率大大提升


image.png

下面通過關(guān)鍵源碼介紹下封裝過程

HttpManager的定義

構(gòu)造全局單例惩激,配置請求參數(shù)店煞,配置通用的GET\POST,支持baseUrl的切換

import 'package:dio/dio.dart';
import 'package:flutter_net/code.dart';
import 'package:flutter_net/dio_log_interceptor.dart';
import 'package:flutter_net/loading_utils.dart';
import 'response_interceptor.dart';
import 'result_data.dart';
import 'address.dart';

class HttpManager {
  static HttpManager _instance = HttpManager._internal();
  Dio _dio;

  static const CODE_SUCCESS = 200;
  static const CODE_TIME_OUT = -1;

  factory HttpManager() => _instance;

  ///通用全局單例风钻,第一次使用時初始化
  HttpManager._internal({String baseUrl}) {
    if (null == _dio) {
      _dio = new Dio(
          new BaseOptions(baseUrl: Address.BASE_URL, connectTimeout: 15000));
      _dio.interceptors.add(new DioLogInterceptor());
//      _dio.interceptors.add(new PrettyDioLogger());
      _dio.interceptors.add(new ResponseInterceptors());
    }
  }

  static HttpManager getInstance({String baseUrl}) {
    if (baseUrl == null) {
      return _instance._normal();
    } else {
      return _instance._baseUrl(baseUrl);
    }
  }

  //用于指定特定域名
  HttpManager _baseUrl(String baseUrl) {
    if (_dio != null) {
      _dio.options.baseUrl = baseUrl;
    }
    return this;
  }

  //一般請求顷蟀,默認(rèn)域名
  HttpManager _normal() {
    if (_dio != null) {
      if (_dio.options.baseUrl != Address.BASE_URL) {
        _dio.options.baseUrl = Address.BASE_URL;
      }
    }
    return this;
  }

  ///通用的GET請求
  get(api, {params, withLoading = true}) async {
    if (withLoading) {
      LoadingUtils.show();
    }

    Response response;
    try {
      response = await _dio.get(api, queryParameters: params);
      if (withLoading) {
        LoadingUtils.dismiss();
      }
    } on DioError catch (e) {
      if (withLoading) {
        LoadingUtils.dismiss();
      }
      return resultError(e);
    }

    if (response.data is DioError) {
      return resultError(response.data['code']);
    }

    return response.data;
  }

  ///通用的POST請求
  post(api, {params, withLoading = true}) async {
    if (withLoading) {
      LoadingUtils.show();
    }

    Response response;

    try {
      response = await _dio.post(api, data: params);
      if (withLoading) {
        LoadingUtils.dismiss();
      }
    } on DioError catch (e) {
      if (withLoading) {
        LoadingUtils.dismiss();
      }
      return resultError(e);
    }

    if (response.data is DioError) {
      return resultError(response.data['code']);
    }

    return response.data;
  }
}

ResultData resultError(DioError e) {
  Response errorResponse;
  if (e.response != null) {
    errorResponse = e.response;
  } else {
    errorResponse = new Response(statusCode: 666);
  }
  if (e.type == DioErrorType.CONNECT_TIMEOUT ||
      e.type == DioErrorType.RECEIVE_TIMEOUT) {
    errorResponse.statusCode = Code.NETWORK_TIMEOUT;
  }
  return new ResultData(
      errorResponse.statusMessage, false, errorResponse.statusCode);
}

響應(yīng)基類

默認(rèn)200的情況isSuccess為true,響應(yīng)為response.data,賦值給data

class ResultData {
  var data;
  bool isSuccess;
  int code;
  var headers;

  ResultData(this.data, this.isSuccess, this.code, {this.headers});
}

Api的封裝

請求的集中管理

class Api {
  ///示例請求
  static request(String param) {
    var params = DataHelper.getBaseMap();
    params['param'] = param;
    return HttpManager.getInstance().get(Address.TEST_API, params);
  }
}

公共參數(shù)和加密等

class DataHelper{
  static SplayTreeMap getBaseMap() {
    var map = new SplayTreeMap<String, dynamic>();
    map["platform"] = AppConstants.PLATFORM;
    map["system"] = AppConstants.SYSTEM;
    map["channel"] = AppConstants.CHANNEL;
    map["time"] = new DateTime.now().millisecondsSinceEpoch.toString();
    return map;
  }
  
  static string2MD5(String data) {
    var content = new Utf8Encoder().convert(data);
    var digest = md5.convert(content);
    return hex.encode(digest.bytes);
  }
}

地址的配置

方便地址管理

class Address {
  static const String TEST_API =  "test_api";
}

響應(yīng)攔截器

過濾正確的響應(yīng)數(shù)據(jù)骡技,對數(shù)據(jù)進行初步封裝

import 'package:dio/dio.dart';
import 'package:exchange_flutter/common/net/code.dart';
import 'package:flutter/material.dart';
import '../result_data.dart';

class ResponseInterceptors extends InterceptorsWrapper {
  @override
  onResponse(Response response) {
    RequestOptions option = response.request;
    try {
      if (option.contentType != null &&
          option.contentType.primaryType == "text") {
        return new ResultData(response.data, true, Code.SUCCESS);
      }
      ///一般只需要處理200的情況鸣个,300、400哮兰、500保留錯誤信息
      if (response.statusCode == 200 || response.statusCode == 201) {
        int code = response.data["code"];
        if (code == 0) {
          return new ResultData(response.data, true, Code.SUCCESS,
              headers: response.headers);
        } else if (code == 100006 || code == 100007) {

        } else {
          Fluttertoast.showToast(msg: response.data["msg"]);
          return new ResultData(response.data, false, Code.SUCCESS,
              headers: response.headers);
        }
      }
    } catch (e) {
      print(e.toString() + option.path);

      return new ResultData(response.data, false, response.statusCode,
          headers: response.headers);
    }

    return new ResultData(response.data, false, response.statusCode,
        headers: response.headers);
  }
}

日志攔截器

打印請求參數(shù)和返回參數(shù)


import 'package:dio/dio.dart';

///日志攔截器
class DioLogInterceptor extends Interceptor {
  @override
  Future onRequest(RequestOptions options) async {
    String requestStr = "\n==================== REQUEST ====================\n"
        "- URL:\n${options.baseUrl + options.path}\n"
        "- METHOD: ${options.method}\n";

    requestStr += "- HEADER:\n${options.headers.mapToStructureString()}\n";

    final data = options.data;
    if (data != null) {
      if (data is Map)
        requestStr += "- BODY:\n${data.mapToStructureString()}\n";
      else if (data is FormData) {
        final formDataMap = Map()
          ..addEntries(data.fields)
          ..addEntries(data.files);
        requestStr += "- BODY:\n${formDataMap.mapToStructureString()}\n";
      } else
        requestStr += "- BODY:\n${data.toString()}\n";
    }
    print(requestStr);
    return options;
  }

  @override
  Future onError(DioError err) async {
    String errorStr = "\n==================== RESPONSE ====================\n"
        "- URL:\n${err.request.baseUrl + err.request.path}\n"
        "- METHOD: ${err.request.method}\n";

    errorStr +=
        "- HEADER:\n${err.response.headers.map.mapToStructureString()}\n";
    if (err.response != null && err.response.data != null) {
      print('╔ ${err.type.toString()}');
      errorStr += "- ERROR:\n${_parseResponse(err.response)}\n";
    } else {
      errorStr += "- ERRORTYPE: ${err.type}\n";
      errorStr += "- MSG: ${err.message}\n";
    }
    print(errorStr);
    return err;
  }

  @override
  Future onResponse(Response response) async {
    String responseStr =
        "\n==================== RESPONSE ====================\n"
        "- URL:\n${response.request.uri}\n";
    responseStr += "- HEADER:\n{";
    response.headers.forEach(
        (key, list) => responseStr += "\n  " + "\"$key\" : \"$list\",");
    responseStr += "\n}\n";
    responseStr += "- STATUS: ${response.statusCode}\n";

    if (response.data != null) {
      responseStr += "- BODY:\n ${_parseResponse(response)}";
    }
    printWrapped(responseStr);
    return response;
  }

  void printWrapped(String text) {
    final pattern = new RegExp('.{1,800}'); // 800 is the size of each chunk
    pattern.allMatches(text).forEach((match) => print(match.group(0)));
  }

  String _parseResponse(Response response) {
    String responseStr = "";
    var data = response.data;
    if (data is Map)
      responseStr += data.mapToStructureString();
    else if (data is List)
      responseStr += data.listToStructureString();
    else
      responseStr += response.data.toString();

    return responseStr;
  }
}

extension Map2StringEx on Map {
  String mapToStructureString({int indentation = 2}) {
    String result = "";
    String indentationStr = " " * indentation;
    if (true) {
      result += "{";
      this.forEach((key, value) {
        if (value is Map) {
          var temp = value.mapToStructureString(indentation: indentation + 2);
          result += "\n$indentationStr" + "\"$key\" : $temp,";
        } else if (value is List) {
          result += "\n$indentationStr" +
              "\"$key\" : ${value.listToStructureString(indentation: indentation + 2)},";
        } else {
          result += "\n$indentationStr" + "\"$key\" : \"$value\",";
        }
      });
      result = result.substring(0, result.length - 1);
      result += indentation == 2 ? "\n}" : "\n${" " * (indentation - 1)}}";
    }

    return result;
  }
}

extension List2StringEx on List {
  String listToStructureString({int indentation = 2}) {
    String result = "";
    String indentationStr = " " * indentation;
    if (true) {
      result += "$indentationStr[";
      this.forEach((value) {
        if (value is Map) {
          var temp = value.mapToStructureString(indentation: indentation + 2);
          result += "\n$indentationStr" + "\"$temp\",";
        } else if (value is List) {
          result += value.listToStructureString(indentation: indentation + 2);
        } else {
          result += "\n$indentationStr" + "\"$value\",";
        }
      });
      result = result.substring(0, result.length - 1);
      result += "\n$indentationStr]";
    }

    return result;
  }
}


示例請求

dart的json解析推薦使用json_serializable毛萌,其他的有些坑,慎用

void request() async {
    ResultData res = await Api.request("param");
    if (res.isSuccess) {
       //拿到res.data就可以進行Json解析了喝滞,這里一般用來構(gòu)造實體類
            TestBean bean = TestBean.fromMap(res.data);

    }else{
      //處理錯誤
    }
  }
Demo地址 https://github.com/po1arbear/Flutter-Net.git

如果覺得有幫助,希望能給個star鼓勵下膏秫,如果不能滿足你的需求右遭,歡迎提issue : )

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缤削,隨后出現(xiàn)的幾起案子窘哈,更是在濱河造成了極大的恐慌,老刑警劉巖亭敢,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滚婉,死亡現(xiàn)場離奇詭異,居然都是意外死亡帅刀,警方通過查閱死者的電腦和手機让腹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門远剩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骇窍,你說我怎么就攤上這事瓜晤。” “怎么了腹纳?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵痢掠,是天一觀的道長。 經(jīng)常有香客問我嘲恍,道長足画,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任佃牛,我火速辦了婚禮淹辞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吁脱。我一直安慰自己桑涎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布兼贡。 她就那樣靜靜地躺著攻冷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遍希。 梳的紋絲不亂的頭發(fā)上等曼,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音凿蒜,去河邊找鬼禁谦。 笑死,一個胖子當(dāng)著我的面吹牛废封,可吹牛的內(nèi)容都是我干的州泊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼漂洋,長吁一口氣:“原來是場噩夢啊……” “哼遥皂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刽漂,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤演训,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贝咙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體样悟,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窟她。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陈症。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖礁苗,靈堂內(nèi)的尸體忽然破棺而出爬凑,到底是詐尸還是另有隱情,我是刑警寧澤试伙,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布嘁信,位于F島的核電站,受9級特大地震影響疏叨,放射性物質(zhì)發(fā)生泄漏潘靖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一蚤蔓、第九天 我趴在偏房一處隱蔽的房頂上張望卦溢。 院中可真熱鬧,春花似錦秀又、人聲如沸单寂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宣决。三九已至,卻和暖如春昏苏,著一層夾襖步出監(jiān)牢的瞬間尊沸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工贤惯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洼专,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓孵构,卻偏偏與公主長得像屁商,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颈墅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345