Flutter 网络封装 2022-10-12 周三

开发者 2024-9-26 13:54:36 13 0 来自 中国
网络选择


  • 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应用框架搭建(四) 网络哀求封装
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 18:24, Processed in 0.174188 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表