flutter dio 请求封装(空安全)

一、添加依赖

dart 复制代码
dio: ^5.3.2

二、请求封装

dart 复制代码
class HttpHelper {
  static Dio? mDio;
  static BaseOptions? options;
  static HttpHelper? httpHelper;

  CancelToken cancelToken = CancelToken();

  static const String GET = 'get';
  static const String POST = 'post';
  static const String PUT = 'put';
  static const String PATCH = 'patch';
  static const String DELETE = 'delete';

  static HttpHelper? get instance => getInstance();

  static Dio? get dio => getDio();

  static HttpHelper? getInstance() {
    httpHelper ??= HttpHelper();
    return httpHelper;
  }

  static Dio? getDio() {
    final token = GetStorage().read('token');

    final Map<String, dynamic> headerMap = {};

    if (token != null) {
      headerMap.putIfAbsent("Authorization", () => "Bearer $token");
    }

    options = BaseOptions(
      //请求基地址,可以包含子路径
      baseUrl: Api.BASE_URL,
      //连接服务器超时时间,单位是毫秒.
      connectTimeout: const Duration(seconds: 10),
      //2.x中为接收数据的最长时限
      receiveTimeout: const Duration(seconds: 5),
      //Http请求头.
      headers: headerMap,
      // 请求的Content-Type,默认值是"application/json; charset=utf-8".
      //   /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据,
      //   /// 可以设置此选项为 `Headers.formUrlEncodedContentType`,  这样[Dio]
      //   /// 就会自动编码请求体.
      contentType: Headers.jsonContentType,

      /// [responseType] 表示期望以那种格式(方式)接受响应数据。
      /// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`.
      ///
      /// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。
      /// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`.
      ///
      /// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`.
      responseType: ResponseType.json,
    );

    mDio = Dio(options);
    mDio?.interceptors.add(interceptorsWrapper());

    mDio?.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () {
        final client = HttpClient();
        // Config the client.
        client.findProxy = (uri) {
          // Forward all request to proxy "localhost:8888".
          // Be aware, the proxy should went through you running device,
          // not the host platform.
          return 'PROXY 192.168.0.191:8089';
        };
        // You can also create a new HttpClient for Dio instead of returning,
        // but a client must being returned here.
        return client;
      },
    );

