Flutter,Dio從0到精通,一文到位

dioFlutter中文網(wǎng)開(kāi)源的一個(gè)強(qiáng)大的Dart Http請(qǐng)求庫(kù),支持Restful API、FormData、攔截器、請(qǐng)求取消、Cookie管理、文件上傳/下載、超時(shí)等...

目前已經(jīng)更新到2.1版本……

dependencies: dio:2.1.x? ? ?#latest version

簡(jiǎn)單上手

import 'package:dio/dio.dart';

Dio dio = new Dio();

Response response=await dio.get("https://www.google.com/");

print(response.data);

一,發(fā)起get請(qǐng)求

Response response;

response=await dio.get("/test?id=12&name=wendu")

print(response.data.toString());

// 請(qǐng)求參數(shù)也可以通過(guò)對(duì)象傳遞,上面的代碼等同于:

response=await dio.get("/test",data:{"id":12,"name":"wendu"})

print(response.data.toString());

二,發(fā)起post請(qǐng)求

response=await dio.post("/test",data:{"id":12,"name":"wendu"})

三,發(fā)起多個(gè)請(qǐng)求

response= await Future.wait([dio.post("/info"),dio.get("/token")]);

四,下載文件

response=await dio.download("https://www.google.com/","./xx.html")

五,發(fā)送FormData

FormData formData = new FormData.from({

? "name": "wendux",

? "age": 25,

});

response = await dio.post("/info", data: formData)

六,通過(guò)FormData長(zhǎng)傳多個(gè)文件

FormData formData = new FormData.from({

? "name": "wendux",

? "age": 25,

? "file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"),

? "file2": new UploadFileInfo(new File("./upload.txt"), "upload2.txt"),

? ? // 支持文件數(shù)組上傳

? "files": [

? ? ? new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"),

? ? ? new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")

? ? ]

});

response = await dio.post("/info", data: formData)

Dio? Apis?

創(chuàng)建一個(gè)Dio實(shí)例,并配置它

Dio dio = new Dio; // 使用默認(rèn)配置

// 配置dio實(shí)例

dio.options.baseUrl="https://www.xx.com/api"

dio.options.connectTimeout = 5000; //5s

dio.options.receiveTimeout=3000;

// 或者通過(guò)傳遞一個(gè) `options`來(lái)創(chuàng)建dio實(shí)例

Options options= new Options(

? ? baseUrl:"https://www.xx.com/api",

? ? connectTimeout:5000,

? ? receiveTimeout:3000

);

Dio dio = new Dio(options);

Dio實(shí)例的核心api是

Future<Response> request(String path, {data, Options options,CancelToken cancelToken})

例句:response=await?request("/test", data: {"id":12,"name":"xx"},?new?Options(method:"GET"));

為了方便使用,Dio提供了一些其他Restful API ,這些api都是request的別名。

Future<Response> get(path, {data, Options options,CancelToken cancelToken})

Future<Response> post(path, {data, Options options,CancelToken cancelToken})

Future<Response> put(path, {data, Options options,CancelToken cancelToken})

Future<Response> delete(path, {data, Options options,CancelToken cancelToken})

Future<Response> head(path, {data, Options options,CancelToken cancelToken})

Future<Response> put(path, {data, Options options,CancelToken cancelToken})

Future<Response> path(path, {data, Options options,CancelToken cancelToken})

