前言
前面關(guān)于Flutter的講解部分我把關(guān)于flutter的基礎(chǔ)入門部分帶著大家梳理了一遍,那從本篇博客開始,我們開始進(jìn)入新的領(lǐng)域,也算是給進(jìn)階篇開個頭,今天我們來一塊學(xué)習(xí)一下Flutter中的網(wǎng)絡(luò)請求庫--->
Dio
,關(guān)于Flutter原生帶的Http使用起來不論在功能上還是擴(kuò)展上都不是那么的強(qiáng)大,鑒于此筆者在這里推薦大家在項(xiàng)目中使用Dio
封裝網(wǎng)絡(luò)請求庫。關(guān)于Http的使用讀者可自行查閱資料學(xué)習(xí)。
課程目標(biāo)
- 使用Dio完成最簡單的GET、POST請求
- 基于Dio封裝網(wǎng)絡(luò)請求庫,并使用自己封裝的網(wǎng)絡(luò)請求工具類完成GET、POST請求
- 了解
InterceptorsWrapper
攔截器 - 利用攔截器給網(wǎng)絡(luò)請求添加統(tǒng)一參數(shù)(如,token,userId等)
- 統(tǒng)一處理響應(yīng)返回?cái)?shù)據(jù)(做json轉(zhuǎn)實(shí)體或者格式化操作)
- 操作請求統(tǒng)一攔截
1.使用Dio完成簡單的GET、POST請求
1.1使用dio get請求一條json數(shù)據(jù)
getRequest() async {
Response response = await Dio()
.get('https://www.wanandroid.com/banner/json');
this.setState(() {
result= response.toString();
});
}
1.2 利用dio post請求注冊一個新用戶
postRequest() async {
var path = "https://www.wanandroid.com/user/register";
var params = {
"username": "aa112233",
"password": "123456",
"repassword": "123456"
};
Response response =
await Dio().post(path, queryParameters: params);
this.setState(() {
result= response.toString();
});
}
由于簡單的GET、跟POST請求操作起來比較簡單,我就不單獨(dú)附效果圖跟講解說明了,相信讀者從開始看系列博客到現(xiàn)在,讀懂上面的代碼已經(jīng)不在話下了,我把上面的get、跟post請求放到一張效果圖上代碼也貼到一塊供大家讀閱,另外感謝
玩安卓
提供的開放API作為本篇博客的請求測試用例。
效果圖
代碼
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class NetWorkPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => PageState();
}
class PageState extends State<NetWorkPage> {
var resultJson = "";
@override
void initState() {
super.initState();
}
getRequest() async {
Response response = await Dio()
.get('https://www.wanandroid.com/banner/json');
this.setState(() {
resultJson = response.toString();
});
}
postRequest() async {
var path = "https://www.wanandroid.com/user/register";
var params = {
"username": "aa112233",
"password": "123456",
"repassword": "123456"
};
Response response =
await Dio().post(path, queryParameters: params);
this.setState(() {
resultJson = response.toString();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dio網(wǎng)絡(luò)請求"),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MaterialButton(
color: Colors.pinkAccent,
child: Text("GET 請求"),
onPressed: () {
getRequest();
}),
MaterialButton(
color: Colors.blueAccent,
child: Text("POST 請求"),
onPressed: () {
postRequest();
}),
Expanded(
child: Padding(
padding: EdgeInsets.all(20),
child: Center(
child: resultJson.length <= 0
? Text("數(shù)據(jù)加載中...")
: Text(
resultJson,
style: TextStyle(fontSize: 16),
),
),
))
],
),
);
}
}
2.封裝自己的Dio網(wǎng)絡(luò)請求庫
在項(xiàng)目開發(fā)過程中隨著項(xiàng)目越寫越大,代碼量也會成倍的增加,隔離業(yè)務(wù)抽取共性代碼
的思想自然而然的出現(xiàn)在一個嚴(yán)于律己的開發(fā)者腦子里,特別是像網(wǎng)絡(luò)請求這種操作,好的封裝不僅會減少冗余代碼更能讓代碼層級變得清晰可讀,下面筆者就帶著自己的理解對Dio做一個簡單封裝,筆者自認(rèn)為才疏學(xué)淺,代碼中如有寫的不好的地方還請各位不吝賜教。
一般我們在處理工具類時都會用到單例的思想,做為一個項(xiàng)目全局的網(wǎng)絡(luò)請求工具類,我們同樣也把DioUtils
封裝成單例模式
static DioUtils getInstance() {
if (_instance == null) {
_instance = new DioUtils();
}
return _instance;
}
對于請求參數(shù)的初始化跟一些網(wǎng)絡(luò)配置,Dio為開發(fā)者提供了BaseOptions、Options、RequestOptions可選Options配置,三者的優(yōu)先級關(guān)系依次遞增
試想這樣一個場景:一般我們在對網(wǎng)絡(luò)請求工具類設(shè)置參數(shù),理論上是全局不能修改的,如果我們的業(yè)務(wù)需求必須讓我們修改配置參數(shù),比如基于不同的接口服務(wù)需要設(shè)置不同請求中的
header
內(nèi)容,這個時候利用options的優(yōu)先級我們就可以很輕松的處理這個問題,稍后我會在代碼里具體講解
先來看下BaseOptions給我們提供的可供配置的參數(shù):
BaseOptions({
String method,
int connectTimeout,
int receiveTimeout,
Iterable<Cookie> cookies,
this.baseUrl,
this.queryParameters,
Map<String, dynamic> extra,
Map<String, dynamic> headers,
ResponseType responseType = ResponseType.json,
ContentType contentType,
ValidateStatus validateStatus,
bool receiveDataWhenStatusError = true,
bool followRedirects = true,
int maxRedirects = 5,
RequestEncoder requestEncoder,
ResponseDecoder responseDecoder,
})
上面構(gòu)造方法中的屬性讀者基本都能見名知意做到自解釋,我就不單獨(dú)解釋了,貼上一段我在代碼里的配置:
//請求參數(shù)配置
_baseOptions = new BaseOptions(
baseUrl: BASE_URL,
//請求服務(wù)地址
connectTimeout: 5000,
//響應(yīng)時間
receiveTimeout: 5000,
headers: {
//需要配置請求的header可在此處配置
},
//請求的Content-Type,默認(rèn)值是[ContentType.json]. 也可以用ContentType.parse("application/x-www-form-urlencoded")
contentType: ContentType.json,
//表示期望以那種格式(方式)接受響應(yīng)數(shù)據(jù)。接受三種類型 `json`, `stream`, `plain`, `bytes`. 默認(rèn)值是 `json`,
responseType: ResponseType.json,
);
然后把_baseOptions
通過參數(shù)的形式傳入Dio實(shí)例中完成配置的初始化
//創(chuàng)建dio實(shí)例
_dio = new Dio(_baseOptions);
接下來我把我們開篇用DIO做的簡單GET、POST方法用我們新寫的工具類封裝完成后重新做請求,讓我們來一起感受下封裝帶來的便利。
封裝GET請求
/**
* get請求
*/
get(url, {data, options, cancleToken}) async {
print('get request path ------${url}-------請求參數(shù)${data}');
Response response;
try {
response = await _dio.get(url,
queryParameters: data, options: options, cancelToken: cancleToken);
print('get success ---${response.data}');
} on DioError catch (e) {
print('請求失敗---錯誤類型${e.type}');
}
return response.data;
}
利用我們封裝好的get請求方法,開篇的get請求只需改為:
getRequest() async {
String result= await DioUtils().get('/banner/json');
this.setState(() {
resultJson = result;
});
}
或者get的時候需要攜帶參數(shù),例如如下這樣一個請求
https://www.wanandroid.com/article/list/0/json?cid=60
方法:GET
參數(shù):cid 分類的id,上述二級目錄的id
對上面的url進(jìn)行分析
baseUrl
:https://www.wanandroid.com 我們已經(jīng)在工具類中初始化過了,所以不用設(shè)置
path
: /article/list/0/json 我們在get請求中需要傳入的url
data
:cid=60 我們封裝好的get請求中對應(yīng)的data數(shù)據(jù)
上述請求為:
getRequest() async {
var data = {
"cid": 60
};
String result = await DioUtils().get('/article/list/0/json', data: data);
this.setState(() {
resultJson = result;
});
}
在剛剛講BaseOptions時,我們提到還有Options、RequestOptions可供配置,我們提到可以利用Options的優(yōu)先級重新覆蓋掉原先在工具類里設(shè)置好的網(wǎng)絡(luò)配置,比如修改提前在header中設(shè)置好的請求內(nèi)容。
還是上述代碼,我先修改_baseOptions中的header的配置如下:
我們利用Options修改BaseUrl后的請求url
_baseOptions = new BaseOptions(
baseUrl: BASE_URL,
connectTimeout: 5000,
receiveTimeout: 5000,
headers: {
//預(yù)設(shè)好的header信息
"testHeader":"bb"
},
contentType: ContentType.json,
responseType: ResponseType.json,
);
還是上述請求,現(xiàn)在我們重新走一遍上述的get請求,看下log控制臺打印的header信息
然后我修改原先的get請求,給options添加RequestOptions,修改里面的header值如代碼所示:
getRequest() async {
var data = {"cid": 60};
RequestOptions requestOptions = new RequestOptions(headers: {"testHeader":"aaaa"});
String result = await DioUtils()
.get('/article/list/0/json', data: data,options: requestOptions);
this.setState(() {
resultJson = result;
});
}
運(yùn)行代碼,再次執(zhí)行g(shù)et請求,控制臺的header信息已經(jīng)被我們修改過了:
筆者只不過是借用這個例子讓讀者了解怎么去修改工具類里配置好的參數(shù),通過RequestOptions不僅僅是可以修改headerl
,基本你能在工具類設(shè)置的東西都能做修改,感興趣的讀者可以自行閱讀源碼測試,限于篇幅問題我這里就不展開講解了。
POST請求封裝
POST請求封裝跟GET類似,這里我就不過多分析了,我直接貼源碼了讀者可結(jié)合代碼自行對比差異
postRequest() async {
var params = {
"username": "aa112233",
"password": "123456",
"repassword": "123456"
};
String result = await DioUtils().post("/user/register",data: params);
this.setState(() {
resultJson = result;
});
}
3.利用攔截器給網(wǎng)絡(luò)請求添加統(tǒng)一參數(shù)
在DIo中我們可以通過Interceptors
為我們的網(wǎng)絡(luò)請求添加攔截器
_dio.interceptors.add()
我們通過_dio.interceptors.add()
方法可以根據(jù)不同業(yè)務(wù)為我們的工具類添加不同的攔截器,比如在網(wǎng)絡(luò)請求開始之前,我們給每個請求都添加統(tǒng)一的token,或者userId,或者我們可以對請求返回的數(shù)據(jù)做統(tǒng)一json格式化處理,對錯誤響應(yīng)統(tǒng)一處理,這些業(yè)務(wù)場景都可以通過interceptors
來完成,比如下面我的配置:
//可根據(jù)項(xiàng)目需要選擇性的添加請求攔截器
_dio.interceptors.add(
InterceptorsWrapper(onRequest: (RequestOptions requestions) async {
//此處可網(wǎng)絡(luò)請求之前做相關(guān)配置,比如會所有請求添加token,或者userId
requestions.queryParameters["token"] = "testtoken123443423";
requestions.queryParameters["userId"] = "123456";
return requestions;
}, onResponse: (Response response) {
//此處攔截工作在數(shù)據(jù)返回之后,可在此對dio請求的數(shù)據(jù)做二次封裝或者轉(zhuǎn)實(shí)體類等相關(guān)操作
return response;
}, onError: (DioError error) {
//處理錯誤請求
return error;
}),
);
}
現(xiàn)在我們通過工具類進(jìn)行的所有的網(wǎng)絡(luò)請求的url后面都會被加入token=“testtoken123443423”&userId=123456這樣兩個參數(shù),還是上面的GET請求,下面我們通過代碼跟控制臺的輸出內(nèi)容看下通過攔截器添加完通用參數(shù)的請求,log控制臺打印的請求參數(shù)信息
getRequest() async {
var data = {"cid": 60};
String result = await DioUtils()
.get('/article/list/0/json', data: data);
this.setState(() {
resultJson = result;
});
}
上面的GET請求,我們在參數(shù)里只添加了
cid = 60
的參數(shù),但是通過控制臺我們已經(jīng)清楚的看到token跟userId已經(jīng)通過攔截器的被我們添加到queryParameters
里面了。
限于篇幅問題,封裝好的DioUtils我就不貼出來了,附上源碼的github地址,供讀者參考,最后感謝玩安卓開放API平臺提供的測試API用于本篇博客中的測試用例。