flutter上使用retrofit+dio实现全局网络异常toast

2,597 阅读4分钟
前言

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参数,如下是他的定义

image.png 一个自定义的字段在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

image.png竟然有意外的发现,生成器支持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不需要

看看生成的代码

image.png 可以看到Extra注解确实起了作用,后面的逻辑就和使用Option.extra改造#直接使用Dio的方式介绍的一样的了。

总结

再来回顾一下使用retrofit处理全局网络toast的流程。

  1. 定义拦截器ErrorMessageInterceptor来处理异常信息的toast
  2. 使用Options.extra传递参数,用于给拦截器判断是否需要显示toast
  3. 使用Retrofit时在方法上使用Extra注解传递参数