本文正在参与「金石计划」

可能是 Flutter 上最强的网络封装结构, 根据dio完成的非侵入式结构(不影响原有功用). 学习成本低、运用简略, 一行代码发起网络恳求, 甚至无需初始化。

之前写过两篇关于封装网络库的文章:

  • 强壮的dio封装,可能满足你的一切需求
  • 一步一步教你封装最新版的Dio

距离最早的文章发布时间,已经过去了三年。这期间 dio 已经更新到5.x.x版别了,在运用中也积累了许多定制需求和优化计划。在确定需求和计划后,修修改改,终于发布了最新最有用的网络恳求版别。

欢迎奉献代码/问题

特色

  • 个人运用下来感觉开发功率比现在网络恳求库都高:最简略易用
  • 专为 Flutter 而生,支撑全渠道
  • 遵从规划形式最佳实践,Builder Pattern 大局装备
  • 捕获恳求错误,不需求开发者处理
  • 优异的源码/注释/文档/示例
  • 相似kotlin的语法糖:恳求结果when句子判断和密封类

主要功用

  • RESTful API 规划 GET/POST/PUT/HEAH/DELETE/PATCH/DOWNLOAD
  • 可取消恳求
  • 异步解析,数据量大不再卡顿
  • 大局错误处理(减少崩溃率)
  • 自定义解析器,支撑大局和单个恳求
  • 自定义解析办法
  • 装备恳求参数
  • 漂亮的日志打印
  • 证书快速装备
  • 代理装备
  • 拦截器装备
  • 友好支撑缓存装备
  • 监听上传/下载进度

简略运用

添加依靠:

dependencies:
  flutter_nb_net: ^0.0.1

像 dio 相同运用,无需装备,回来实体类完成BaseNetworkModel,复写fromJson函数即可:

class BannerModel extends BaseNetworkModel<BannerModel> {
  @override
  BannerModel fromJson(Map<String, dynamic> json) {
    return BannerModel.fromJson(json);
  }
  //...
  }

温馨提示:dart实体类可用freezedjson_serializable生成或许JsonToDart插件一键生成。

  /// Get 恳求
  void requestGet() async {
    var appResponse = await get<BannerModel, BannerModel>("banner/json",
        decodeType: BannerModel());
    appResponse.when(success: (BannerModel model) {
      var size = model.data?.length;
      debugPrint("成功回来$size条");
    }, failure: (String msg, int code) {
      debugPrint("失利了:msg=$msg/code=$code");
    });
  }

get<BannerModel, BannerModel>这儿有两个泛型,前者是接口回来的数据需求序列化的类型,后者是开发重视的类型。比如接口回来一个用户列表,前面泛型就是User类型,后边是List<User>。又或许接口回来的数据包了几层,我们只需求最里边的数据格式,那么前面就是需求序列化的整个数据类型,第二个泛型是最里边的数据类型。别的decodeType也是必须的参数,传入第一个泛型的实例即可。

装备运用

大局装备

在运用前进行大局装备:

 NetOptions.instance
      // header
      .addHeaders({"aaa": '111'})
      // baseUrl
      .setBaseUrl("https://www.wanandroid.com/")
      // 代理/https
      .setHttpClientAdapter(IOHttpClientAdapter()
        ..onHttpClientCreate = (client) {
          client.findProxy = (uri) {
            return 'PROXY 192.168.20.43:8888';
          };
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) => true;
          return client;
        })
      // cookie
      .addInterceptor(CookieManager(CookieJar()))
      // dio_http_cache
      .addInterceptor(DioCacheManager(CacheConfig(
        baseUrl: "https://www.wanandroid.com/",
      )).interceptor)
      // dio_cache_interceptor
      .addInterceptor(DioCacheInterceptor(
          options: CacheOptions(
        store: MemCacheStore(),
        policy: CachePolicy.forceCache,
        hitCacheOnErrorExcept: [401, 403],
        maxStale: const Duration(days: 7),
        priority: CachePriority.normal,
        cipher: null,
        keyBuilder: CacheOptions.defaultCacheKeyBuilder,
        allowPostMethod: false,
      )))
       //  大局解析器
      .setHttpDecoder(MyHttpDecoder.getInstance())
       //  超时时间
      .setConnectTimeout(const Duration(milliseconds: 3000))
      // 允许打印log,默许未 true
      .enableLogger(true)
      .create();

假如接口回来的数据格式是统一规范的,装备一个自定义大局解析器即可.setHttpDecoder(MyHttpDecoder.getInstance())

