刚把公司的一个小内部项目转成flutter应用,flutter刚出来到现在也好几年过去了,现在使用的原因是,flutter各方面已经稳点了,开源库也有很多,问题各个论坛也有很好的解答,整体使用下来,datt语言保持了kotlin的使用味道,反正我用起来真的很爽,与java类似,添加了很多语法糖,回调在dart中真的是一个很大胆的尝试,但是使用下来还是非常棒的这里我会将我写的model提供出来。是对于dio网络请求的封装,总共分三层,配置启动层,中间层和具体使用,我自己使用的很香,你可以参考我这种写法去写你的中间层
在你的pubspec.yaml文件中加入依赖
dependencies:
flutter:
sdk: flutter
dio: ^2.1.13
一般你的网络回调数据都是类似下面这种格式:
class BaseResp<T> {
String status;
int code;
String message;
T data;
BaseResp(this.status, this.code, this.message, this.data);
@override
String toString() {
StringBuffer sb = new StringBuffer('{');
sb.write("\"status\":\"$status\"");
sb.write(",\"code\":$code");
sb.write(",\"message\":\"$message\"");
sb.write(",\"data\":\"$data\"");
sb.write('}');
return sb.toString();
}
}
范型为你的基础数据,可能是list也可能是其他所有类型,后面的中间层会使用到
还有一种类型,为后台返回的是流,那么可以使用下面这个类型
class BaseRespR<T> {
String status;
int code;
String message;
T data;
Response response;
BaseRespR(this.status, this.code, this.message, this.data, this.response);
@override
String toString() {
StringBuffer sb = new StringBuffer('{');
sb.write("\"status\":\"$status\"");
sb.write(",\"code\":$code");
sb.write(",\"message\":\"$message\"");
sb.write(",\"data\":\"$data\"");
sb.write('}');
return sb.toString();
}
}
response为dio中的类,
链接的path工具,如你的接口的不同路径,不包括baseurl
/**
* @Author: Nimodou
* @Blog: https://blog.csdn.net/qq_28535319
* @Email: [email protected]
* @Email: [email protected]
* @Date: 2019/7/27
* @Description:
*/
///
class ApiUrls{
//登陆
static const String LOGIN="xxxxx/sss/xxx";
//获取设备列表
static const String GETDEVICELIST="xxxxx/xxxx";
//获取绑定列表
static const String GETDEVICEBINDLIST="xxxx/xxxxxx";
//baseurl
static const BASEURL="http://www.hhhh-cloud.com/sss/sss/sss/v1/";
static String getPath({String path: ''}) {
StringBuffer sb = new StringBuffer(path);
return sb.toString();
}
}
/**
* @Author: Nimodou
* @Blog: https://blog.csdn.net/qq_28535319
* @Email: [email protected]
* @Email: [email protected]
* @Date: 2019/7/27
* @Description:
*/
///
/// 请求方法.
class Method {
static final String get = "GET";
static final String post = "POST";
static final String put = "PUT";
static final String head = "HEAD";
static final String delete = "DELETE";
static final String patch = "PATCH";
}
///Http配置.
class HttpConfig {
/// constructor.
HttpConfig({
this.status,
this.code,
this.msg,
this.data,
this.options,
this.pem,
this.pKCSPath,
this.pKCSPwd,
});
/// BaseResp [String status]字段 key, 默认:status.
String status;
/// BaseResp [int code]字段 key, 默认:errorCode.
String code;
/// BaseResp [String msg]字段 key, 默认:errorMsg.
String msg;
/// BaseResp [T data]字段 key, 默认:data.
String data;
/// Options.
BaseOptions options;
/// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
/// PEM证书内容.
String pem;
/// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
/// PKCS12 证书路径.
String pKCSPath;
/// 详细使用请查看dio官网 https://github.com/flutterchina/dio/blob/flutter/README-ZH.md#Https证书校验.
/// PKCS12 证书密码.
String pKCSPwd;
}
/// 单例 DioUtil.
/// debug模式下可以打印请求日志. DioUtil.openDebug().
/// dio详细使用请查看dio官网(https://github.com/flutterchina/dio).
class DioUtil {
static final DioUtil _singleton = DioUtil._init();
static Dio _dio;
/// BaseResp [String status]字段 key, 默认:status.
String _statusKey = "status";
/// BaseResp [int code]字段 key, 默认:errorCode.
String _codeKey = "code";
/// BaseResp [String msg]字段 key, 默认:errorMsg.
String _msgKey = "message";
/// BaseResp [T data]字段 key, 默认:data.
String _dataKey = "data";
/// Options.
BaseOptions _options = getDefOptions();
/// PEM证书内容.
String _pem;
/// PKCS12 证书路径.
String _pKCSPath;
/// PKCS12 证书密码.
String _pKCSPwd;
/// 是否是debug模式.
static bool _isDebug = false;
static DioUtil getInstance() {
return _singleton;
}
factory DioUtil() {
return _singleton;
}
DioUtil._init() {
_dio = new Dio(_options);
_dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志
}
/// 打开debug模式.
static void openDebug() {
_isDebug = true;
}
void setCookie(String cookie) {
Map<String, dynamic> _headers = new Map();
_headers["Cookie"] = cookie;
_dio.options.headers.addAll(_headers);
}
/// set Config.
void setConfig(HttpConfig config) {
_statusKey = config.status ?? _statusKey;
_codeKey = config.code ?? _codeKey;
_msgKey = config.msg ?? _msgKey;
_dataKey = config.data ?? _dataKey;
_mergeOption(config.options);
_pem = config.pem ?? _pem;
if (_dio != null) {
_dio.options = _options;
if (_pem != null) {//证书校验
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==_pem){ // Verify the certificate
return true;
}
return false;
};
};
}
if (_pKCSPath != null) {
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
SecurityContext sc = new SecurityContext();
//file is the path of certificate
sc.setTrustedCertificates(_pKCSPath);
HttpClient httpClient = new HttpClient(context: sc);
return httpClient;
};
}
}
}
/// Make http request with options.
/// [method] The request method.
/// [path] The url path.
/// [data] The request data
/// [options] The request options.
/// 返回 status code msg data .
Future<BaseResp<T>> request<T>(String method, String path,
{data, Options options, CancelToken cancelToken, Map<String, dynamic> queryParameters,String pathReplace,String match }) async {
if(match!=null&&pathReplace!=null){
path=path.replaceAll(match, pathReplace);
}
// print("$path");
print(data);
Response response = await _dio.request(path,
data: data,
queryParameters:queryParameters,
options: _checkOptions(method, options),
cancelToken: cancelToken);
_printHttpLog(response);
String _status;
int _code;
String _msg;
T _data;
if (response.statusCode == HttpStatus.ok ||
response.statusCode == HttpStatus.created) {
try {
if (response.data is Map) {
_status = (response.data[_statusKey] is int)
? response.data[_statusKey].toString()
: response.data[_statusKey];
_code = (response.data[_codeKey] is String)
? int.tryParse(response.data[_codeKey])
: response.data[_codeKey];
_msg = response.data[_msgKey];
_data = response.data[_dataKey];
} else {
Map<String, dynamic> _dataMap = _decodeData(response);
_status = (_dataMap[_statusKey] is int)
? _dataMap[_statusKey].toString()
: _dataMap[_statusKey];
_code = (_dataMap[_codeKey] is String)
? int.tryParse(_dataMap[_codeKey])
: _dataMap[_codeKey];
_msg = _dataMap[_msgKey];
_data = _dataMap[_dataKey];
}
return new BaseResp(_status, _code, _msg, _data);
} catch (e) {
return new Future.error(new DioError(
response: response,
message: "data parsing exception...",
type: DioErrorType.RESPONSE,
));
}
}
return new Future.error(new DioError(
response: response,
message: "statusCode: $response.statusCode, service error",
type: DioErrorType.RESPONSE,
));
}
/// Make http request with options.
/// [method] The request method.
/// [path] The url path.
/// [data] The request data
/// [options] The request options.
/// 返回 status code msg data Response.
Future<BaseRespR<T>> requestR<T>(String method, String path,
{data, Options options, CancelToken cancelToken,Map<String, dynamic> queryParameters}) async {
Response response = await _dio.request(path,
data: data,
queryParameters:queryParameters,
options: _checkOptions(method, options),
cancelToken: cancelToken);
_printHttpLog(response);
String _status;
int _code;
String _msg;
T _data;
if (response.statusCode == HttpStatus.ok ||
response.statusCode == HttpStatus.created) {
try {
if (response.data is Map) {
_status = (response.data[_statusKey] is int)
? response.data[_statusKey].toString()
: response.data[_statusKey];
_code = (response.data[_codeKey] is String)
? int.tryParse(response.data[_codeKey])
: response.data[_codeKey];
_msg = response.data[_msgKey];
_data = response.data[_dataKey];
} else {
Map<String, dynamic> _dataMap = _decodeData(response);
_status = (_dataMap[_statusKey] is int)
? _dataMap[_statusKey].toString()
: _dataMap[_statusKey];
_code = (_dataMap[_codeKey] is String)
? int.tryParse(_dataMap[_codeKey])
: _dataMap[_codeKey];
_msg = _dataMap[_msgKey];
_data = _dataMap[_dataKey];
}
return new BaseRespR(_status, _code, _msg, _data, response);
} catch (e) {
return new Future.error(new DioError(
response: response,
message: "data parsing exception...",
type: DioErrorType.RESPONSE,
));
}
}
return new Future.error(new DioError(
response: response,
message: "statusCode: $response.statusCode, service error",
type: DioErrorType.RESPONSE,
));
}
/// Download the file and save it in local. The default http method is "GET",you can custom it by [Options.method].
/// [urlPath]: The file url.
/// [savePath]: The path to save the downloading file later.
/// [onProgress]: The callback to listen downloading progress.please refer to [OnDownloadProgress].
Future<Response> download(
String urlPath,
savePath, {
ProgressCallback onProgress,
CancelToken cancelToken,
data,
Options options,
}) {
return _dio.download(urlPath, savePath,
onReceiveProgress: onProgress,
cancelToken: cancelToken,
data: data,
options: options);
}
Future<BaseResp<T>> upload<T>(String method, String path,
{data, Options options, CancelToken cancelToken,Map<String, dynamic> queryParameters}) async {
// FormData formData = new FormData.from(data);
/*FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
"file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"),
//支持直接上传字节数组 (List) ,方便直接上传内存中的内容
"file2": new UploadFileInfo.fromBytes(
utf8.encode("hello world"), "word.txt"),
// 支持文件数组上传
"files": [
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"),
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")
]
});*/
Response response = await _dio.post(path, data: data,options: _checkOptions(method, options),
cancelToken: cancelToken,queryParameters:queryParameters);
String _status;
int _code;
String _msg;
T _data;
if (response.statusCode == HttpStatus.ok ||
response.statusCode == HttpStatus.created) {
try {
if (response.data is Map) {
_status = (response.data[_statusKey] is int)
? response.data[_statusKey].toString()
: response.data[_statusKey];
_code = (response.data[_codeKey] is String)
? int.tryParse(response.data[_codeKey])
: response.data[_codeKey];
_msg = response.data[_msgKey];
_data = response.data[_dataKey];
} else {
Map<String, dynamic> _dataMap = _decodeData(response);
_status = (_dataMap[_statusKey] is int)
? _dataMap[_statusKey].toString()
: _dataMap[_statusKey];
_code = (_dataMap[_codeKey] is String)
? int.tryParse(_dataMap[_codeKey])
: _dataMap[_codeKey];
_msg = _dataMap[_msgKey];
_data = _dataMap[_dataKey];
}
return new BaseResp(_status, _code, _msg, _data);
} catch (e) {
return new Future.error(new DioError(
response: response,
message: "data parsing exception...",
type: DioErrorType.RESPONSE,
));
}
}
return new Future.error(new DioError(
response: response,
message: "statusCode: $response.statusCode, service error",
type: DioErrorType.RESPONSE,
));
}
/// decode response data.
Map<String, dynamic> _decodeData(Response response) {
if (response == null ||
response.data == null ||
response.data.toString().isEmpty) {
return new Map();
}
return json.decode(response.data.toString());
}
/// check Options.
Options _checkOptions(method, options) {
Options opNew;
if (options == null) {
opNew = new Options(method: method,responseType: ResponseType.json,contentType: ContentType.parse("application/json; charset=utf-8"));
}else{
opNew=options;
}
return opNew;
}
/// merge Option.
void _mergeOption(BaseOptions opt) {
_options.method = opt.method ?? _options.method;
_options.headers = (new Map.from(_options.headers))..addAll(opt.headers);
_options.baseUrl = opt.baseUrl ?? _options.baseUrl;
_options.connectTimeout = opt.connectTimeout ?? _options.connectTimeout;
_options.receiveTimeout = opt.receiveTimeout ?? _options.receiveTimeout;
_options.responseType = opt.responseType ?? _options.responseType;
// _options.data = opt.data ?? _options.data;
_options.extra = (new Map.from(_options.extra))..addAll(opt.extra);
_options.contentType = opt.contentType ?? _options.contentType;
_options.validateStatus = opt.validateStatus ?? _options.validateStatus;
_options.followRedirects = opt.followRedirects ?? _options.followRedirects;
}
/// print Http Log.
void _printHttpLog(Response response) {
if (!_isDebug) {
return;
}
try {
print("----------------Http Log----------------" +
"\n[statusCode]: " +
response.statusCode.toString() +
"\n[request ]: " +
_getOptionsStr(response.request));
_printDataStr("reqdata ", response.request.data);
_printDataStr("response", response.data);
} catch (ex) {
print("Http Log" + " error......");
}
}
/// get Options Str.
String _getOptionsStr(RequestOptions request) {
return "method: " +
request.method +
" baseUrl: " +
request.baseUrl +
" path: " +request.path
;
}
/// print Data Str.
void _printDataStr(String tag, Object value) {
String da = value.toString();
while (da.isNotEmpty) {
if (da.length > 512) {
print("[$tag ]: " + da.substring(0, 512));
da = da.substring(512, da.length);
} else {
print("[$tag ]: " + da);
da = "";
}
}
}
/// get dio.
Dio getDio() {
return _dio;
}
/// create new dio.
static Dio createNewDio([BaseOptions options]) {
options = options ?? getDefOptions();
Dio dio = new Dio(options);
return dio;
}
/// get Def Options.
static BaseOptions getDefOptions() {
BaseOptions options = new BaseOptions ();
options.contentType =
ContentType.parse("application/json; charset=utf-8");
options.connectTimeout = 10000 * 30;
options.receiveTimeout = 10000 * 30;
return options;
}
}
这里有
static final String get = “GET”;
static final String post = “POST”;
static final String put = “PUT”;
static final String head = “HEAD”;
static final String delete = “DELETE”;
static final String patch = “PATCH”;
等操作,这里并不是具体使用,我么用一个中间层将model与请求执行层耦合起来
首先你要生成你的数据模型的bean,比如一个用户信息UserBean等
class UserBean {
String value;
int timestamp;
int userId;
UserBean({this.value, this.timestamp, this.userId});
UserBean.fromJson(Map<String, dynamic> json) {
value = json['value'];
timestamp = json['timestamp'];
userId = json['userId'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['value'] = this.value;
data['timestamp'] = this.timestamp;
data['userId'] = this.userId;
return data;
}
}
这里有一个网站可以直接将接口数据转成Model格式,我经常使用,veryhappy,https://javiercbk.github.io/json_to_dart/ 把你的json数据放入文本框可以直接生成数据Model,然后复制到你创建的dart中即可
ff
然后就可以写中间层了,这里我就不一一把我所有的请求都放出来,我就放几个常用的
/**
* @Author: Nimodou
* @Blog: https://blog.csdn.net/qq_28535319
* @Email: [email protected]
* @Email: [email protected]
* @Date: 2019/7/27
* @Description:
*/
///
///
class DioModelControl{
DioModelControl();
DioModelControl _dioModelControl;
DioModelControl.getInstans() {
if(null==_dioModelControl){
_dioModelControl = new DioModelControl();
}
}
/**
* 登陆
*/
Future<UserBean> logion(String loginLabel,String password,{Function printError,BuildContext context}) async {
BaseResp<Map<String,dynamic>> baseResp = await DioUtil().request<Map<String,dynamic>>(
Method.post, ApiUrls.getPath(path: ApiUrls.LOGIN),
data: {"loginLabel":loginLabel,"password":password}
);
UserBean userBean;
if(baseResp.code==200||baseResp.code==201||baseResp.code==204){
if (baseResp.data != null) {
userBean = UserBean.fromJson(baseResp.data);
}
return userBean;
}
return _error<UserBean>(baseResp,printError: printError,context: context);
}
/**
* 获取父设备列表
*/
Future<DeviceListBean> getDeviceList(String roomNo,String orgName,String devId,int pageNumber,{String parentDevId="000000000000",Function printError,BuildContext context}) async{
BaseResp<Map<String,dynamic>> baseResp=await DioUtil().request<Map<String,dynamic>>(
Method.post,ApiUrls.getPath(path:ApiUrls.GETDEVICELIST ),data: {"roomNo":roomNo,"orgName":orgName,"devId":devId,"parentDevId":parentDevId},queryParameters: {"pageNumber":pageNumber}
);
DeviceListBean deviceListBean;
if(baseResp.code==200||baseResp.code==201||baseResp.code==204){
if (baseResp.data != null) {
deviceListBean = DeviceListBean.fromJson(baseResp.data);
}
return deviceListBean;
}
return _error<DeviceListBean>(baseResp,printError: printError,context: context);
}
}
/**
* 根据名字获取机构
*/
Future<List<MechanismBean>> getMechanisListBeanWithName(String name,{Function printError,BuildContext context}) async{
BaseResp<List> baseResp=await DioUtil().request<List>(
Method.get,ApiUrls.getPath(path:ApiUrls.GETMECHANISLISTBEANWITHNAME ),queryParameters: {"name":name}
);
List<MechanismBean> list;
if(baseResp.code==200||baseResp.code==201||baseResp.code==204){
if (baseResp.data != null) {
list=baseResp.data.map((value){
return MechanismBean.fromJson(value);
}).toList();
}
return list;
}
return _error<List<MechanismBean>>(baseResp,printError: printError,context: context);
}
/**
* 删除设备
*/
Future<String> deleteDevice(int id,{Function printError,BuildContext context}) async{
BaseResp baseResp=await DioUtil().request(
Method.delete,ApiUrls.getPath(path:ApiUrls.DELETEDEVICE ),match: "{id}",pathReplace: "$id"
);
String message;
if(baseResp.code==200||baseResp.code==201||baseResp.code==204){
if (baseResp.message != null) {
message = baseResp.message;
}
return message;
}
return _error<String>(baseResp,printError: printError,context: context);
}
/**
* 上传文件
*/
Future<UploadBean> uploadFile(String directory,String mediaType,UploadFileInfo upfile,{Function printError,BuildContext context}) async{
FormData formData = new FormData.from({
"file": upfile
});
BaseResp<Map<String,dynamic>> baseResp=await DioUtil().request<Map<String,dynamic>>(
Method.post,ApiUrls.getPath(path:ApiUrls.UPLOADFILE ),
data: formData,
queryParameters: {
"directory":directory,"mediaType":mediaType,},
);
UploadBean uploadBean;
if(baseResp.code==200||baseResp.code==201||baseResp.code==204){
if (baseResp.data != null) {
uploadBean = UploadBean.fromJson(baseResp.data);
}
return uploadBean;
}
return _error<UploadBean>(baseResp,printError: printError,context: context);
}
//处理错误,可以自己在ui层.error处理
Future _error<T>(BaseResp baseResp,{Function printError,BuildContext context}){
if(baseResp.code==401&&context!=null){
RouteUtil.goLogin(context);
return new Future<T>.error(baseResp.message);
}
showToast(baseResp.message);
if(printError!=null){
printError(baseResp.message);
return new Future<T>.error(baseResp.message);
}else{
if(baseResp.code==401){
return new Future<T>.error("tokenerror");
}
return new Future<T>.error(baseResp.message);
}
}
注意这里query参数用queryParameters: ,map参数用data,path可以使用我删除设备的操作,上传文件用我例子中的即可
业务层就是具体使用了,我也写几个简单的例子,到这一步基本就是在你的state中实现了
///上传文件
void _sendUpload (){
UploadFileInfo upfile=UploadFileInfo(new File(filePath),filePath.split('/').last);
PopuUtils.showLoading(context, "正在上传");
DioModelControl.getInstans().uploadFile("assistant",widget.type==1? "voice":"video",upfile ).then((value){
ctMusic.text=value.urlPrefix+value.url;
widget.sceneResponseList[0]?.url=value.urlPrefix+value.url;
Navigator.of(context).pop();
RouteUtil.popAndBackResult(context, widget.sceneResponseList);
});
}
void _login(){
DioModelControl.getInstans()
.logion(_unameController.value.text,
_pwdController.value.text,printError: (value){
showToast(value);
})
.then((value) {
if (!ObjectUtil.isEmptyString(
value.value)) {
SpUtil.putString(
BaseConfig.keyAppToken,
value.value);
}
}).then((value){
RouteUtil.goMain(context);
});
}
void _onRefresh() {
_page=1;
_list.clear();
DioModelControl.getInstans().getDeviceList(_room_no, _org_name, _mac_str, _page,context: context).then(
(value){
if(value!=null&&value.items!=null){
_list.addAll(value.items);
if (mounted) setState(() {});
_refreshController.refreshCompleted();
}
}
);
}
具体的使用优化你可自己操作,如改变option,由于dart’的可选参数类型的设定,我们在不改变原有的代码基础上非常简单就能实现,我觉得flutter还是大有作为的,用起来反正很香
路还很长,慢慢走