网络选择
- Flutter自带httpClient,这个也是很好用的;
- Http库,有个三方库的名字就叫这个;
- Dio,这是现在最热门的,相称于iOS中AFNetworking。随大流,就选这个进行封装。
Dio引入
- Dio是一个第三方库,以是必要先下载。使用一行下令就可以引入flutter pub add dio
dio: ^4.0.6
- 日志是必要的,最简单的就是用系统提供debugPrint,根本上也够用了。为Dio专门写的插件也有,比如dio_logger。也有比力盛行的插件,比如logger
- loading一方面是期待,另一方是防止用户误使用。一样平常这个也是用第三方插件的居多。
这方面有一个比力突出的第三方插件,那就用吧。
flutter_easyloading: ^3.0.5
别的,toast一样平常和loading都在一个插件中,这两种都是必要的,类似的插件也可以思量
bot_toast
感觉上flutter_easyloading要好一点。
- 网络状态检测,紧张是判断有网还是没网,功能和ping类似,这个也一样平常必要第三方插件。假如没有这个,只有等Dio毗连断了再提示。这方面也有一个比力盛行的第三方插件。
connectivity_plus: ^2.3.6
固然,监听网络状态也是可以的,不外这个不是强需求,可以再必要的时间添加。
- 网络缓存,失败重传,cookie管理这些,不是必须的需求,可以延后思量。现在这些也有相应的Dio配套三方库可以选择。这些都是拦截器模式的,可以随时添加。
- 抓包,署理,证书验证这些也暂时不加,等以后有必要的时间再思量。
毗连常数
用一个类来生存网络毗连必要常数,现在紧张是超时时间,baseURL,头部信息等。常见的需求切换背景情况,紧张的就是更换这个baseURL
class HttpOptions { /// 超时时间;单元是ms static const int connectTimeout = 30000; static const int receiveTimeout = 30000; /// 地点前缀 static const String baseUrl = 'https://baidu.com'; /// header static const Map<String, dynamic> headers = { 'Accept': 'application/json,*/*', 'Content-Type': 'application/json', 'currency': 'CNY', 'lang': 'en', 'device': 'app', };}非常处置惩罚
- 错误提示是紧张的底子功能,绕不外的,以是自界说一个类,实现Exception协议,紧张包罗错误码和错误信息两部分内容。只必要一次封装就好了,分太细反而贫苦。
- DioError是Dio提供的一个错误信息结构,我们可以基于这个,添加一些自界说的信息。
- DioError中还包罗服务相应Response,内里有代表服务强相应的状态码和错误信息,可以使用这点,对服务器端的错误状态进行一次封装。
import 'package:dio/dio.dart';class HttpException implements Exception { final int code; final String msg; HttpException({ this.code = -1, this.msg = 'unknow error', }); @override String toString() { return 'Http Error [$code]: $msg'; } factory HttpException.create(DioError error) { /// dio非常 switch (error.type) { case DioErrorType.cancel: { return HttpException(code: -1, msg: 'request cancel'); } case DioErrorType.connectTimeout: { return HttpException(code: -1, msg: 'connect timeout'); } case DioErrorType.sendTimeout: { return HttpException(code: -1, msg: 'send timeout'); } case DioErrorType.receiveTimeout: { return HttpException(code: -1, msg: 'receive timeout'); } case DioErrorType.response: { try { int statusCode = error.response?.statusCode ?? 0; // String errMsg = error.response.statusMessage; // return ErrorEntity(code: errCode, message: errMsg); switch (statusCode) { case 400: { return HttpException(code: statusCode, msg: 'Request syntax error'); } case 401: { return HttpException(code: statusCode, msg: 'Without permission'); } case 403: { return HttpException(code: statusCode, msg: 'Server rejects execution'); } case 404: { return HttpException(code: statusCode, msg: 'Unable to connect to server'); } case 405: { return HttpException(code: statusCode, msg: 'The request method is disabled'); } case 500: { return HttpException(code: statusCode, msg: 'Server internal error'); } case 502: { return HttpException(code: statusCode, msg: 'Invalid request'); } case 503: { return HttpException(code: statusCode, msg: 'The server is down.'); } case 505: { return HttpException(code: statusCode, msg: 'HTTP requests are not supported'); } default: { return HttpException( code: statusCode, msg: error.response?.statusMessage ?? 'unknow error'); } } } on Exception catch (_) { return HttpException(code: -1, msg: 'unknow error'); } } default: { return HttpException(code: -1, msg: error.message); } } }}非常拦截器
- 把错误处置惩罚做一个拦截器,在onError方法中将自界说的非常范例放入DioError的error字段(dynamic的)
- 这个确实有点绕。体现通过DioError的error type来创建自界说的非常范例HttpException;然后又把自界说的非常范例通过拦截器放入DioError的error字段,重新回归到Dio的框架处置惩罚过程之中。
- 假如没有网络,DioError的type字段为DioErrorType.other,这部分自界说的非常范例HttpException并没有处置惩罚。以是这个时间,还必要调用一下检测网络状态第三方插件connectivity_plus,看看是不是断网了。
class ErrorInterceptor extends Interceptor { @override void onError(DioError err, ErrorInterceptorHandler handler) async { /// 根据DioError创建HttpException HttpException httpException = HttpException.create(err); /// dio默认的错误实例,假如是没有网络,只能得到一个未知错误,无法精准的得知是否是无网络的情况 /// 这里对于断网的情况,给一个特别的code和msg if (err.type == DioErrorType.other) { var connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult == ConnectivityResult.none) { httpException = HttpException(code: -100, msg: 'None Network.'); } } /// 将自界说的HttpException err.error = httpException; /// 调用父类,回到dio框架 super.onError(err, handler); }}封装
- 一样平常都会把Dio封装为一个单例。一个APP中,一个Dio实例富足了。
- 固然Dio提供了get,post等各种方法,但是必要参加一些自界说的参数,以是一样平常会直接封装更底层的request方法。
- http的method是字符串,可以思量用一个摆列,在调用request方法的时间同一转换。
- loading,错误信息,可以在这个request方法上同一用try catch结构在这里处置惩罚
- 错误信息,log,自界说头部(比如token)等可以通过拦截器的情势参加。有许多共同Dio的拦截器第三方插件,比如pretty_dio_logger
- 公共头部信息,超时时间,baseUrl等信息可以通过BaseOption的情势在Dio单例创建的时间给出。
- 切换情况可以通过修改baseUrl的方式实现。直接代码表明是最方便的,高级一点的话,可以通过本地缓存的方式来实现。
class HttpRequest { // 单例模式使用Http类, static final HttpRequest _instance = HttpRequest._internal(); factory HttpRequest() => _instance; static late final Dio dio; /// 内部构造方法 HttpRequest._internal() { /// 初始化dio BaseOptions options = BaseOptions( connectTimeout: HttpOptions.connectTimeout, receiveTimeout: HttpOptions.receiveTimeout, sendTimeout: HttpOptions.sendTimeout, baseUrl: HttpOptions.baseUrl, headers: HttpOptions.headers, ); dio = Dio(options); /// 添加各种拦截器 dio.interceptors.add(ErrorInterceptor()); dio.interceptors.add(dioLoggerInterceptor); } /// 封装request方法 Future request({ required String path, required HttpMethod method, dynamic data, Map<String, dynamic>? queryParameters, bool showLoading = true, bool showErrorMessage = true, }) async { const Map methodValues = { HttpMethod.get: 'get', HttpMethod.post: 'post', HttpMethod.put: 'put', HttpMethod.delete: 'delete', HttpMethod.patch: 'patch', HttpMethod.head: 'head' }; Options options = Options( method: methodValues[method], ); try { if (showLoading) { EasyLoading.show(status: 'loading...'); } Response response = await HttpRequest.dio.request( path, data: data, queryParameters: queryParameters, options: options, ); return response.data; } on DioError catch (error) { HttpException httpException = error.error; if (showErrorMessage) { EasyLoading.showToast(httpException.msg); } } finally { if (showLoading) { EasyLoading.dismiss(); } } }}enum HttpMethod { get, post, delete, put, patch, head,}工具方法
- 直接使用request方法不是很方便,以是,再封装一层,对外仍旧提供get,post等方便方法
- 一样平常直接包装成静态方法,用起来最方便。
/// 调用底层的request,重新提供get,post等方便方法class HttpUtil { static HttpRequest httpRequest = HttpRequest(); /// get static Future get({ required String path, Map<String, dynamic>? queryParameters, bool showLoading = true, bool showErrorMessage = true, }) { return httpRequest.request( path: path, method: HttpMethod.get, queryParameters: queryParameters, showLoading: showLoading, showErrorMessage: showErrorMessage, ); } /// post static Future post({ required String path, required HttpMethod method, dynamic data, bool showLoading = true, bool showErrorMessage = true, }) { return httpRequest.request( path: path, method: HttpMethod.post, data: data, showLoading: showLoading, showErrorMessage: showErrorMessage, ); }逻辑模块
- 一样平常背景会按照逻辑模块分类,分类一样平常以path中的字符来区分。比如用户模块一样平常以/user/开头。
- 对应于背景的风俗,可以思量以模块名为文件名,将类似的接口放在同一个文件中。比如用户模块都放在user_api.dart文件中。
- 详细到每一个接口,path,参数等都可以确定,以是在用static方法包一层是可行的。
import 'package:panda_buy/apis/http/http_util.dart';class UserApi { /// path界说 static const String pathPrefix = '/gateway/user/'; /// 获取公钥 static Future getPubKey() { return HttpUtil.get( path: '${pathPrefix}pubkey', showLoading: false, showErrorMessage: false, ); }
log
- dio_logger 是为Dio定制的,只要一行代码就可以了dio.interceptors.add(dioLoggerInterceptor);
- log内容按照request和response分开,根本能用。
- 颜色区分没有,不知道什么缘故因由。
- 以拦截器的情势作为Dio的配件引入,感觉还是不错的。
参考文章
Flutter Dio 亲妈级别封装教程
Flutter应用框架搭建(四) 网络哀求封装 |