简介
Dart IO库中提供了用于发起Http请求的一些类,我们可以直接使用HttpClient来发起请求。就像iOS开发中,系统提供了URLSession,可以直接用,但是大多数情况,还是会选择第三方库AFNetworking。所以,在Flutter中也差不多,HttpClient也很少会直接用,而是找响应的第三方库。
Flutter中的网络库,基本上用Dio
引入
dio: ^4.0.6
基本使用
import 'package:dio/dio.dart';
Dio dio = Dio();
Response response;
// get
response = await dio.get("/test", queryParameters:{"id":12,"name":"wendu"});
print(response);
// post
response = await dio.post("/test", data:{"id":12,"name":"wendu"});
print(response);
包装成单例
一般都会在外面包一层,做一个单例,Dio对象作为内部成员。
class DioManager {
/// *********************************** 实例变量 ***********************************
Dio dio;
String dioManageID;
/// [DioManager]持有的 - 静态的final实例对象, 并进行初始化
static final DioManager _dioManager = DioManager._instance();
/// *********************************** 构造函数 ***********************************
/// [DioManager]私有的 自定义命名式构造方法, Ps:instance不是关键字, 可随意命名
/// 加 _ 表示该命名式构造函数为[DioManager]私有, 外部是不可调用的,
/// 从而确保该命名式构造函数的使用, 仅可用来创建 _dioManager 这个静态的final实例对象
DioManager._instance() {
dio = Dio();
dioManageID = "看看ID是啥" + Random().nextInt(1000).toString();
}
/// 工厂化的主构造函数 - 返回私有的实例对象
/// 返回的就是唯一的实例 _dioManager
factory DioManager() {
return _dioManager;
}
}
外包装类是单例,内部的成员变量就理所当然只有一个。
结果函数封装
在iOS开发中,结果用block。在这里,可以直接使用函数作为参数。
typedef Success = Function(T data);
typedef Fail = Function(T data);
泛型是Objective-C没有的特性
错误封装
Objective-C中的NSError虽然有点啰嗦,不过将code和message封装起来表示错误信息是非常好的。flutter和dart中没有类似的,所以自己封装一个
class NetError {
int code;
String msg;
NetError(this.code, this.msg);
}
网络可达性
是不是有网络,网络状态检查,这个功能Dio中没有,需要用到其他的插件
插件地址
插件引入:
connectivity_plus: ^2.3.0
- 插件使用:
import 'package:connectivity_plus/connectivity_plus.dart';
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
// I am connected to a mobile network.
} else if (connectivityResult == ConnectivityResult.wifi) {
// I am connected to a wifi network.
}
- 网络状态,采用枚举的方式定义:
/// Connection status check result.
enum ConnectivityResult {
/// Bluetooth: Device connected via bluetooth
bluetooth,
/// WiFi: Device connected via Wi-Fi
wifi,
/// Ethernet: Device connected to ethernet network
ethernet,
/// Mobile: Device connected to cellular network
mobile,
/// None: Device not connected to any network
none
}
调试log封装
log对于网络的的调试来说还是有作用的。
可以自己写,比如用debugPrint();
或者通过常数(都是bool变量)kReleaseMode,kDebugMode,kProfileMode判断模式,然后调用print()打印log
也可以用插件,比如:pretty_dio_logger
封装哪个API?
最上层的API就是常用的get(), post()等等
中间层的API是request()
最底层的API是fetch()
其实,封装哪层都是有道理的,这里选择第二层的request();这点和AFNetworking很像:既可以直接用最顶层的Get,POST方法,也可以用底层一点,自己组建UIRequest的方法。
下面的文章就是封装request()的例子。Method有多种,可以用一个枚举来表示。
enum Method { GET, POST, DELETE, PUT, PATCH, HEAD }
//使用:MethodValues[Method.POST]
const MethodValues = {
Method.GET: "get",
Method.POST: "post",
Method.DELETE: "delete",
Method.PUT: "put",
Method.PATCH: "patch",
Method.HEAD: "head",
};
这种定义枚举,并且给枚举赋值的方式很常用。枚举用来做选择,枚举的值用来做实际的参数等,很方便。
- 对于request()方法的封装。这里把Future变成了block,不是非常好。但是,适合用惯了AFNetworking的。
// 请求,返回参数为 T
// method:请求方法,Method.POST等
// path:请求地址
// params:请求参数
// success:请求成功回调
// error:请求失败回调
static Future request(Method method, String path, dynamic params,
{required Success success, required Fail fail}) async {
try {
//没有网络
var connectivityResult = await (new Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
_onError(ExceptionHandle.net_error, '网络异常,请检查你的网络!', fail);
// BotToast.cleanAll();
return;
}
Dio? _dio = createInstance();
var data;
var queryParameters;
if (method == Method.GET) {
queryParameters = params;
}
if (method == Method.POST||method == Method.PUT||method == Method.DELETE) {
data = params;
}
Map headers = {
'Accept': 'application/json,*/*',
'Content-Type': 'application/json',
'Authorization': LocalStorage.get(LocalStorageKeys.TOKENHEAD)!=null?
LocalStorage.get(LocalStorageKeys.TOKENHEAD)+' '+LocalStorage.get(LocalStorageKeys.TOKEN):'',
};
Response response = await _dio!.request(path,
data: data,
queryParameters: queryParameters,
options: Options(method: MethodValues[method],headers: headers,responseType: ResponseType.plain));
if (response != null) {
if (success != null) {
success(response);
}
} else {
_onError(ExceptionHandle.unknown_error, '未知错误', fail);
}
} on DioError catch (e) {
// BotToast.cleanAll();
// ToastUtils.showText('服务异常,请稍后再试!');
LogUtils.print_('请求出错:' + e.toString());
final NetError netError = ExceptionHandle.handleException(e);
// _onError(netError.code, netError.msg, fail);
_onError(netError.code, e.error.source, fail);
}
}
}
参考文章
Flutter Dio源码分析(四)--封装