前言
app在日常和后端交互时,总是会发生一些预料之外的错误,如果不把错误信息提示给用户会让用户感到懵逼。但也不是所有的异常都需要提醒用户,这个要根据业务取舍。
直接使用Dio的方式
这里写了很简单的示例,用来演示根据实际情况可以方便的使用showErrorMsg来控制是否showToast来提醒用户。这里的方式是很简单、灵活的,所有的请求使用了requestByGet的时候都是默认会弹错误信息,对于某些接口不想toast的可以给showErrorMsg设置false
Future<Response?> requestByGet({
required String url,
Map<String, dynamic>? params,
bool showErrorMsg = true,
}) async {
Response? response;
try {
response = await dio.get(url, queryParameters: params);
} catch (_) {
if (showErrorMsg) {
showToast("发生了错误");
}
}
return response;
}
retrofit生成代码的痛点
虽然retrofit生成的对dio封装很好用,但是想对模板代码进行改造就很费力。来看一下retrofit生成的代码
interface代码
@GET("/banner/json")
Future<BannerModel> getBanner();
implements代码
@override
Future<BannerModel> getBanner() async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
final _result = await _dio.fetch<Map<String, dynamic>>(
_setStreamType<BannerModel>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(_dio.options, '/banner/json',
queryParameters: queryParameters, data: _data)
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
final value = BannerModel.fromJson(_result.data!);
return value;
}
开发中只需要编写interface代码就行,implements代码由生成器自动生成。这里问题就凸显出来了,想要像#直接使用Dio的方式
那样包裹一个try-catch到implemetns代码里以添加全局toast成了不可能。那怎么做到无侵入呢?也许你已经想到了拦截器,使用拦截器获取所有的异常并且toast
在拦截器里处理异常
class ErrorMessageInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
super.onResponse(response, handler);
//自定义的业务错误
if (response.data.code != 200) {
showToast(response.data.msg);
}
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
super.onError(err, handler);
//http状态码!=200-300
showToast("网络错误");
}
}
把拦截器添加到dio里去,不就实现了全局错误信息的toast
吗?是的,这确实是一个好的做法,不仅实现了功能,而且还解耦了代码不需要改造retrofit的就可以使用。
现在就还有一个小问题,在拦截器是无差别的全面拦截
,所有请求的异常统统提醒给用户,但是有的信息不想给用户看到怎么办?如果你也有思考过这个问题,那么告诉你一个好消息,这里确实有一种解决方案来处理这个问题。dio里有一个属性贯穿请求生命的属性extra
在request、response、interceptor的回调里全都有他的身影。
在请求时添加extra标识,然后在拦截器拦截器里获取该数据,就可以控制是否toast错误信息
Options.extra
Options.extra参数,如下是他的定义
一个自定义的字段在Interceptor,Transformer,Response都可以访问
使用Option.extra改造#直接使用Dio的方式
上面提到了拦截器处理异常toast那么在requestByGet
方法里就不需要编写try-catch了,而是传递一个showErrorMsg
的属性到拦截器,由拦截器根据参数触发是否toast。
Future<Response?> requestByGet({
required String url,
Map<String, dynamic>? params,
bool showErrorMsg = true,
}) async {
Response? response;
//定义一个extra,在发起请求的时候传递出去
Options options = Options(extra: {"showErrorMsg": showErrorMsg});
response = await dio.get(url, queryParameters: params, options: options);
return response;
}
拦截器里添加获取extra
的方法
class ErrorMessageInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
super.onResponse(response, handler);
//获取请求的extra参数,根据参数判断是否需要toast异常信息
final bool showError = response.extra['showErrorMsg'] ?? false;
//自定义的业务错误
if (response.data.code != 200 && showError) {
showToast(response.data.msg);
}
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
super.onError(err, handler);
//获取请求的extra参数,根据参数判断是否需要toast异常信息
final bool showError = err.requestOptions.extra['showErrorMsg'] ?? false;
//http状态码!=200-300
if(showError){
showToast("网络错误");
}
}
}
整理一下到这里为止实现了什么?通过拦截器全局toast异常信息,使用Option.extra传递showErrorMsg字段判断当前请求是否需要toast,解耦了代码
Retrofit怎么使用extra
在官网retrofit | Dart Package (pub.dev)查看例子的时候没找到相关的配置,本以为Retrofit不支持添加extra的,抱着试试的心态在生成器代码里搜了下extra
竟然有意外的发现,生成器支持extra,关键字注解就是Extra。直接在方法上添加@Extra即可,就像这个样子:
@RestApi(baseUrl: "https://www.wanandroid.com")
abstract class WanApi {
factory WanApi(Dio dio, {String baseUrl}) = _WanApi;
@GET("/banner/json")
@Extra({"showErrorMsg":true})
Future<BannerModel> getBanner();
@GET("/article/top/json")
@Extra({"showErrorMsg":false})
Future<TopArticleModel> getTopArticle();
}
geBanner方法需要toast异常信息,getTopArticle不需要
看看生成的代码
可以看到Extra注解确实起了作用,后面的逻辑就和
使用Option.extra改造#直接使用Dio的方式
介绍的一样的了。
总结
再来回顾一下使用retrofit处理全局网络toast的流程。
定义拦截器ErrorMessageInterceptor来处理异常信息的toast
使用Options.extra传递参数,用于给拦截器判断是否需要显示toast
使用Retrofit时在方法上使用Extra注解传递参数