用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来源和作者)
注意:无特殊说明,flutter版本为3.0+
上篇总结了api的组织结构,这一篇我们探讨一下Flutter 常用请求库Dio的使用和封装。
一.dio
一般http请求我们要关注的点有如下几个:
访问路径,请求方式,请求头部,请求参数,超时时间,返回结果,返回类型等。
注:(对http/https协议不太明白的,需要补课的童鞋可以去看下基础教程https://www.runoob.com/http/http-tutorial.html)
首先看下dio可配置的参数:
BaseOptions({ String? method, int? connectTimeout, int? receiveTimeout, int? sendTimeout, String baseUrl = '', Map<String, dynamic>? queryParameters, Map<String, dynamic>? extra, Map<String, dynamic>? headers, ResponseType? responseType = ResponseType.json, String? contentType, ValidateStatus? validateStatus, bool? receiveDataWhenStatusError, bool? followRedirects, int? maxRedirects, RequestEncoder? requestEncoder, ResponseDecoder? responseDecoder, ListFormat? listFormat, this.setRequestContentTypeWhenNoPayload = false, })
除了基础配置dio还提供了拦截器interceptors,适配器httpClientAdapter,请求方法get,post,delete,put,patch,download等基础的封装,方便我们使用。
二.Http 基础类
class Http { ///定义各类超时时间 static const int CONNECT_TIMEOUT = 10000; // 默认设置10s超时 static const int RECEIVE_TIMEOUT = 3000; static const int SEND_TIMEOUT = 3000; Dio? dio; static Http _instance = Http._internal();
1._internal()获取Dio实例的方法,主要是配置适配器
Http._internal() { dio = Dio();
dio.httpClientAdapter = DefaultHttpClientAdapter() ..onHttpClientCreate = (HttpClient c,lient) { client.idleTimeout = Duration(milliseconds: CONNECT_TIMEOUT); };
// 添加错误统一处理拦截器 dio!.interceptors.add(ErrorInterceptor());
//添加日志拦截器
dio!.interceptors.add(DioLogInterceptor());
2.post方法源码示例
Future post( String path, { required String baseUrl, Map<String, dynamic>? params, Map<String, dynamic>? headers, data, Options? options, CancelToken? cancelToken, }) async { Options requestOptions = options ?? Options(); //requestOptions.baseUrl = baseUrl; Map<String, dynamic>? _authorization = headers ?? getAuthorizationHeader(); if (_authorization != null) { requestOptions = requestOptions.copyWith(headers: _authorization); } dio!.options.baseUrl = baseUrl; dio!.options.connectTimeout = CONNECT_TIMEOUT; dio!.options.receiveTimeout = RECEIVE_TIMEOUT; dio!.options.sendTimeout = SEND_TIMEOUT;
var response; var timeout = Duration(milliseconds: dio!.options.connectTimeout); cancelToken = CancelToken();
var t = Timer(timeout, () { if (response == null) { cancelToken!.cancel(); } });
response = await dio!.post(path, data: data, queryParameters: params, options: requestOptions, cancelToken: cancelToken); t.cancel();
return response.data; }
cancelToken 是dio提供的一个非常有用的东西,可以主动在返回超时时候,主动结束请求,并触发DioErrorType.cancel的错误事件。
三.httpUtils 加强封装
1.添加是否等待处理
还以post为例,我们定义一个方法调用http的基础类
static Future post(String path,{data,required String baseUrl, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, bool showLoading = false}
这里加了一个参数showLoading,在我们实际开发过程中,有一些操作要阻断用户操作,等待接口请求,一些是不需要用户感知的请求,我们可以在所有api中,默认添加该参数为false,有必要阻断用户的时候调用的时候将该参数设置成为true,举个例子如下:
/// 忘记密码 static Future<String> forgetPassword(dynamic param, {bool showLoading= false}) async { var res = await HttpUtils.post(_forgetPassword, data: param, baseUrl: "xxx" showLoading: showLoading); return res; }
当判断需要loading 的时候
if (showLoading) { showLoadingDialog(); }
作者使用的是flutter_smart_dialog: ^4.5.3+7,使用比较简单,你可以自己去封装所需要的loading样式。
2.定义统一返回类型
class ApiResponse<T> implements Exception { String? errorMsg; String? msg; int? code; T? data;
ApiResponse(this.msg);
ApiResponse.error(String msg) { toastMsg(msg); }
@override String toString() { return "Msg : $msg \n errorMsg : $errorMsg \n Data : $data"; }
ApiResponse.fromJson(Map<String, dynamic> json) { msg = json['msg']; errorMsg = json['errorMsg']; code = json['code']; data = json['data']; }
Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['msg'] = this.msg; data['errorMsg'] = this.errorMsg; data['code'] = this.code; data['data'] = this.data; return data; } }
3.返回处理
判断后台返回的正确code,则返回结果,完结后清除loading状态。
源码如下
class HttpUtils {
static Future post(String path, {data, required String baseUrl, Map<String, dynamic>? params, Options? options, CancelToken? cancelToken, bool showLoading = false, bool canNull = false, Function(Object?)? onError, bool errorToast = true, bool isolate = true}) async { if (showLoading) { showLoadingDialog(path: path); }
return Http()
.post( path, baseUrl: baseUrl, data: data, params: params, options: options, cancelToken: cancelToken, ) .then((value) { var result = ApiResponse.fromJson(value is Map ? value : jsonDecode(value)); if (result.code == 2000) { return result.data; } else { throw ApiResponse(result.msg!); } }).onError((error, stackTrace) { if (onError != null) { onError(error); } if (error is RemoteError) { toastMsg("网络连接失败", displayType: SmartToastType.last); } else if (error is ApiResponse) { toastMsg(error.msg!); } else { toastMsg("未知错误"); } throw Exception(error.toString()); }).whenComplete(() { if (showLoading) { SmartDialog.dismiss(); } });
}
|