关于flutter:Flutter-Dio-亲妈级别封装教程

110次阅读

共计 14555 个字符,预计需要花费 37 分钟才能阅读完成。

前不久看到 艾维码 大佬的 dio 封装,通过摸索,改吧改吧,应用的不错。对于之前 艾维码 大佬文章中一些曾经生效的做了修改

为什么肯定要封装一手?

token 拦挡,谬误拦挡,对立错误处理,对立缓存,信息封装(谬误,正确)

Cookie???滚犊子

不论 cookie,再见

全局初始化,传入参数

dio 初始化,传入 baseUrl, connectTimeout,receiveTimeout,options,header 拦截器等。dio 初始化的时候容许咱们传入的一些配置

dio 初始化的配置

这里说下,之前 艾维码 大佬的帖子中的 options,最新版的 dio 曾经应用 requestOptions, 之前的 merge,当初应用 copyWith。详情向下看

如果要白嫖残缺的计划

能够参考应用这套计划开发的 flutter + getx 仿开眼视频 app,有 star 的大佬能够赏点 star。

我的项目地址 github 地址
apk 下载 panbaidu 提取码:3ev2

初始化

这里说下拦截器,能够在初始化的时候传入,也能够手写传入,例如我这里定义了四个拦截器,第一个用于全局 request 时候给申请投加上 context-type:json。第二个是全局错误处理拦截器,上面的内容会介绍拦截器局部。
cache 拦截器,全局解决接口缓存数据,retry 重试拦截器(我临时没怎么用)

    class Http {static final Http _instance = Http._internal();
      // 单例模式应用 Http 类,factory Http() => _instance;

      static late final Dio dio;
      CancelToken _cancelToken = new CancelToken();

      Http._internal() {
        // BaseOptions、Options、RequestOptions 都能够配置参数,优先级别顺次递增,且能够依据优先级别笼罩参数
        BaseOptions options = new BaseOptions();

        dio = Dio(options);

        // 增加 request 拦截器
        dio.interceptors.add(RequestInterceptor());
        // 增加 error 拦截器
        dio.interceptors.add(ErrorInterceptor());
        // // 增加 cache 拦截器
        dio.interceptors.add(NetCacheInterceptor());
        // // 增加 retry 拦截器
        dio.interceptors.add(
          RetryOnConnectionChangeInterceptor(
            requestRetrier: DioConnectivityRequestRetrier(
              dio: dio,
              connectivity: Connectivity(),),
          ),
        );

    // 在调试模式下须要抓包调试,所以咱们应用代理,并禁用 HTTPS 证书校验
    // if (PROXY_ENABLE) {//   (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
    //       (client) {//     client.findProxy = (uri) {
    //       return "PROXY $PROXY_IP:$PROXY_PORT";
    //     };
    //     // 代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以咱们禁用证书校验
    //     client.badCertificateCallback =
    //         (X509Certificate cert, String host, int port) => true;
    //   };
    // }
      }

      /// 初始化公共属性
      ///
      /// [baseUrl] 地址前缀
      /// [connectTimeout] 连贯超时赶时间
      /// [receiveTimeout] 接管超时赶时间
      /// [interceptors] 根底拦截器
      void init({
        String? baseUrl,
        int connectTimeout = 1500,
        int receiveTimeout = 1500,
        Map<String, String>? headers,
        List<Interceptor>? interceptors,
      }) {
        dio.options = dio.options.copyWith(
          baseUrl: baseUrl,
          connectTimeout: connectTimeout,
          receiveTimeout: receiveTimeout,
          headers: headers ?? const {},);
        // 在初始化 http 类的时候,能够传入拦截器
        if (interceptors != null && interceptors.isNotEmpty) {dio.interceptors..addAll(interceptors);
        }
      }

      // 敞开 dio
      void cancelRequests({required CancelToken token}) {_cancelToken.cancel("cancelled");
      }

      // 增加认证
      // 读取本地配置
      Map<String, dynamic>? getAuthorizationHeader() {
        Map<String, dynamic>? headers;
        // 从 getx 或者 sputils 中获取
        // String accessToken = Global.accessToken;
        String accessToken = "";
        if (accessToken != null) {
          headers = {'Authorization': 'Bearer $accessToken',};
        }
        return headers;
      }

      Future get(
        String path, {
        Map<String, dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
        bool refresh = false,
        bool noCache = !CACHE_ENABLE,
        String? cacheKey,
        bool cacheDisk = false,
      }) async {Options requestOptions = options ?? Options();
        requestOptions = requestOptions.copyWith(
          extra: {
            "refresh": refresh,
            "noCache": noCache,
            "cacheKey": cacheKey,
            "cacheDisk": cacheDisk,
          },
        );
        Map<String, dynamic>? _authorization = getAuthorizationHeader();
        if (_authorization != null) {requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        Response response;
        response = await dio.get(
          path,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );

        return response.data;
      }

      Future post(
        String path, {
        Map<String, dynamic>? params,
        data,
        Options? options,
        CancelToken? cancelToken,
      }) async {Options requestOptions = options ?? Options();
        Map<String, dynamic>? _authorization = getAuthorizationHeader();
        if (_authorization != null) {requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.post(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future put(
        String path, {
        data,
        Map<String, dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {Options requestOptions = options ?? Options();

        Map<String, dynamic>? _authorization = getAuthorizationHeader();
        if (_authorization != null) {requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.put(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future patch(
        String path, {
        data,
        Map<String, dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {Options requestOptions = options ?? Options();
        Map<String, dynamic>? _authorization = getAuthorizationHeader();
        if (_authorization != null) {requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.patch(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future delete(
        String path, {
        data,
        Map<String, dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {Options requestOptions = options ?? Options();

        Map<String, dynamic>? _authorization = getAuthorizationHeader();
        if (_authorization != null) {requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.delete(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }
    }

dio 拦截器

上面咱们来看下拦截器,上面是一个解决解决拦截器案例

    // 这里是一个我独自写得 soket 谬误实例,因为 dio 默认生成的是不容许批改 message 内容的,我只能自定义一个应用
    class MyDioSocketException extends SocketException {
      late String message;

      MyDioSocketException(
        message, {
        osError,
        address,
        port,
      }) : super(
              message,
              osError: osError,
              address: address,
              port: port,
            );
    }

    /// 错误处理拦截器
    class ErrorInterceptor extends Interceptor {
      // 是否有网
      Future<bool> isConnected() async {var connectivityResult = await (Connectivity().checkConnectivity());
        return connectivityResult != ConnectivityResult.none;
      }

      @override
      Future<void> onError(DioError err, ErrorInterceptorHandler errCb) async {
        // 自定义一个 socket 实例,因为 dio 原生的实例,message 属于是只读的
        // 这里是我独自加的,因为默认的 dio err 实例,的几种类型,短少无网络状况下的谬误提示信息
        // 这里我手动做解决,来加工一手,成果,看上面的图片,你就晓得
        if (err.error is SocketException) {
          err.error = MyDioSocketException(
            err.message,
            osError: err.error?.osError,
            address: err.error?.address,
            port: err.error?.port,
          );
        }
        // dio 默认的谬误实例,如果是没有网络,只能失去一个未知谬误,无奈精准的得悉是否是无网络的状况
        if (err.type == DioErrorType.other) {bool isConnectNetWork = await isConnected();
          if (!isConnectNetWork && err.error is MyDioSocketException) {err.error.message = "以后网络不可用,请查看您的网络";}
        }
        // error 对立解决
        AppException appException = AppException.create(err);
        // 谬误提醒
        debugPrint('DioError===: ${appException.toString()}');
        err.error = appException;
        return super.onError(err, errCb);
      }
    }

以上的代码能够看到,ErrorInterceptor 类继承自 Interceptor,能够从新 onRequest、onResponse、onError,三个状态,最初 return super.onError 将 err 实例传递给超类。

对立的错误信息包装解决

试想一下,如果你的我的项目,有十几种状态码,每种也也都须要吧 code 码转换成文字信息,因为有时候你须要给用户提醒。例如:连贯超时,申请失败,网络谬误,等等。上面是对立的错误处理

AppException.dart

import 'package:dio/dio.dart';

/// 自定义异样
class AppException implements Exception {
  final String _message;
  final int _code;

  AppException(
    this._code,
    this._message,
  );

  String toString() {return "$_code$_message";}

  String getMessage() {return _message;}

  factory AppException.create(DioError error) {switch (error.type) {
      case DioErrorType.cancel:
        {return BadRequestException(-1, "申请勾销");
        }
      case DioErrorType.connectTimeout:
        {return BadRequestException(-1, "连贯超时");
        }
      case DioErrorType.sendTimeout:
        {return BadRequestException(-1, "申请超时");
        }
      case DioErrorType.receiveTimeout:
        {return BadRequestException(-1, "响应超时");
        }
      case DioErrorType.response:
        {
          try {
            int? errCode = error.response!.statusCode;
            // String errMsg = error.response.statusMessage;
            // return ErrorEntity(code: errCode, message: errMsg);
            switch (errCode) {
              case 400:
                {return BadRequestException(errCode!, "申请语法错误");
                }
              case 401:
                {return UnauthorisedException(errCode!, "没有权限");
                }
              case 403:
                {return UnauthorisedException(errCode!, "服务器拒绝执行");
                }
              case 404:
                {return UnauthorisedException(errCode!, "无奈连贯服务器");
                }
              case 405:
                {return UnauthorisedException(errCode!, "申请办法被禁止");
                }
              case 500:
                {return UnauthorisedException(errCode!, "服务器外部谬误");
                }
              case 502:
                {return UnauthorisedException(errCode!, "有效的申请");
                }
              case 503:
                {return UnauthorisedException(errCode!, "服务器挂了");
                }
              case 505:
                {return UnauthorisedException(errCode!, "不反对 HTTP 协定申请");
                }
              default:
                {// return ErrorEntity(code: errCode, message: "未知谬误");
                  return AppException(errCode!, error.response!.statusMessage!);
                }
            }
          } on Exception catch (_) {return AppException(-1, "未知谬误");
          }
        }
      default:
        {return AppException(-1, error.error.message);
        }
    }
  }
}

/// 申请谬误
class BadRequestException extends AppException {BadRequestException(int code, String message) : super(code, message);
}

/// 未认证异样
class UnauthorisedException extends AppException {UnauthorisedException(int code, String message) : super(code, message);
}

应用的时候这样应用,

Future<ApiResponse<Feed>> getFeedData(url) async {
    try {dynamic response = await HttpUtils.get(url);
      // print(response);
      Feed data = Feed.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {print(e);
      // 这里看这里,如果是有谬误的申请下,应用 AppException 对谬误对象进行解决
      // 解决过后,你就能够比方弹个 toast,提醒给用户等,// 弹窗 toast 等在上面的办法中调用
      return ApiResponse.error(e.error);
    }
  }
  
Future<void> _refresh() async {ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
    
    // 加工过后,咱们能够取得两个状态,Status.COMPLETED 和 Status.ERROR
    // 看这里
    if (swiperResponse.status == Status.COMPLETED) {// 胜利的代码,想干嘛干嘛}else if (swiperResponse.status == Status.ERROR) {
        // 失败的代码,能够给个 toast,提醒给用户
        // 例如我在这里提醒用户
        // 应用 exception!.getMessage(); 取得谬误对象的文字信息,是咱们拦截器解决过后的提醒文字,非英文,拿到这,提醒给用户不香吗???看上面的图片成果
        String errMsg = swiperResponse.exception!.getMessage();
        publicToast(errMsg);
    }
}

这里的提醒就是自定义 err 拦截器中减少的代码,对于 dio 不可能失去是否无网络的补充

磁盘缓存数据,拦截器

磁盘缓存接口数据,首先咱们要封装一个 SpUtil 类,
sputils.dart

class SpUtil {SpUtil._internal();
  static final SpUtil _instance = SpUtil._internal();

  factory SpUtil() {return _instance;}

  SharedPreferences? prefs;

  Future<void> init() async {prefs = await SharedPreferences.getInstance();
  }

  Future<bool> setJSON(String key, dynamic jsonVal) {String jsonString = jsonEncode(jsonVal);
    return prefs!.setString(key, jsonString);
  }

  dynamic getJSON(String key) {String? jsonString = prefs?.getString(key);
    return jsonString == null ? null : jsonDecode(jsonString);
  }

  Future<bool> setBool(String key, bool val) {return prefs!.setBool(key, val);
  }

  bool? getBool(String key) {return prefs!.getBool(key);
  }

  Future<bool> remove(String key) {return prefs!.remove(key);
  }
}

缓存拦截器

const int CACHE_MAXAGE = 86400000;
const int CACHE_MAXCOUNT = 1000;
const bool CACHE_ENABLE = false;

class CacheObject {CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;
  Response response;
  int timeStamp;

  @override
  bool operator ==(other) {return response.hashCode == other.hashCode;}

  @override
  int get hashCode => response.realUri.hashCode;
}

class NetCacheInterceptor extends Interceptor {
  // 为确保迭代器程序和对象插入工夫统一程序统一,咱们应用 LinkedHashMap
  var cache = LinkedHashMap<String, CacheObject>();

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler requestCb,
  ) async {if (!CACHE_ENABLE) {return super.onRequest(options, requestCb);
    }

    // refresh 标记是否是刷新缓存
    bool refresh = options.extra["refresh"] == true;

    // 是否磁盘缓存
    bool cacheDisk = options.extra["cacheDisk"] == true;

    // 如果刷新,先删除相干缓存
    if (refresh) {
      // 删除 uri 雷同的内存缓存
      delete(options.uri.toString());

      // 删除磁盘缓存
      if (cacheDisk) {await SpUtil().remove(options.uri.toString());
      }

      return;
    }

    // get 申请,开启缓存
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 策略 1 内存缓存优先,2 而后才是磁盘缓存

      // 1 内存缓存
      var ob = cache[key];
      if (ob != null) {
        // 若缓存未过期,则返回缓存内容
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            CACHE_MAXAGE) {return;} else {
          // 若已过期则删除缓存,持续向服务器申请
          cache.remove(key);
        }
      }

      // 2 磁盘缓存
      if (cacheDisk) {var cacheData = SpUtil().getJSON(key);
        if (cacheData != null) {return;}
      }
    }
    return super.onRequest(options, requestCb);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler responseCb) async {
    // 如果启用缓存,将返回后果保留到缓存
    if (CACHE_ENABLE) {await _saveCache(response);
    }
    return super.onResponse(response, responseCb);
  }

  Future<void> _saveCache(Response object) async {
    RequestOptions options = object.requestOptions;

    // 只缓存 get 的申请
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      // 策略:内存、磁盘都写缓存

      // 缓存 key
      String key = options.extra["cacheKey"] ?? options.uri.toString();

      // 磁盘缓存
      if (options.extra["cacheDisk"] == true) {await SpUtil().setJSON(key, object.data);
      }

      // 内存缓存
      // 如果缓存数量超过最大数量限度,则先移除最早的一条记录
      if (cache.length == CACHE_MAXCOUNT) {cache.remove(cache[cache.keys.first]);
      }

      cache[key] = CacheObject(object);
    }
  }

  void delete(String key) {cache.remove(key);
  }
}

开始封装

class HttpUtils {
  static void init({
    required String baseUrl,
    int connectTimeout = 1500,
    int receiveTimeout = 1500,
    List<Interceptor>? interceptors,
  }) {Http().init(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
      interceptors: interceptors,
    );
  }

  static void cancelRequests({required CancelToken token}) {Http().cancelRequests(token: token);
  }

  static Future get(
    String path, {
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
    bool refresh = false,
    bool noCache = !CACHE_ENABLE,
    String? cacheKey,
    bool cacheDisk = false,
  }) async {return await Http().get(
      path,
      params: params,
      options: options,
      cancelToken: cancelToken,
      refresh: refresh,
      noCache: noCache,
      cacheKey: cacheKey,
    );
  }

  static Future post(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {return await Http().post(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future put(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {return await Http().put(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future patch(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {return await Http().patch(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future delete(
    String path, {
    data,
    Map<String, dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {return await Http().delete(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }
}

注入,初始化

main。dart。这里参考我集体的应用例子

Future<void> main() async {WidgetsFlutterBinding.ensureInitialized();
  // debugPaintSizeEnabled = true;
  await initStore();
  runApp(MyApp());
}

Future<void> initStore() async {
  // 初始化本地存储类
  await SpUtil().init();
  // 初始化 request 类
  HttpUtils.init(baseUrl: Api.baseUrl,);
  // 历史记录,全局 getx 全局注入,await Get.putAsync(() => HistoryService().init());
  print("全局注入");
}

class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: PageRoutes.INIT_ROUTER,
      getPages: PageRoutes.routes,
    );
  }
}

应用封装好得例子

// 这里定义一个函数,返回的是 future《apiResponse》,能够失去 status 的状态
Future<ApiResponse<Feed>> getFeedData(url) async {
    try {dynamic response = await HttpUtils.get(url);
      // print(response);
      Feed data = Feed.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {print(e);
      return ApiResponse.error(e.error);
    }
  }

  Future<void> _refresh() async {ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
    if (!mounted) {return;}
    // 应用 status.COMPLETED 判断是否胜利
    if (swiperResponse.status == Status.COMPLETED) {setState(() {
        nextPageUrl = swiperResponse.data!.nextPageUrl;
        _swiperList = [];
        _swiperList.addAll(swiperResponse.data!.issueList![0]!.itemList!);
        _itemList = [];});
      // 拉取新的,列表
      await _loading();
      // 应用 status.ERROR 判断是否失败
    } else if (swiperResponse.status == Status.ERROR) {setState(() {stateCode = 2;});
      // 谬误的话,咱们能够调用 getMessage() 获取错误信息。提醒给用户(汉化后的敌对提醒语)String errMsg = swiperResponse.exception!.getMessage();
      publicToast(errMsg);
      print("产生谬误,地位 home bottomBar1 swiper,url: ${initPageUrl}");
      print(swiperResponse.exception);
    }
  }

如果要白嫖残缺的计划

能够参考应用这套计划开发的 flutter + getx 仿开眼视频 app,有 star 的大佬能够赏点 star。

我的项目地址 github 地址
apk 下载 panbaidu 提取码:3ev2

正文完
 0