Flutter网络请求库Dio的封装(单例、动态baseUrl、拦截器、日志、请求loading)

左手的ㄟ右手 2022-12-22 09:18 578阅读 0赞

封装网络请求的几个好处:

  1. 便于统一配置请求参数,如header,公共参数,加密规则等
  2. 方便调试,详细的日志打印信息
  3. 优化代码性能,避免到处滥new对象,构建全局单例
  4. 简化请求步骤,只暴露需要的响应数据,而对错误的响应统一回调
  5. 对接口数据的基类封装,简化解析流程
  6. 无侵入的,灵活的请求loading配置

请求loading自动化

只需要传递一个参数,就可以为请求加上Loading效果,没有任何的代码入侵

  1. var params = DataHelper.getBaseMap();
  2. params.clear();
  3. params["apikey"] = "0df993c66c0c636e29ecbb5344252a4a";
  4. params["start"] = "0";
  5. params["count"] = "10";
  6. //withLoading也可以省略,默认就加上,会更简洁
  7. ResultData res = await HttpManager.getInstance()
  8. .get(Address.TEST_API, params: params, withLoading: true);

3c5a462397b424eed8dd3217db830f20.webp

请求时loading

清晰全面的日志打印

再也不需要额外地配置抓包了,接口调试效率大大提升

6162f2e1168be7e0477b3776c2dab34a.webp

image.png

下面通过关键源码介绍下封装过程

HttpManager的定义

构造全局单例,配置请求参数,配置通用的GET\POST,支持baseUrl的切换

  1. import 'package:dio/dio.dart';
  2. import 'package:flutter_net/code.dart';
  3. import 'package:flutter_net/dio_log_interceptor.dart';
  4. import 'package:flutter_net/loading_utils.dart';
  5. import 'response_interceptor.dart';
  6. import 'result_data.dart';
  7. import 'address.dart';
  8. class HttpManager {
  9. static HttpManager _instance = HttpManager._internal();
  10. Dio _dio;
  11. static const CODE_SUCCESS = 200;
  12. static const CODE_TIME_OUT = -1;
  13. factory HttpManager() => _instance;
  14. ///通用全局单例,第一次使用时初始化
  15. HttpManager._internal({String baseUrl}) {
  16. if (null == _dio) {
  17. _dio = new Dio(
  18. new BaseOptions(baseUrl: Address.BASE_URL, connectTimeout: 15000));
  19. _dio.interceptors.add(new DioLogInterceptor());
  20. // _dio.interceptors.add(new PrettyDioLogger());
  21. _dio.interceptors.add(new ResponseInterceptors());
  22. }
  23. }
  24. static HttpManager getInstance({String baseUrl}) {
  25. if (baseUrl == null) {
  26. return _instance._normal();
  27. } else {
  28. return _instance._baseUrl(baseUrl);
  29. }
  30. }
  31. //用于指定特定域名
  32. HttpManager _baseUrl(String baseUrl) {
  33. if (_dio != null) {
  34. _dio.options.baseUrl = baseUrl;
  35. }
  36. return this;
  37. }
  38. //一般请求,默认域名
  39. HttpManager _normal() {
  40. if (_dio != null) {
  41. if (_dio.options.baseUrl != Address.BASE_URL) {
  42. _dio.options.baseUrl = Address.BASE_URL;
  43. }
  44. }
  45. return this;
  46. }
  47. ///通用的GET请求
  48. get(api, {params, withLoading = true}) async {
  49. if (withLoading) {
  50. LoadingUtils.show();
  51. }
  52. Response response;
  53. try {
  54. response = await _dio.get(api, queryParameters: params);
  55. if (withLoading) {
  56. LoadingUtils.dismiss();
  57. }
  58. } on DioError catch (e) {
  59. if (withLoading) {
  60. LoadingUtils.dismiss();
  61. }
  62. return resultError(e);
  63. }
  64. if (response.data is DioError) {
  65. return resultError(response.data['code']);
  66. }
  67. return response.data;
  68. }
  69. ///通用的POST请求
  70. post(api, {params, withLoading = true}) async {
  71. if (withLoading) {
  72. LoadingUtils.show();
  73. }
  74. Response response;
  75. try {
  76. response = await _dio.post(api, data: params);
  77. if (withLoading) {
  78. LoadingUtils.dismiss();
  79. }
  80. } on DioError catch (e) {
  81. if (withLoading) {
  82. LoadingUtils.dismiss();
  83. }
  84. return resultError(e);
  85. }
  86. if (response.data is DioError) {
  87. return resultError(response.data['code']);
  88. }
  89. return response.data;
  90. }
  91. }
  92. ResultData resultError(DioError e) {
  93. Response errorResponse;
  94. if (e.response != null) {
  95. errorResponse = e.response;
  96. } else {
  97. errorResponse = new Response(statusCode: 666);
  98. }
  99. if (e.type == DioErrorType.CONNECT_TIMEOUT ||
  100. e.type == DioErrorType.RECEIVE_TIMEOUT) {
  101. errorResponse.statusCode = Code.NETWORK_TIMEOUT;
  102. }
  103. return new ResultData(
  104. errorResponse.statusMessage, false, errorResponse.statusCode);
  105. }

响应基类

默认200的情况isSuccess为true,响应为response.data,赋值给data

  1. class ResultData {
  2. var data;
  3. bool isSuccess;
  4. int code;
  5. var headers;
  6. ResultData(this.data, this.isSuccess, this.code, {this.headers});
  7. }

Api的封装

请求的集中管理

  1. class Api {
  2. ///示例请求
  3. static request(String param) {
  4. var params = DataHelper.getBaseMap();
  5. params['param'] = param;
  6. return HttpManager.getInstance().get(Address.TEST_API, params);
  7. }
  8. }

公共参数和加密等

  1. class DataHelper{
  2. static SplayTreeMap getBaseMap() {
  3. var map = new SplayTreeMap<String, dynamic>();
  4. map["platform"] = AppConstants.PLATFORM;
  5. map["system"] = AppConstants.SYSTEM;
  6. map["channel"] = AppConstants.CHANNEL;
  7. map["time"] = new DateTime.now().millisecondsSinceEpoch.toString();
  8. return map;
  9. }
  10. static string2MD5(String data) {
  11. var content = new Utf8Encoder().convert(data);
  12. var digest = md5.convert(content);
  13. return hex.encode(digest.bytes);
  14. }
  15. }

地址的配置

方便地址管理

  1. class Address {
  2. static const String TEST_API = "test_api";
  3. }

响应拦截器

过滤正确的响应数据,对数据进行初步封装

  1. import 'package:dio/dio.dart';
  2. import 'package:exchange_flutter/common/net/code.dart';
  3. import 'package:flutter/material.dart';
  4. import '../result_data.dart';
  5. class ResponseInterceptors extends InterceptorsWrapper {
  6. @override
  7. onResponse(Response response) {
  8. RequestOptions option = response.request;
  9. try {
  10. if (option.contentType != null &&
  11. option.contentType.primaryType == "text") {
  12. return new ResultData(response.data, true, Code.SUCCESS);
  13. }
  14. ///一般只需要处理200的情况,300、400、500保留错误信息
  15. if (response.statusCode == 200 || response.statusCode == 201) {
  16. int code = response.data["code"];
  17. if (code == 0) {
  18. return new ResultData(response.data, true, Code.SUCCESS,
  19. headers: response.headers);
  20. } else if (code == 100006 || code == 100007) {
  21. } else {
  22. Fluttertoast.showToast(msg: response.data["msg"]);
  23. return new ResultData(response.data, false, Code.SUCCESS,
  24. headers: response.headers);
  25. }
  26. }
  27. } catch (e) {
  28. print(e.toString() + option.path);
  29. return new ResultData(response.data, false, response.statusCode,
  30. headers: response.headers);
  31. }
  32. return new ResultData(response.data, false, response.statusCode,
  33. headers: response.headers);
  34. }
  35. }

日志拦截器

打印请求参数和返回参数

  1. import 'package:dio/dio.dart';
  2. ///日志拦截器
  3. class DioLogInterceptor extends Interceptor {
  4. @override
  5. Future onRequest(RequestOptions options) async {
  6. String requestStr = "\n==================== REQUEST ====================\n"
  7. "- URL:\n${options.baseUrl + options.path}\n"
  8. "- METHOD: ${options.method}\n";
  9. requestStr += "- HEADER:\n${options.headers.mapToStructureString()}\n";
  10. final data = options.data;
  11. if (data != null) {
  12. if (data is Map)
  13. requestStr += "- BODY:\n${data.mapToStructureString()}\n";
  14. else if (data is FormData) {
  15. final formDataMap = Map()
  16. ..addEntries(data.fields)
  17. ..addEntries(data.files);
  18. requestStr += "- BODY:\n${formDataMap.mapToStructureString()}\n";
  19. } else
  20. requestStr += "- BODY:\n${data.toString()}\n";
  21. }
  22. print(requestStr);
  23. return options;
  24. }
  25. @override
  26. Future onError(DioError err) async {
  27. String errorStr = "\n==================== RESPONSE ====================\n"
  28. "- URL:\n${err.request.baseUrl + err.request.path}\n"
  29. "- METHOD: ${err.request.method}\n";
  30. errorStr +=
  31. "- HEADER:\n${err.response.headers.map.mapToStructureString()}\n";
  32. if (err.response != null && err.response.data != null) {
  33. print('╔ ${err.type.toString()}');
  34. errorStr += "- ERROR:\n${_parseResponse(err.response)}\n";
  35. } else {
  36. errorStr += "- ERRORTYPE: ${err.type}\n";
  37. errorStr += "- MSG: ${err.message}\n";
  38. }
  39. print(errorStr);
  40. return err;
  41. }
  42. @override
  43. Future onResponse(Response response) async {
  44. String responseStr =
  45. "\n==================== RESPONSE ====================\n"
  46. "- URL:\n${response.request.uri}\n";
  47. responseStr += "- HEADER:\n{";
  48. response.headers.forEach(
  49. (key, list) => responseStr += "\n " + "\"$key\" : \"$list\",");
  50. responseStr += "\n}\n";
  51. responseStr += "- STATUS: ${response.statusCode}\n";
  52. if (response.data != null) {
  53. responseStr += "- BODY:\n ${_parseResponse(response)}";
  54. }
  55. printWrapped(responseStr);
  56. return response;
  57. }
  58. void printWrapped(String text) {
  59. final pattern = new RegExp('.{1,800}'); // 800 is the size of each chunk
  60. pattern.allMatches(text).forEach((match) => print(match.group(0)));
  61. }
  62. String _parseResponse(Response response) {
  63. String responseStr = "";
  64. var data = response.data;
  65. if (data is Map)
  66. responseStr += data.mapToStructureString();
  67. else if (data is List)
  68. responseStr += data.listToStructureString();
  69. else
  70. responseStr += response.data.toString();
  71. return responseStr;
  72. }
  73. }
  74. extension Map2StringEx on Map {
  75. String mapToStructureString({int indentation = 2}) {
  76. String result = "";
  77. String indentationStr = " " * indentation;
  78. if (true) {
  79. result += "{";
  80. this.forEach((key, value) {
  81. if (value is Map) {
  82. var temp = value.mapToStructureString(indentation: indentation + 2);
  83. result += "\n$indentationStr" + "\"$key\" : $temp,";
  84. } else if (value is List) {
  85. result += "\n$indentationStr" +
  86. "\"$key\" : ${value.listToStructureString(indentation: indentation + 2)},";
  87. } else {
  88. result += "\n$indentationStr" + "\"$key\" : \"$value\",";
  89. }
  90. });
  91. result = result.substring(0, result.length - 1);
  92. result += indentation == 2 ? "\n}" : "\n${" " * (indentation - 1)}}";
  93. }
  94. return result;
  95. }
  96. }
  97. extension List2StringEx on List {
  98. String listToStructureString({int indentation = 2}) {
  99. String result = "";
  100. String indentationStr = " " * indentation;
  101. if (true) {
  102. result += "$indentationStr[";
  103. this.forEach((value) {
  104. if (value is Map) {
  105. var temp = value.mapToStructureString(indentation: indentation + 2);
  106. result += "\n$indentationStr" + "\"$temp\",";
  107. } else if (value is List) {
  108. result += value.listToStructureString(indentation: indentation + 2);
  109. } else {
  110. result += "\n$indentationStr" + "\"$value\",";
  111. }
  112. });
  113. result = result.substring(0, result.length - 1);
  114. result += "\n$indentationStr]";
  115. }
  116. return result;
  117. }
  118. }

示例请求

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

  1. void request() async {
  2. ResultData res = await Api.request("param");
  3. if (res.isSuccess) {
  4. //拿到res.data就可以进行Json解析了,这里一般用来构造实体类
  5. TestBean bean = TestBean.fromMap(res.data);
  6. }else{
  7. //处理错误
  8. }
  9. }

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

如果觉得有帮助,希望能给个star鼓励下,如果不能满足你的需求,欢迎提issue : )

作者:刺客的幻影
链接:https://www.jianshu.com/p/5ead0cf96642
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

表情:
评论列表 (有 0 条评论,578人围观)

还没有评论,来说两句吧...

相关阅读