/// 自定义解码器
class MyHttpDecoder extends NetDecoder {
  /// 单例对象
  static final MyHttpDecoder _instance = MyHttpDecoder._internal();
  /// 内部结构办法,可防止外部暴露结构函数,进行实例化
  MyHttpDecoder._internal();
  /// 工厂结构办法,这儿运用命名结构函数方式进行声明
  factory MyHttpDecoder.getInstance() => _instance;
  @override
  K decode<T extends BaseNetworkModel, K>(
      {required Response<dynamic> response, required T decodeType}) {
    var errorCode = response.data['errorCode'];
    /// 恳求成功
    if (errorCode == 0) {
      var data = response.data['data'];
      if (data is List) {
        var dataList = List<T>.from(
            data.map((item) => decodeType.fromJson(item)).toList()) as K;
        return dataList;
      } else {
        var model = decodeType.fromJson(data) as K;
        return model;
      }
    } else {
      var errorMsg = response.data['errorMsg'];
      throw NetException(errorMsg, errorCode);
    }
  }
}

假如需求缓存数据,可以运用dio_cache_interceptor、dio_http_cache等 dio 推荐的缓存库。

 // dio_http_cache
      .addInterceptor(DioCacheManager(CacheConfig(
        baseUrl: "https://www.wanandroid.com/",
      )).interceptor)
      // dio_cache_interceptor
      .addInterceptor(DioCacheInterceptor(
          options: CacheOptions(
        store: MemCacheStore(),
        policy: CachePolicy.forceCache,
        hitCacheOnErrorExcept: [401, 403],
        maxStale: const Duration(days: 7),
        priority: CachePriority.normal,
        cipher: null,
        keyBuilder: CacheOptions.defaultCacheKeyBuilder,
        allowPostMethod: false,
      )))

由于dio_http_cache依靠的dio 和json_annotation是旧版别,所以假如运用dio_http_cache`需求解决下依靠冲突:

dependency_overrides:
  dio: ^5.0.3
  json_annotation: ^4.8.0

装备代理和证书:

      .setHttpClientAdapter(IOHttpClientAdapter()
        ..onHttpClientCreate = (client) {
          client.findProxy = (uri) {
            return 'PROXY 192.168.20.43:8888';
          };
          client.badCertificateCallback =
              (X509Certificate cert, String host, int port) => true;
          return client;
        })

装备cookie

addInterceptor(CookieManager(CookieJar()))

敞开 log,默许敞开:

.enableLogger(true)

究极进化版基于 dio 的网络封装库

特别装备

有的接口比较特别,比如回来的数据格式是特别的,需求独自解析,此刻有两种办法完成,第一种合适多个相同的特别接口,自定义针对这种数据格式的解析器,在恳求时传入即可;第二种合适独自出现的数据格式,通过回调办法在其中自行解析。

解析器httpDecode

    var appResponse = await get<BannerBean, List<BannerBean>>("banner/json",
        decodeType: BannerBean(), httpDecode: MyHttpDecoder.getInstance());
    appResponse.when(success: (List<BannerBean> model) {
      var size = model.length;
      debugPrint("成功回来$size条");
    }, failure: (String msg, int code) {
      debugPrint("失利了:$msg");
    });

回调converter

   var appResponse = await get<BannerModel, List<BannerBean>>("banner/json",
        options: buildCacheOptions(const Duration(days: 7)),
        decodeType: BannerModel(), converter: (response) {
      var errorCode = response.data['errorCode'];
      /// 恳求成功
      if (errorCode == 0) {
        var data = response.data['data'];
        var dataList = List<BannerBean>.from(
            data.map((item) => BannerBean.fromJson(item)).toList());
        return Result.success(dataList);
      } else {
        var errorMsg = response.data['errorMsg'];
        return Result.failure(msg: errorMsg, code: errorCode);
      }
    });
    appResponse.when(success: (List<BannerBean> model) {
      debugPrint("成功回来${model.length}条");
    }, failure: (String msg, int code) {
      debugPrint("失利了:msg=$msg/code=$code");
    });

姓名的由来

一开始这个库的姓名是net,这是我第一次在 pub 上发布,不知道库名称不能重合的规矩,一向失利:

`xxx@gmail.com` has insufficient permissions to upload new versions to existing package `net`.

说明 pub 上已经有了这个姓名的库,改名flutter_net,依然失利:

`xxx@gmail.com` has insufficient permissions to upload new versions to existing package `flutter_net`.

最后改名为flutter_nb_net,终于发布成功了。

国际惯例上源码

pub地址