有时我们不需要复杂的应用程序,只需要显示数据列表的简单功能,我们可以通过简单的方法实现:
Future fetchItems() {
return Dio().("https://some-website.com/listing");
}
复制代码
没有错误,也没有必要log打印任何响应信息。
但事实是,几乎没有这么简单的应用程序,有时我们需要从服务器中做更多的响应或调试应用程序,例如:
将动态的Header发送给服务器,如存储SharedPreferences中的值。
检查每个响应头并保存其值。
验证服务器返回的错误,并将其映射到我们的应用程序中可以处理的错误类别中。
此外,我们希望为我们的应用程序无法访问网络时,为我们的应用程序添加简单的缓存功能,以显示该请求的缓存响应;
我们也可以添加它log打印我们的请求和响应信息。
Interceptors拦截器将帮助我们处理这些问题,通过为我们提供错误、请求和响应的具体回调。
在深入之前,我们先来看看如何配置。Dio。
Dio 配置
可以通过BaseOption来配置Dio,这个对象允许我们设置一些初始化的参数Dio实例: connectTimeout,receiveTimeout和baseUrl,它们将用于每一个Api的调用。
Dio createDio() {
return Dio(
BaseOptions(
connectTimeout: 5000,
receiveTimeout: 5000,
baseUrl: "https://some-website.com"
)
);
}
复制代码
但是,拦截器不能添加到基本配置中,我们需要创建它dio在实例中,我们需要在拦截器集合中添加拦截器:
Dio addInterceptors(Dio dio) {
return dio..interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options) => requestInterceptor(options),
onResponse: (Response response) => responseInterceptor(response),
onError: (DioError dioError) => errorInterceptor(dioError)));
}
复制代码
我们已经设置了一个dio例子可用于任何一个Api调用。
添加动态Headers
假如我们需要将SharedPreferences存储的一个值作为header我们不能使用传输到服务器的服务器BaseOption中的extra在创建请求时设置静态数据的字段。
InterceptorsWrapper给我们一个RequestOptions对象具有以下属性:
Request dynamic data
Url String path
Query Parameters Map queryParameters
有了这些信息,我们可以实现自己requestInterceptor方法了。
该方法可以返回动态类型:
RequestOptions对象,如果我们想继续要求,
Response如果应用程序想自己处理请求
DioError或dio.reject对象会抛出异常
这将使我们可以灵活的验证每个请求,添加数据以及在必要时抛出错误,我们使用起来只需要添加一些数据,然后处理返回的数据就可以了。
dynamic requestInterceptor(RequestOptions options) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.get("token");
options.headers.addAll({"Token": "$token${DateTime.now()}"});
return options;
}
复制代码
我们也可以创建它endpoints是否需要添加不同的请求?token的header:
Future getListOfTodos() {
return dio.get("/todos/1", options: Options(headers: {"requiresToken" : true}));
}
复制代码
然后统一处理拦截器:
dynamic requestInterceptor(RequestOptions options) async {
if (options.headers.containsKey("requiresToken")) {
//remove the auxiliary header
options.headers.remove("requiresToken");
SharedPreferences prefs = await SharedPreferences.getInstance();
var header = prefs.get("Header");
options.headers.addAll({"Header": "$header${DateTime.now()}"});
return options;
}
}
复制代码
验证 Response
Response中有Headers,还有Status Code响应数据。我们将响应数据类型设置为动态类型。他可以:
如果我们想继续要求,Response
如果我们在验证数据响应后抛出错误,那就是DioError
如果Header中的isUserActive值为false,然后回到一个DioError.
dynamic responseInterceptor(Response options) async {
if (options.headers.value("verifyToken") != null) {
//if the header is present, then compare it with the Shared Prefs key
SharedPreferences prefs = await SharedPreferences.getInstance();
var verifyToken = prefs.get("VerifyToken");
// if the value is the same as the header, continue with the request
if (options.headers.value("verifyToken") == verifyToken) {
return options;
}
}
return DioError(request: options.request, message: "User is no longer active");
}
复制代码
验证服务器错误
假设服务器有删除用户账户的机制,一旦账户被删除,它将收到错误的信息,因此界面用户界面将跳转到创建用户界面,服务器将返回{"error":"ERROR_001"}我们需要创建一个拦截器来统一处理错误信息。
错误拦截器的类型也是动态的:
如果我们想继续发送错误的请求,我们将返回DioError对象
如果我们想分析请求并返回它Response对象,在这种情况下,应用程序不知道服务器错误,将继续正常要求。
dynamic errorInterceptor(DioError dioError) {
if (dioError.message.contains("ERROR_001")) {
// this will push a new route and remove all the routes that were present
navigatorKey.currentState.pushNamedAndRemoveUntil(
"/login", (Route route) => false);
}
return dioError;
}
复制代码
扩展拦截器
我们可以创建一个拦截器来覆盖它onRequest、onError、onResponse方法。
class AppInterceptors extends Interceptor {@override
FutureOr onRequest(RequestOptions options) async {
if (options.headers.containsKey("requiresToken")) {
//remove the auxiliary header
options.headers.remove("requiresToken");
SharedPreferences prefs = await SharedPreferences.getInstance();
var header = prefs.get("Header");
options.headers.addAll({"Header": "$header${DateTime.now()}"});
return options;
}
}
@override
FutureOr onError(DioError dioError) {
if (dioError.message.contains("ERROR_001")) {
// this will push a new route and remove all the routes that were present
navigatorKey.currentState.pushNamedAndRemoveUntil(
"/login", (Route route) => false);
}
return dioError;
}
@override
FutureOr onResponse(Response options) async {
if (options.headers.value("verifyToken") != null) {
//if the header is present, then compare it with the Shared Prefs key
SharedPreferences prefs = await SharedPreferences.getInstance();
var verifyToken = prefs.get("VerifyToken");
// if the value is the same as the header, continue with the request
if (options.headers.value("verifyToken") == verifyToken) {
return options;
}
}
return DioError(request: options.request, message: "User is no longer active");
}
}
复制代码
该类可以被轻松的添加到dio对象的拦截器集合中:
Dio addInterceptors(Dio dio) {
dio.interceptors.add(AppInterceptors());
}
复制代码
创建一个简单的缓存
import 'package:dio/dio.dart';
class CacheInterceptor extends Interceptor {
CacheInterceptor();
var _cache = new Map();
@override
onRequest(RequestOptions options) {
return options;
}
@override
onResponse(Response response) {
_cache[response.request.uri] = response;
}
@override
onError(DioError e) {
print('onError: $e');
if (e.type == DioErrorType.CONNECT_TIMEOUT || e.type == DioErrorType.DEFAULT) {
var cachedResponse = _cache[e.request.uri];
if (cachedResponse != null) {
return cachedResponse;
}
}
return e;
}
}
复制代码
之后可以通过添加数据库缓存当方式来改进该缓存拦截器。
记录所有Dio相关的内容
在请求中,我们想通过控制台打印所有的请求信息以及相应信息,以方便我们调试请求中的问题。
和以前一样,我们创建一个新的Interceptor类,实现所有必要的方法,并从请求中记录我们想要的所有信息,此外,我们希望我们的人日志打印的是格式化好的信息:
# REQUEST:
--> GET https://jsonplaceholder.typicode.com/todos/1
...
--> END GET
# RESPONSE:
...
复制代码对于Request,我们希望打印请求参数、请求体、headers、URL
对于Response,我们要打印出URL、Headers、body和状态码
对于错误,我们需要打印状态码和错误本身
class LoggingInterceptors extends Interceptor {
@override
FutureOr onRequest(RequestOptions options) {
print(
"--> ${options.method != null ? options.method.toUpperCase() : 'METHOD'} ${"" + (options.baseUrl ?? "") + (options.path ?? "")}");
print("Headers:");
options.headers.forEach((k, v) => print('$k: $v'));
if (options.queryParameters != null) {
print("queryParameters:");
options.queryParameters.forEach((k, v) => print('$k: $v'));
}
if (options.data != null) {
print("Body: ${options.data}");
}
print(
"--> END ${options.method != null ? options.method.toUpperCase() : 'METHOD'}");
return options;
}
@override
FutureOr onError(DioError dioError) {
print(
"
print(
"${dioError.response != null ? dioError.response.data : 'Unknown Error'}");
print("
}
@override
FutureOr onResponse(Response response) {
print(
"
print("Headers:");
response.headers?.forEach((k, v) => print('$k: $v'));
print("Response: ${response.data}");
print("
}
}
复制代码—> GET https:///jsonplaceholder.typicode.com/todos/1/
Headers:
requiresToken: true
queryParameters:
—> END GET
Headers:
connection: [keep-alive]
set-cookie: [__cfduid=dd3fb888c5f062dd954e06e2e4c1166241567679659; expires=Fri, 04-Sep-20 10:34:19 GMT; path=/; domain=.typicode.com; HttpOnly]
cache-control: [public, max-age=14400]
transfer-encoding: [chunked]
date: [Thu, 05 Sep 2019 10:34:19 GMT]
content-encoding: [gzip]
vary: [Origin, Accept-Encoding]
age: [4045]
cf-cache-status: [HIT]
expect-ct: [max-age=604800, report-uri=“https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct”]
content-type: [application/json; charset=utf-8]
pragma: [no-cache]
server: [cloudflare]
x-powered-by: [Express]
access-control-allow-credentials: [true]
cf-ray: [51178cd29f9b724b-AMS]
etag: [W/“53-hfEnumeNh6YirfjyjaujcOPPT+s”]