Future<Response> download(String urlPath, savePath,

請(qǐng)求配置

{

? /// Http method.

? String method;

? /// 請(qǐng)求基地址,可以包含子路徑,如: "https://www.google.com/api/".

? String baseUrl;

? /// Http請(qǐng)求頭.

? Map<String, dynamic> headers;

? /// 連接服務(wù)器超時(shí)時(shí)間,單位是毫秒.

? int connectTimeout;

? ///? 響應(yīng)流上前后兩次接受到數(shù)據(jù)的間隔,單位為毫秒。如果兩次間隔超過(guò)[receiveTimeout],

? ///? [Dio] 將會(huì)拋出一個(gè)[DioErrorType.RECEIVE_TIMEOUT]的異常.

? ///? 注意: 這并不是接收數(shù)據(jù)的總時(shí)限.

? int receiveTimeout;

? /// 請(qǐng)求數(shù)據(jù),可以是任意類型.

? var data;

? /// 請(qǐng)求路徑,如果 `path` 以 "http(s)"開(kāi)始, 則 `baseURL` 會(huì)被忽略; 否則,

? /// 將會(huì)和baseUrl拼接出完整的的url.

? String path="";

? /// 請(qǐng)求的Content-Type,默認(rèn)值是[ContentType.JSON].

? /// 如果您想以"application/x-www-form-urlencoded"格式編碼請(qǐng)求數(shù)據(jù),

? /// 可以設(shè)置此選項(xiàng)為 `ContentType.parse("application/x-www-form-urlencoded")`,? 這樣[Dio]

? /// 就會(huì)自動(dòng)編碼請(qǐng)求體.

? ContentType contentType;

? /// [responseType] 表示期望以那種格式(方式)接受響應(yīng)數(shù)據(jù)。

? /// 目前 [ResponseType] 接受三種類型 `JSON`, `STREAM`, `PLAIN`.

? ///

? /// 默認(rèn)值是 `JSON`, 當(dāng)響應(yīng)頭中content-type為"application/json"時(shí),dio 會(huì)自動(dòng)將響應(yīng)內(nèi)容轉(zhuǎn)化為json對(duì)象。

? /// 如果想以二進(jìn)制方式接受響應(yīng)數(shù)據(jù),如下載一個(gè)二進(jìn)制文件,那么可以使用 `STREAM`.

? ///

? /// 如果想以文本(字符串)格式接收響應(yīng)數(shù)據(jù),請(qǐng)使用 `PLAIN`.

? ResponseType responseType;

? /// `validateStatus` 決定http響應(yīng)狀態(tài)碼是否被dio視為請(qǐng)求成功, 返回`validateStatus`

? ///? 返回`true` , 請(qǐng)求結(jié)果就會(huì)按成功處理,否則會(huì)按失敗處理.

? ValidateStatus validateStatus;

? /// 用戶自定義字段,可以在 [Interceptor]、[Transformer] 和 [Response] 中取到.

? Map<String, dynamic> extra;

}

響應(yīng)數(shù)據(jù)

當(dāng)請(qǐng)求成功時(shí)會(huì)返回一個(gè)Response對(duì)象,它包含如下字段:

{

? /// 響應(yīng)數(shù)據(jù),可能已經(jīng)被轉(zhuǎn)換了類型, 詳情請(qǐng)參考Options中的[ResponseType].

? var data;

? /// 響應(yīng)頭

? HttpHeaders headers;

? /// 本次請(qǐng)求信息

? Options request;

? /// Http status code.

? int statusCode;

? /// 響應(yīng)對(duì)象的自定義字段(可以在攔截器中設(shè)置它),調(diào)用方可以在`then`中獲取.

? Map<String, dynamic> extra;

}

Response response=await dio.get("https://www.google.com");

print(response.data);

print(response.headers);

print(response.request);

print(statusCode);

攔截器

每一個(gè) Dio 實(shí)例都有一個(gè)請(qǐng)求攔截器?RequestInterceptor?和一個(gè)響應(yīng)攔截器?ResponseInterceptor, 通過(guò)攔截器你可以在請(qǐng)求之前或響應(yīng)之后(但還沒(méi)有被?then?或?catchError處理)做一些統(tǒng)一的預(yù)處理操作。

dio.interceptor.request.onSend = (Options options){

? ? // 在請(qǐng)求被發(fā)送之前做一些事情

? ? return options; //continue

? ? // 如果你想完成請(qǐng)求并返回一些自定義數(shù)據(jù),可以返回一個(gè)`Response`對(duì)象或返回`dio.resolve(data)`。

? ? // 這樣請(qǐng)求將會(huì)被終止,上層then會(huì)被調(diào)用,then中返回的數(shù)據(jù)將是你的自定義數(shù)據(jù)data.

? ? //

? ? // 如果你想終止請(qǐng)求并觸發(fā)一個(gè)錯(cuò)誤,你可以返回一個(gè)`DioError`對(duì)象,或返回`dio.reject(errMsg)`,

? ? // 這樣請(qǐng)求將被中止并觸發(fā)異常,上層catchError會(huì)被調(diào)用。

}

dio.interceptor.response.onSuccess = (Response response) {

? ? // 在返回響應(yīng)數(shù)據(jù)之前做一些預(yù)處理

? ? return response; // continue

};

dio.interceptor.response.onError = (DioError e){

? ? // 當(dāng)請(qǐng)求失敗時(shí)做一些預(yù)處理

? ? return e;//continue

}

如果你想一處攔截器,可以將它們置為null。

dio.interceptor.request.onSend=null;

dio.interceptor.response.onSuccess=null;

dio.interceptor.response.onError=null;

完成和終止請(qǐng)求、響應(yīng)

在所有攔截器中,你都可以改變請(qǐng)求執(zhí)行流, 如果你想完成請(qǐng)求/響應(yīng)并返回自定義數(shù)據(jù),你可以返回一個(gè)?Response?對(duì)象或返回?dio.resolve(data)的結(jié)果。 如果你想終止(觸發(fā)一個(gè)錯(cuò)誤,上層catchError會(huì)被調(diào)用)一個(gè)請(qǐng)求/響應(yīng),那么可以返回一個(gè)DioError?對(duì)象或返回?dio.reject(errMsg)?的結(jié)果.

dio.interceptor.request.onSend = (Options options){

? ? return dio.resolve("fake data")

}

Response response= await dio.get("/test");

print(response.data);//"fake data"

攔截器中支持異步任務(wù)

攔截器中不僅支持同步任務(wù),而且也支持異步任務(wù), 下面是在請(qǐng)求攔截器中發(fā)起異步任務(wù)的一個(gè)實(shí)例:

dio.interceptor.request.onSend = (Options options) async{

? ? //...If no token, request token firstly.

? ? Response response = await dio.get("/token");

? ? //Set the token to headers

? ? options.headers["token"] = response.data["data"]["token"];

? ? return options; //continue

}

Lock/unlock 攔截器

你可以通過(guò)調(diào)用攔截器的?lock()/unlock?方法來(lái)鎖定/解鎖攔截器。一旦請(qǐng)求/響應(yīng)攔截器被鎖定,接下來(lái)的請(qǐng)求/響應(yīng)將會(huì)在進(jìn)入請(qǐng)求/響應(yīng)攔截器之前排隊(duì)等待,直到解鎖后,這些入隊(duì)的請(qǐng)求才會(huì)繼續(xù)執(zhí)行(進(jìn)入攔截器)。這在一些需要串行化請(qǐng)求/響應(yīng)的場(chǎng)景中非常實(shí)用,后面我們將給出一個(gè)示例。

tokenDio=new Dio(); //Create a new instance to request the token.

tokenDio.options=dio;

dio.interceptor.request.onSend = (Options options) async{

? ? // If no token, request token firstly and lock this interceptor

? ? // to prevent other request enter this interceptor.

? ? dio.interceptor.request.lock();

? ? // We use a new Dio(to avoid dead lock) instance to request token.

? ? Response response = await tokenDio.get("/token");

? ? //Set the token to headers

? ? options.headers["token"] = response.data["data"]["token"];

? ? dio.interceptor.request.unlock()

? ? return options; //continue

}

Clear()

你也可以調(diào)用攔截器的clear()方法來(lái)清空等待隊(duì)列。

別名

當(dāng)請(qǐng)求攔截器被鎖定時(shí),接下來(lái)的請(qǐng)求將會(huì)暫停,這等價(jià)于鎖住了dio實(shí)例,因此,Dio示例上提供了請(qǐng)求攔截器lock/unlock的別名方法:

dio.lock() == dio.interceptor.request.lock()

dio.unlock() == dio.interceptor.request.unlock()

dio.clear() == dio.interceptor.request.clear()

假設(shè)這么一個(gè)場(chǎng)景:出于安全原因,我們需要給所有的請(qǐng)求頭中添加一個(gè)csrfToken,如果csrfToken不存在,我們先去請(qǐng)求csrfToken,獲取到csrfToken后,再發(fā)起后續(xù)請(qǐng)求。 由于請(qǐng)求csrfToken的過(guò)程是異步的,我們需要在請(qǐng)求過(guò)程中鎖定后續(xù)請(qǐng)求(因?yàn)樗鼈冃枰猚srfToken), 直到csrfToken請(qǐng)求成功后,再解鎖,代碼如下:

