Demo地址 https://github.com/po1arbear/Flutter-Net.git
封裝網(wǎng)絡(luò)請求的幾個好處:
- 便于統(tǒng)一配置請求參數(shù)俩垃,如header励幼,公共參數(shù),加密規(guī)則等
- 方便調(diào)試口柳,詳細(xì)的日志打印信息
- 優(yōu)化代碼性能苹粟,避免到處濫new對象,構(gòu)建全局單例
- 簡化請求步驟跃闹,只暴露需要的響應(yīng)數(shù)據(jù)嵌削,而對錯誤的響應(yīng)統(tǒng)一回調(diào)
- 對接口數(shù)據(jù)的基類封裝,簡化解析流程
- 無侵入的望艺,靈活的請求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);
清晰全面的日志打印
再也不需要額外地配置抓包了,接口調(diào)試效率大大提升
下面通過關(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 : )