注册
dio

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

可能是 Flutter 上最强的网络框架, 基于dio实现的非侵入式框架(不影响原有功能). 学习成本低、使用简单, 一行代码发起网络请求, 甚至无需初始化。


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



距离最早的文章发布时间,已经过去了三年。这期间 dio 也更新到5.x.x了,在使用中也积攒了许多定制需求和优化方案。在确定需求和方案后,修修改改,终于发布了最新最实用的网络请求版本。


欢迎贡献代码/问题


特点



  • 个人使用下来感觉开发效率比目前网络请求库都高:最简单易用
  • 专为 Flutter 而生,支持全平台
  • 遵循设计模式最佳实践,build模式全局配置
  • catch请求错误,不需要开发者处理
  • 优秀的源码/注释/文档/示例
  • 类似kotlin的语法糖:请求结果的when语句和密封类

主要功能



  • RESTful API 设计
    GET/POST/PUT/HEAH/DELETE/PATCH/DOWNLOAD
  • 可取消请求
  • 异步解析,数据量大不再卡顿
  • 全局错误处理(减少崩溃率)
  • 自定义解析器,支持全局和单个请求
  • 自定义解析方法
  • 配置请求参数
  • 漂亮的日志打印
  • 证书快速配置
  • 代理配置
  • 拦截器配置
  • 强制缓存模式/自定义缓存Key/缓存有效期/LRU缓存算法/缓存任何数据
  • 监听上传/下载进度

简单使用


添加依赖:


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",
responseType: 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>;又或者接口返回的数据包了几层,我们只需要最里面的数据格式,那么前面就是需要序列化的整个数据类型,第二个泛型是最里面的数据类型。


配置使用


全局配置


在使用前进行全局配置:


 NetWrapper.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 responseType}) {
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) => responseType.fromJson(item)).toList()) as K;
return dataList;
} else {
var model = responseType.fromJson(data) as K;
return model;
}
} else {
var errorMsg = response.data['errorMsg'];
throw NetException(errorMsg, errorCode);
}
}
}

如果添加缓存,可以使用dio_cache_interceptordio_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依赖的diojson_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)

response_log_android_studio.png


特殊配置


有的接口比较特殊,比如返回的数据格式是特殊的,需要单独解析,此时有两种方法实现,第一种适合多个相同的特殊接口,在请求时传入自定义的解析器,第二种是在回调中解析。


解析器httpDecode


    var appResponse = await get<BannerBean, List<BannerBean>>("banner/json",
responseType: 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)),
responseType: 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地址


作者:北海道浪子
链接:https://juejin.cn/post/7212597327579332668
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册