前言:
image.png明天就是五一,小伙伴们如何打算自己的假期生活呢?最近也找了份新工作,刚开始都是些琐碎的事情的安排,修改下界面以及修改下bug!没有过多的技术含量,也许这就是刚进入一家公司,熟悉下他们的代码的最快速的方式吧!首先祝大家五一快乐。
说到Flutter其实前两年就有关注,简单写写widget等,但最近面试,flutter似乎成了移动端必备的技能,简单的会写写widget已经不能满足于找工作,现在很多的创业型公司更多的尝试使用Flutter作为移动端开发,即使是在大厂,会使用Flutter写项目也是会加分的。为此学习Flutter也提上日程,破在眉睫的事。
因为之前学习过Flutter的UI相关的,所以首篇Flutter直接就从网络框架入手学习。
从Flutter中文网我们可以了解到Flutter自身使用的网络请求数据使用的是HttpClient,由于功能较弱,至于功能弱具体是指的什么,文档也没能说明白,可能是指的支持请求方法不是很全包括常用的GET、POST等请求方式,但目前使用更多的是DIO网络请求框架,它除了上面的两种以外,还支持PUT、DELETE等。
学习网络框架前我们的先学习Flutter对数据的解码和编码也就是对数据进行解析。在Android中我们可以使用GSON对数据进行处理,比方说把JSON数据转换成实体对象以及把实体对象实例转换成JSON,那么在Flutter又是如何对数据进行处理的呢,这里我们就的引入dart:convert,通过这个库,我们就可以对我们的数据进行解析啦!!!
Flutter使用convert将Map转换成String ------------jsonEncode
Map<String,dynamic> map = new Map();
map["username"] = "ptrtony";
map["age"] = 27;
String json = fromJson(map);
print(json);
return json;
fromJson(Map<String,dynamic> maps){
return jsonEncode(maps);
}
Flutter使用covert将String转换成Map -------------jsonDecode
String fromJsonMethod(){
Map<String,dynamic> map = new Map();
map["username"] = "ptrtony";
map["age"] = 27;
String json = fromJson(map);
print(json);
return json;
}
void toJsonMethod(){
Map<String,dynamic> map= toJson(fromJsonMethod());
print(map);
}
toJson(String json){
return jsonDecode(json);
}
从上面的例子我们可以发现,使用convert是将String转换成Map或则反之Map转传成String,但对于我们Android开发来说我们更习惯于将Json数据转换成实体或是将实体转换成Json数据,那么我们就要对数据做下封装成我们比较熟悉的json解析。
第一种方式手写
class TestMo {
String name;
int count;
TestMo(this.name, this.count);
TestMo.fromJson(Map<String, dynamic> json) {
name = json["name"];
count = json["count"];
}
String toJson(TestMo testMo) {
Map<String, dynamic> json = Map();
json["name"] = testMo.name;
json["count"] = testMo.count;
return jsonEncode(json);
}
}
当然了,对于这种没有任何技术含量又比较耗时的工作,我们可以直接使用json_serializable插件来处理
第一步安装:
dev_dependencies:
json_serializable: ^3.5.0
点击Pub get 安装好后,在类TestMo加入注解@JsonSerializable() 然后执行build
import 'package:blibli_app/test/result.dart';
import 'package:json_annotation/json_annotation.dart';
@JsonSerializable()
class TestMo {
String name;
int count;
TestMo(this.name, this.count);
///当然在正常的实际开发过程中,数据集可能比较于我这两条多的多的多,但我们只需要加入以下两行模版代码就可以了
factory Result.fromJson(Map<String,dynamic> json) => _$ResultFromJson(json);
Map<String,dynamic> toJson() => _$ResultToJson(this);
}
json解析的准备工作已经完成,接下来我们就开始Flutter的网络框架的搭建吧
接下来我们再来封装下请求BaseRequest类,我们在封装前,首先来分析下我们需要做什么工作,网络的请求需要用到HTTP的什么知识以及其原理。那么接下来就以我个人的理解,我们就来简单的实现下BaseRequest类的封装吧
HTTP指的是超文本的传输协议,在C/S架构中,客户端向服务器端发送数据由请求行、请求头、以及请求体构成
请求行又包括请求方式、请求的服务器地址(服务器地址URL) 以及HTTP的版本目前我们使用的版本是HTTP1.1
接下来我们就来编写下请求行的相关代码实现,首先请求方式有许多,比如上面提到的GET、POST、PUT、DELETE,那么我们在设计的时候该如何考虑这么多请求方式呢 这里我们可以定义枚举HttpMethod
enum HttpMethod { GET, POST, DELETE,PUT }
在Flutter中我们可以使用Uri提供的api来处理URL,当然在请求的时候Flutter提供了两种网络传输方式,分别是HTTP、HTTPS,在Uri提供的API中也提供了两种方式的实现
factory Uri.https(String authority, String unencodedPath,
[Map<String, dynamic>? queryParameters]) = _Uri.https;
factory Uri.http(String authority, String unencodedPath,
[Map<String, dynamic>? queryParameters]) = _Uri.http;
接下来我们可以定义authority的方法返回服务器地址,path作为接口api的访问路径,同时我们可以定义bool值来对我们传输的那种方式的判断useHttps
当然我们在上传数据到服务器需要上传一些参数,然而这些参数更多的是放在访问路径的后面
通常是http://xxxxxx?xxx&xxx&xxx ,这种也非常好理解,"?"前包括网络数据传输方式http还是https以及服务器地址(通常叫做服务器域名)以及访问服务器的路径地址,"?"后就是请求参数了,这里的参数可能有多个,以key、value的方式,这里我们就可以定义一个pathParams的Map集合
接下来我们可以定义一个方法url来拼接完整的接口地址包括请求的参数,具体的代码如下:
String url() {
Uri uri;
var pathStr = path();
//拼接Path参数
if (pathParams != null) {
if (pathStr.endsWith("/")) {
pathStr = "$pathStr$pathParams";
} else {
pathStr = "$pathStr/$pathParams";
}
}
//http和https的切换
if (useHttps) {
uri = Uri.https(authority(), pathStr, params);
} else {
uri = Uri.http(authority(), pathStr, params);
}
print("url:" + uri.toString());
return uri.toString();
}
接下来我们再来考虑下请求头,请求头的参数也是key、value传递的,那么我们可以定义Map来存储请求头的参数
Map<String, dynamic> header = {
'course-flag': 'fa',
//访问令牌,在公告栏中去获取
'auth-token': 'MjAyMC0wNi0yMyAwMzoyNTowMQ==fa'
};
///添加Header
BaseRequest addHeader(String k, Object v) {
header[k] = v.toString();
return this;
}
这样简单的BaseRequest也就封装好了
我们首先来回顾下,BaseRequest中包含哪些内容,首先由请求方式HttpMethod的成员变量,authority服务器地址以及访问地址path和请求参数pathParams拼接的url方法以及添加请求头的addHeader方法
完整的代码如下:
enum HttpMethod { GET, POST, DELETE }
///基础请求
abstract class BaseRequest {
var pathParams;
var useHttps = true;
HttpMethod httpMethod();
String authority() {
return "api.devio.org";
}
String path();
String url() {
Uri uri;
var pathStr = path();
//拼接Path参数
if (pathParams != null) {
if (pathStr.endsWith("/")) {
pathStr = "$pathStr$pathParams";
} else {
pathStr = "$pathStr/$pathParams";
}
}
//http和https的切换
if (useHttps) {
uri = Uri.https(authority(), pathStr, params);
} else {
uri = Uri.http(authority(), pathStr, params);
}
if (needLogin()) {
addHeader(LoginDao.BOARDING_PASS, LoginDao.getBoardingPass());
}
print("url:" + uri.toString());
return uri.toString();
}
bool needLogin();
Map<String, String> params = Map();
///添加参数
BaseRequest add(String k, Object v) {
params[k] = v.toString();
return this;
}
Map<String, dynamic> header = {
'course-flag': 'fa',
//访问令牌,在公告栏中去获取
'auth-token': 'MjAyMC0wNi0yMyAwMzoyNTowMQ==fa'
};
///添加Header
BaseRequest addHeader(String k, Object v) {
header[k] = v.toString();
return this;
}
}
接下来我们就接着来封装下我们使用的DIO网络库,首先我们可以定义一个抽象类。主要的目的就是解偶,因为我们在设计的时候需要注意的是我们开发的项目可能会运营很多年,但对于底层的架构需要做到灵活多变,比如说DIO网络库不在维护,或者有更好用的网络库,我们如何在不改动底层的框架的前提下,非常方便的切换呢?这里我们就要用到抽象或者接口,也就是单一职责设计模式的使用。
那么这个抽象类需要做哪些事呢?其实上面我们已经封装了网路的请求类,接下来就是把这个请求的数据发送到服务器即可,当然我们也的把服务器返回的数据进行处理,这里就要讲到服务器返回的数据又包括哪些数据呢?其实和客户端发送数据有点类似,包括响应行、响应体等。响应行又包括响应码?如请求成功200 请求失败401 或500等错误响应码
也就是我们需要一个发送请求数据的方法send以及返回的数据以及请求失败的处理
///网络请求抽象类
abstract class NetAdapter{
Future<HiNetResponse<T>>send<T>(BaseRequest request);
}
///统一网络层返回格式
class NetResponse<T>{
T data;
int statusCode;
String statusMessage;
dynamic extra;
BaseRequest request;
HiNetResponse({this.data, this.request,this.statusCode, this.statusMessage, this.extra});
@override
String toString() {
if(data is Map){
return json.encode(data);
}
return data.toString();
}
}
准备工作已经准备就绪,接下来我们再来封装DioAdapter 也就是网络库,其实到这里就非常简单了 ,我们只需要继承NetAdapter然后根据DIO网络库的相关API进行传参就可以了,具体封装代码如下:
class DioAdapter extends NetAdapter {
@override
Future<HiNetResponse<T>> send<T>(BaseRequest request) async{
var response, options = Options(headers: request.header);
var error;
try {
if (request.httpMethod() == HttpMethod.GET) {
response = Dio().get(request.url(), options: options);
} else if (request.httpMethod() == HttpMethod.POST) {
response =
Dio().post(request.url(), data: request.params, options: options);
} else if (request.httpMethod() == HttpMethod.DELETE) {
response =
Dio().delete(request.url(), data: request.params, options: options);
}
} on DioError catch (e) {
error = e;
response = e.response;
}
if (error != null) {
///抛出我们的异常
throw HiNetError(response?.statusCode ?? -1, error.toString(),
data: buildRes(request, response));
}
return buildRes(request, response);
}
///构建HiNetResponse
HiNetResponse buildRes(BaseRequest request, Response response) {
return HiNetResponse(
data: response.data,
request: request,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
extra: response);
}
}
网络框架也就封装完毕,如何使用呢
接下来我们直接以登录举个🌰:
class LoginRequest extends BaseRequest{
@override
HttpMethod httpMethod() {
return HttpMethod.POST;
}
@override
bool needLogin() {
return false;
}
@override
String path() {
return "uapi/user/login";
}
}
DIO网络请求:
send(String userName, String password, {imoocId, orderId}) async {
BaseRequest request = LoginRequest();
request
.add("userName", userName)
.add("password", password)
.add("imoocId", imoocId)
.add("orderId", orderId);
var result = await HiNet.getInstance().fire(request);
print(result);
return result;
}
网友评论