dio.interceptor.request.onSend = (Options options) {print('send request:path:${options.path},baseURL:${options.baseUrl}');if(csrfToken ==null) {print("no token,request token firstly...");//lock the dio.dio.lock();returntokenDio.get("/token").then((d) { options.headers["csrfToken"] = csrfToken = d.data['data']['token'];print("request token succeed, value: "+ d.data['data']['token']);print('continue to perform request:path:${options.path},baseURL:${options.path}');returnoptions; }).whenComplete(() => dio.unlock());// unlock the dio}else{ options.headers["csrfToken"] = csrfToken;returnoptions; } };

錯(cuò)誤處理?

當(dāng)請(qǐng)求過(guò)程中發(fā)生錯(cuò)誤時(shí), Dio 會(huì)包裝?Error/Exception?為一個(gè)?DioError:

try {

? ? //404

? ? await dio.get("https://wendux.github.io/xsddddd");

? } on DioError catch(e) {

? ? ? // The request was made and the server responded with a status code

? ? ? // that falls out of the range of 2xx and is also not 304.

? ? ? if(e.response) {

? ? ? ? print(e.response.data)

? ? ? ? print(e.response.headers)

? ? ? ? print(e.response.request)

? ? ? } else{

? ? ? ? // Something happened in setting up or sending the request that triggered an Error

? ? ? ? print(e.request)

? ? ? ? print(e.message)

? ? ? }

? }

DioError 字段

{

? /// 響應(yīng)信息, 如果錯(cuò)誤發(fā)生在在服務(wù)器返回?cái)?shù)據(jù)之前,它為 `null`

? Response response;

? /// 錯(cuò)誤描述.

? String message;

? /// 錯(cuò)誤類型,見(jiàn)下文

? DioErrorType type;

? /// 錯(cuò)誤棧信息,可能為null

? StackTrace stackTrace;

}

DioErrorType

enum DioErrorType {

? /// Default error type, usually occurs before connecting the server.

? DEFAULT,

? /// When opening? url timeout, it occurs.

? CONNECT_TIMEOUT,

? ///? Whenever more than [receiveTimeout] (in milliseconds) passes between two events from response stream,

? ///? [Dio] will throw the [DioError] with [DioErrorType.RECEIVE_TIMEOUT].

? ///

? ///? Note: This is not the receiving time limitation.

? RECEIVE_TIMEOUT,

? /// When the server response, but with a incorrect status, such as 404, 503...

? RESPONSE,

? /// When the request is cancelled, dio will throw a error with this type.

? CANCEL

}

使用application/x-www-form-urlencoded編碼

默認(rèn)情況下, Dio 會(huì)將請(qǐng)求數(shù)據(jù)(除過(guò)String類型)序列化為?JSON. 如果想要以?application/x-www-form-urlencoded格式編碼, 你可以顯式設(shè)置contentType?:

//Instance level

dio.options.contentType=ContentType.parse("application/x-www-form-urlencoded");

//or works once

dio.post("/info",data:{"id":5}, options: new Options(contentType:ContentType.parse("application/x-www-form-urlencoded")))

FormData? ? 注意: 只有 post 方法支持發(fā)送 FormData.

Dio支持發(fā)送 FormData, 請(qǐng)求數(shù)據(jù)將會(huì)以?multipart/form-data方式編碼, FormData中可以一個(gè)或多個(gè)包含文件 .

FormData formData = new FormData.from({

? ? "name": "wendux",

? ? "age": 25,

? ? "file": new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")

});

response = await dio.post("/info", data: formData)

轉(zhuǎn)換器

轉(zhuǎn)換器Transformer?用于對(duì)請(qǐng)求數(shù)據(jù)和響應(yīng)數(shù)據(jù)進(jìn)行編解碼處理。Dio實(shí)現(xiàn)了一個(gè)默認(rèn)轉(zhuǎn)換器DefaultTransformer作為默認(rèn)的?Transformer. 如果你想對(duì)請(qǐng)求/響應(yīng)數(shù)據(jù)進(jìn)行自定義編解碼處理,可以提供自定義轉(zhuǎn)換器,通過(guò)?dio.transformer設(shè)置.

請(qǐng)求轉(zhuǎn)換器?Transformer.transformRequest(...)?只會(huì)被用于 'PUT'、 'POST'、 'PATCH'方法,因?yàn)橹挥羞@些方法才可以攜帶請(qǐng)求體(request body)。但是響應(yīng)轉(zhuǎn)換器?Transformer.transformResponse()?會(huì)被用于所有請(qǐng)求方法的返回?cái)?shù)據(jù)。