    return mDio;
  }

  static InterceptorsWrapper interceptorsWrapper() {
    return InterceptorsWrapper(onRequest: (options, handler) {
      // Do something before request is sent
      return handler.next(options); //continue
      // 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。
      // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
      //
      // 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,如`handler.reject(error)`,
      // 这样请求将被中止并触发异常,上层catchError会被调用。
    }, onResponse: (response, handler) {
      // Do something with response data
      return handler.next(response); // continue
      // 如果你想终止请求并触发一个错误,你可以 reject 一个`DioError`对象,如`handler.reject(error)`,
      // 这样请求将被中止并触发异常,上层catchError会被调用。
    }, onError: (DioError e, handler) {
      // Do something with response error

      if (e.response != null) {
        if (e.response?.statusCode == 401) {
          ToastMsg.show("当前登录已过期,请重新登录!");
          Future.delayed(const Duration(milliseconds: 1000), () {
            Get.offAllNamed(AppRoutes.LOGIN);
          });
        } else if (e.response?.statusCode == 403) {
          ToastMsg.show("暂无权限访问,请联系管理员!");
        } else {
          ToastMsg.show("系统内部异常!");
        }
      }

      return handler.next(e); //continue
      // 如果你想完成请求并返回一些自定义数据,可以resolve 一个`Response`,如`handler.resolve(response)`。
      // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
    });
  }

  ///Get请求
  Future<BaseRes<T>> getHttp<T>(String url, parameters,{loading = true}) async {
    return await getResponse<T>(url, method: GET, parameters: parameters,loading:loading);
  }

  Future<BaseRes<T>> postHttp<T>(String url, parameters,{loading = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};

    return await getResponse<T>(url, method: POST, parameters: parameters,loading:loading);
  }

  Future<BaseRes<T>> putHttp<T>(String url, parameters,{loading = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};

    return await getResponse<T>(url, method: PUT, parameters: parameters,loading:loading);
  }

  Future<BaseRes<T>> deleteHttp<T>(String url, parameters,{loading = true}) async {
    ///定义请求参数
    parameters = parameters ?? <String, dynamic>{};
    return await getResponse<T>(url, method: DELETE, parameters: parameters,loading:loading);
  }

  /*
   * 下载文件
   */
  downloadFile(urlPath, savePath, onReceiveProgress) async {
    Response? response;
    try {
      response = await dio?.download(urlPath, savePath,
          onReceiveProgress: onReceiveProgress);
    } on DioError catch (e) {
      formatError(e);
    }
    return response?.data;
  }

  Future<BaseRes<T>> getResponse<T>(
    String url, {
    required String method,
    parameters,
    loading,
  }) async {
    //这里指定response自动转成map,不指定的话有可能是String类型
    Response<Map<String, dynamic>>? response;


    if(loading) {
      ToastMsg.showLoading();
    }

    switch (method) {
      case GET:
        response = await dio?.get(url,
            queryParameters: parameters ?? <String, dynamic>{});
        break;
      case PUT:
        response = await dio?.put(url,
            queryParameters: parameters ?? <String, dynamic>{});
        break;
      case DELETE:
        response = await dio?.delete(url,
            queryParameters: parameters ?? <String, dynamic>{});
        break;
      default:
        response =
            await dio?.post(url, data: parameters ?? <String, dynamic>{});
        break;
    }

    if(loading) {
      ToastMsg.cancelLoading();
    }


    //200代表网络请求成功
    if (response?.statusCode == 200) {
      /// 将后台的data字段转成自己想要的数据/数据集,code根据后端实际返回进行判断访问结果
      BaseRes<T> bean = BaseRes.fromJson(response?.data);

      return bean;
    } else if (response?.statusCode == 401) {
      ToastMsg.show("当前登录已过期,请重新登录!");
      Future.delayed(const Duration(milliseconds: 1000), () {
        Get.offAllNamed(AppRoutes.LOGIN);
      });
    } else if (response?.statusCode == 403) {
      ToastMsg.show("暂无权限访问,请联系管理员!");
    } else {
      ToastMsg.show("系统内部异常!");
    }

    throw Exception('${response?.statusCode}+${response?.statusMessage}');
  }

  void formatError(DioError e) {
    print(e.message);
  }

  /*
   * 取消请求
   * 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
   */
  void cancelRequests(CancelToken token) {
    token.cancel("cancelled");
  }
}

三、封装统一返回类

dart 复制代码
@JsonSerializable()
class BaseRes<T>{

  BaseRes(this.msg, this.code, this.data);

  late String msg;
  late int code;
  late T data;


  BaseRes.fromJson(Map<String, dynamic>? json) {
    if (json?['data'] != null && json?['data'] != 'null') {
      data = JsonConvert.fromJsonAsT<T>(json?['data']) as T;
    }
    msg = json?['msg'];
    code = json?['code'];
  }



  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    if (this.data != null) {
      data['data'] = this.data;
    }
    data['code'] = this.code;
    data['msg'] = this.msg;
    return data;
  }
}

四、使用

dart 复制代码
// Entity类使用的是JsonToDartBeanAction生成
 BaseRes<UserInfoEntity>? res = await HttpHelper.instance?.getHttp(Api.GET_USER_INGO,null,loading: false);
    if(res?.code == 200 && res?.data != null) {
      state.userInfo = res!.data.obs;
    }
相关推荐
火柴就是我4 小时前
flutter 之真手势冲突处理
android·flutter
Speed1234 小时前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间5 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭5 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone6 小时前
从flutter源码看其渲染机制
android·flutter
ALLIN1 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei1 天前
Flutter 国际化
flutter
Dabei1 天前
Flutter MQTT 通信文档
flutter
Dabei1 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉1 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter