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

本文主要是介绍Flutter网络请求库Dio的封装(单例、动态baseUrl、拦截器、日志、请求loading),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

请求loading自动化

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

 

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

请求时loading

清晰全面的日志打印

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

 

image.png

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

HttpManager的定义

构造全局单例,配置请求参数,配置通用的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;}//一般请求,默认域名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);
}

响应基类

默认200的情况isSuccess为true,响应为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);}
}

公共参数和加密等

 

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";
}

响应拦截器

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

 

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 {@overrideonResponse(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);}
}

日志拦截器

打印请求参数和返回参数

 


import 'package:dio/dio.dart';///日志拦截器
class DioLogInterceptor extends Interceptor {@overrideFuture 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";} elserequestStr += "- BODY:\n${data.toString()}\n";}print(requestStr);return options;}@overrideFuture 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;}@overrideFuture 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 chunkpattern.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();elseresponseStr += 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解析了,这里一般用来构造实体类TestBean bean = TestBean.fromMap(res.data);}else{//处理错误}}

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

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



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

这篇关于Flutter网络请求库Dio的封装(单例、动态baseUrl、拦截器、日志、请求loading)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/508763

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

解读GC日志中的各项指标用法

《解读GC日志中的各项指标用法》:本文主要介绍GC日志中的各项指标用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基础 GC 日志格式(以 G1 为例)1. Minor GC 日志2. Full GC 日志二、关键指标解析1. GC 类型与触发原因2. 堆

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

springboot如何通过http动态操作xxl-job任务

《springboot如何通过http动态操作xxl-job任务》:本文主要介绍springboot如何通过http动态操作xxl-job任务的问题,具有很好的参考价值,希望对大家有所帮助,如有错... 目录springboot通过http动态操作xxl-job任务一、maven依赖二、配置文件三、xxl-

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

MySQL 打开binlog日志的方法及注意事项

《MySQL打开binlog日志的方法及注意事项》本文给大家介绍MySQL打开binlog日志的方法及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、默认状态二、如何检查 binlog 状态三、如何开启 binlog3.1 临时开启(重启后失效)

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关