執(zhí)行流

雖然在攔截器中也可以對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,但是轉(zhuǎn)換器主要職責(zé)是對(duì)請(qǐng)求/響應(yīng)數(shù)據(jù)進(jìn)行編解碼,之所以將轉(zhuǎn)化器單獨(dú)分離,一是為了和攔截器解耦,二是為了不修改原始請(qǐng)求數(shù)據(jù)(如果你在攔截器中修改請(qǐng)求數(shù)據(jù)(options.data),會(huì)覆蓋原始請(qǐng)求數(shù)據(jù),而在某些時(shí)候您可能需要原始請(qǐng)求數(shù)據(jù)).?

Dio的請(qǐng)求流是:請(qǐng)求攔截器?>>?請(qǐng)求轉(zhuǎn)換器?>>?發(fā)起請(qǐng)求?>>?響應(yīng)轉(zhuǎn)換器?>>?響應(yīng)攔截器?>>?最終結(jié)果。

設(shè)置Http代理

Dio 是使用 HttpClient發(fā)起的http請(qǐng)求,所以你可以通過(guò)配置?httpClient來(lái)支持代理,示例如下:

dio.onHttpClientCreate = (HttpClient client) { client.findProxy = (uri) { //proxy all request to localhost:8888 return "PROXY localhost:8888"; }; // 你也可以自己創(chuàng)建一個(gè)新的HttpClient實(shí)例返回。 // return new HttpClient(SecurityContext); };

請(qǐng)求取消

你可以通過(guò)?cancel token?來(lái)取消發(fā)起的請(qǐng)求:

CancelToken token = new CancelToken();

dio.get(url, cancelToken: token)

? ? .catchError((DioError err){

? ? ? ? if (CancelToken.isCancel(err)) {

? ? ? ? ? ? print('Request canceled! '+ err.message)

? ? ? ? }else{

? ? ? ? ? ? // handle error.

? ? ? ? }

? ? })

// cancel the requests with "cancelled" message.

token.cancel("cancelled");

注意: 同一個(gè)cancel token 可以用于多個(gè)請(qǐng)求,當(dāng)一個(gè)cancel token取消時(shí),所有使用該cancel token的請(qǐng)求都會(huì)被取消

Cookie管理

你可以通過(guò)?cookieJar?來(lái)自動(dòng)管理請(qǐng)求/響應(yīng)cookie.

dio cookie 管理 API 是基于開(kāi)源庫(kù)?cookie_jar.

你可以創(chuàng)建一個(gè)CookieJar?或?PersistCookieJar?來(lái)幫您自動(dòng)管理cookie, dio 默認(rèn)使用?CookieJar?, 它會(huì)將cookie保存在內(nèi)存中。 如果您想對(duì)cookie進(jìn)行持久化, 請(qǐng)使用?PersistCookieJar?, 示例代碼如下:

var dio = new Dio();

dio.cookieJar=new PersistCookieJar("./cookies");

PersistCookieJar?實(shí)現(xiàn)了RFC中標(biāo)準(zhǔn)的cookie策略.?PersistCookieJar?會(huì)將cookie保存在文件中,所以 cookies 會(huì)一直存在除非顯式調(diào)用?delete?刪除.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容