Flutter 網(wǎng)絡請求 Dio 封裝

??在Flutter項目中使用網(wǎng)絡請求的方式大致可分為兩種,分別是Dart原生的網(wǎng)絡請求 HttpClient類以及第三方開源的網(wǎng)絡請求庫颇玷。在Dart社區(qū)開源的第三方http請求庫中Flutter中文網(wǎng)開源的Dio庫人氣最高去件。

??下面我們先來比較下這兩種網(wǎng)絡請求方式坡椒,然后再看怎么基于 Dio庫封裝方便使用的網(wǎng)絡請求工具類HttpManager。

網(wǎng)絡請求庫比較

HttClient類

Dart IO庫中提供了用于發(fā)起Http請求的一些類尤溜,我們可以直接使用HttpClient來發(fā)起請求倔叼。使用HttpClient發(fā)起請求分為五步:

  1. 創(chuàng)建一個HttpClient
 HttpClient httpClient = new HttpClient();
  1. 打開Http連接,設置請求頭
HttpClientRequest request = await httpClient.getUrl(uri);

這一步可以使用任意Http Method宫莱,如httpClient.post(...)缀雳、httpClient.delete(...)等。如果包含Query參數(shù)梢睛,可以在構建uri時添加肥印,如:

Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
    "xx":"xx",
    "yy":"dd"
  });

通過HttpClientRequest可以設置請求header识椰,如:

request.headers.add("user-agent", "test");

如果是post或put等可以攜帶請求體方法,可以通過HttpClientRequest對象發(fā)送request body深碱,如:

String payload="...";
request.add(utf8.encode(payload)); 
//request.addStream(_inputStream); //可以直接添加輸入流
  1. 等待連接服務器
HttpClientResponse response = await request.close();

這一步完成后腹鹉,請求信息就已經(jīng)發(fā)送給服務器了,返回一個HttpClientResponse對象敷硅,它包含響應頭(header)和響應流(響應體的Stream)功咒,接下來就可以通過讀取響應流來獲取響應內(nèi)容。

  1. 讀取響應內(nèi)容
String responseBody = await response.transform(utf8.decoder).join();

我們通過讀取響應流來獲取服務器返回的數(shù)據(jù)绞蹦,在讀取時我們可以設置編碼格式力奋,這里是utf8。

  1. 請求結束幽七,關閉HttpClient
httpClient.close();

關閉client后景殷,通過該client發(fā)起的所有請求都會中止。

??以上的步驟是dart原生網(wǎng)絡HttpClient使用方式澡屡,可以發(fā)現(xiàn)直接使用HttpClient發(fā)起網(wǎng)絡請求是比較麻煩的猿挚,很多事情都得手動處理,如果再涉及到文件上傳/下載驶鹉、Cookie管理等就會變得非常繁瑣绩蜻,并且HttpClient本身功能較弱,很多常用功能都不支持室埋。

Dio庫

dio是一個強大的Dart Http請求庫办绝,支持Restful API、FormData姚淆、攔截器八秃、請求取消、Cookie管理肉盹、文件上傳/下載昔驱、超時等...

  1. pubspec.yaml 添加依賴
dependencies:
  dio: ^x.x.x #請使用pub上的最新版本
  1. 導入引用并創(chuàng)建dio 實例
import 'package:dio/dio.dart';
Dio dio =  Dio();

接下來就可以通過 dio實例來發(fā)起網(wǎng)絡請求了,注意上忍,一個dio實例可以發(fā)起多個http請求骤肛,一般來說,APP只有一個http數(shù)據(jù)源時窍蓝,dio應該使用單例模式腋颠。

  1. 發(fā)起網(wǎng)絡請求

Get 請求

Response response;
response=await dio.get("/test?id=12&name=cheney")
print(response.data.toString());

Post請求

Response response;
response=await dio.post("/test",data:{"id":12,"name":"cheney"})
print(response.data.toString());

??以上就是Dio庫網(wǎng)絡請求的基本使用,是不是很簡單吓笙,除了這些基本的用法淑玫,dio還支持請求配置、攔截器等,官方資料比較詳細絮蒿,故在這里不再贅述尊搬,詳情可以參考dio主頁:https://github.com/flutterchina/dio

封裝Dio工具類

為什么要封裝 dio

做一些公共處理土涝,方便靈活的使用佛寿。

做那些封裝

  • 統(tǒng)一處理請求前綴;(https://www.xx.com/api/v1 不用每個請求都加前綴)
  • 統(tǒng)一輸出請求或響應信息但壮;
  • 統(tǒng)一錯誤信息處理冀泻;
  • 兼容多種網(wǎng)絡請求、支持文件上傳蜡饵、下載弹渔;
  • 支持同步回調(diào)與異步Future 兩種形式
  • 返回數(shù)據(jù)自動轉json格式并默認解析公共數(shù)據(jù)模型;

目錄

類名 描述
HttpManager 網(wǎng)絡請求管理類
HttpError 網(wǎng)絡請求統(tǒng)一錯誤類
LogInterceptor 網(wǎng)絡請求工具類

HttpManager

import 'dart:core';

import 'package:connectivity/connectivity.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_common_utils/http/http_error.dart';
import 'package:flutter_common_utils/log_util.dart';

///http請求成功回調(diào)
typedef HttpSuccessCallback<T> = void Function(dynamic data);

///失敗回調(diào)
typedef HttpFailureCallback = void Function(HttpError data);

///數(shù)據(jù)解析回調(diào)
typedef T JsonParse<T>(dynamic data);


/// @desc  封裝 http 請求
/// @time 2019/3/15 10:35 AM
/// @author Cheney
class HttpManager {
  ///同一個CancelToken可以用于多個請求溯祸,當一個CancelToken取消時肢专,所有使用該CancelToken的請求都會被取消,一個頁面對應一個CancelToken您没。
  Map<String, CancelToken> _cancelTokens =  Map<String, CancelToken>();

  ///超時時間
  static const int CONNECT_TIMEOUT = 30000;
  static const int RECEIVE_TIMEOUT = 30000;

  /// http request methods
  static const String GET = 'get';
  static const String POST = 'post';

  Dio _client;

  static final HttpManager _instance = HttpManager._internal();

  factory HttpManager() => _instance;

  Dio get client => _client;

  /// 創(chuàng)建 dio 實例對象
  HttpManager._internal() {
    if (_client == null) {
      /// 全局屬性:請求前綴、連接超時時間胆绊、響應超時時間
      BaseOptions options = BaseOptions(
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT,
      );
      _client = Dio(options);
    }
  }

  ///初始化公共屬性
  ///
  /// [baseUrl] 地址前綴
  /// [connectTimeout] 連接超時趕時間
  /// [receiveTimeout] 接收超時趕時間
  /// [interceptors] 基礎攔截器
  void init(
      {String baseUrl,
      int connectTimeout,
      int receiveTimeout,
      List<Interceptor> interceptors}) {
    _client.options = _client.options.merge(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) {
      _client.interceptors..addAll(interceptors);
    }
  }

  ///Get網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回調(diào)
  ///[errorCallback] 請求失敗回調(diào)
  ///[tag] 請求統(tǒng)一標識氨鹏,用于取消網(wǎng)絡請求
  void get({
    @required String url,
    Map<String, dynamic> params,
    Options options,
    HttpSuccessCallback successCallback,
    HttpFailureCallback errorCallback,
    @required String tag,
  }) async {
    _request(
      url: url,
      params: params,
      method: GET,
      successCallback: successCallback,
      errorCallback: errorCallback,
      tag: tag,
    );
  }

  ///post網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回調(diào)
  ///[errorCallback] 請求失敗回調(diào)
  ///[tag] 請求統(tǒng)一標識,用于取消網(wǎng)絡請求
  void post({
    @required String url,
    data,
    Map<String, dynamic> params,
    Options options,
    HttpSuccessCallback successCallback,
    HttpFailureCallback errorCallback,
    @required String tag,
  }) async {
    _request(
      url: url,
      data: data,
      method: POST,
      params: params,
      successCallback: successCallback,
      errorCallback: errorCallback,
      tag: tag,
    );
  }

  ///統(tǒng)一網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回調(diào)
  ///[errorCallback] 請求失敗回調(diào)
  ///[tag] 請求統(tǒng)一標識压状,用于取消網(wǎng)絡請求
  void _request({
    @required String url,
    String method,
    data,
    Map<String, dynamic> params,
    Options options,
    HttpSuccessCallback successCallback,
    HttpFailureCallback errorCallback,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常仆抵,請稍后重試!"));
      }
      LogUtil.v("請求網(wǎng)絡異常种冬,請稍后重試镣丑!");
      return;
    }

    //設置默認值
    params = params ?? {};
    method = method ?? 'GET';

    options?.method = method;

    options = options ??
        Options(
          method: method,
        );

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      Response<Map<String, dynamic>> response = await _client.request(url,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      String statusCode = response.data["statusCode"];
      if (statusCode == "0") {
        //成功
        if (successCallback != null) {
          successCallback(response.data["data"]);
        }
      } else {
        //失敗
        String message = response.data["statusDesc"];
        LogUtil.v("請求服務器出錯:$message");
        if (errorCallback != null) {
          errorCallback(HttpError(statusCode, message));
        }
      }
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      if (errorCallback != null && e.type != DioErrorType.CANCEL) {
        errorCallback(HttpError.dioError(e));
      }
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常,請稍后重試娱两!"));
      }
    }
  }

  ///下載文件
  ///
  ///[url] 下載地址
  ///[savePath]  文件保存路徑
  ///[onReceiveProgress]  文件保存路徑
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回調(diào)
  ///[errorCallback] 請求失敗回調(diào)
  ///[tag] 請求統(tǒng)一標識莺匠,用于取消網(wǎng)絡請求
  void download({
    @required String url,
    @required savePath,
    ProgressCallback onReceiveProgress,
    Map<String, dynamic> params,
    data,
    Options options,
    HttpSuccessCallback successCallback,
    HttpFailureCallback errorCallback,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常,請稍后重試十兢!"));
      }
      LogUtil.v("請求網(wǎng)絡異常趣竣,請稍后重試!");
      return;
    }

    ////0代表不設置超時
    int receiveTimeout = 0;
    options ??= options == null
        ? Options(receiveTimeout: receiveTimeout)
        : options.merge(receiveTimeout: receiveTimeout);

    //設置默認值
    params = params ?? {};

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      Response response = await _client.download(url, savePath,
          onReceiveProgress: onReceiveProgress,
          queryParameters: params,
          data: data,
          options: options,
          cancelToken: cancelToken);
      //成功
      if (successCallback != null) {
        successCallback(response.data);
      }
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      if (errorCallback != null && e.type != DioErrorType.CANCEL) {
        errorCallback(HttpError.dioError(e));
      }
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常旱物,請稍后重試遥缕!"));
      }
    }
  }

  ///上傳文件
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[onSendProgress] 上傳進度
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[successCallback] 請求成功回調(diào)
  ///[errorCallback] 請求失敗回調(diào)
  ///[tag] 請求統(tǒng)一標識,用于取消網(wǎng)絡請求
  void upload({
    @required String url,
    FormData data,
    ProgressCallback onSendProgress,
    Map<String, dynamic> params,
    Options options,
    HttpSuccessCallback successCallback,
    HttpFailureCallback errorCallback,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常宵呛,請稍后重試单匣!"));
      }
      LogUtil.v("請求網(wǎng)絡異常,請稍后重試!");
      return;
    }

    //設置默認值
    params = params ?? {};

    //強制 POST 請求
    options?.method = POST;

    options = options ??
        Options(
          method: POST,
        );

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      Response<Map<String, dynamic>> response = await _client.request(url,
          onSendProgress: onSendProgress,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      String statusCode = response.data["statusCode"];
      if (statusCode == "0") {
        //成功
        if (successCallback != null) {
          successCallback(response.data["data"]);
        }
      } else {
        //失敗
        String message = response.data["statusDesc"];
        LogUtil.v("請求服務器出錯:$message");
        if (errorCallback != null) {
          errorCallback(HttpError(statusCode, message));
        }
      }
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      if (errorCallback != null && e.type != DioErrorType.CANCEL) {
        errorCallback(HttpError.dioError(e));
      }
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常户秤,請稍后重試码秉!"));
      }
    }
  }

  ///GET異步網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[tag] 請求統(tǒng)一標識,用于取消網(wǎng)絡請求
  Future<T> getAsync<T>({
    @required String url,
    Map<String, dynamic> params,
    Options options,
    JsonParse<T> jsonParse,
    @required String tag,
  }) async {
    return _requestAsync(
      url: url,
      method: GET,
      params: params,
      options: options,
      jsonParse: jsonParse,
      tag: tag,
    );
  }

  ///POST 異步網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[tag] 請求統(tǒng)一標識虎忌,用于取消網(wǎng)絡請求
  Future<T> postAsync<T>({
    @required String url,
    data,
    Map<String, dynamic> params,
    Options options,
    JsonParse<T> jsonParse,
    @required String tag,
  }) async {
    return _requestAsync(
      url: url,
      method: POST,
      data: data,
      params: params,
      options: options,
      jsonParse: jsonParse,
      tag: tag,
    );
  }

  ///統(tǒng)一網(wǎng)絡請求
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[tag] 請求統(tǒng)一標識泡徙,用于取消網(wǎng)絡請求
  Future<T> _requestAsync<T>({
    @required String url,
    String method,
    data,
    Map<String, dynamic> params,
    Options options,
    JsonParse<T> jsonParse,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      LogUtil.v("請求網(wǎng)絡異常,請稍后重試膜蠢!");
      throw (HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常堪藐,請稍后重試!"));
    }

    //設置默認值
    params = params ?? {};
    method = method ?? 'GET';

    options?.method = method;

    options = options ??
        Options(
          method: method,
        );

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      Response<Map<String, dynamic>> response = await _client.request(url,
          queryParameters: params,
          data: data,
          options: options,
          cancelToken: cancelToken);
      String statusCode = response.data["statusCode"];
      if (statusCode == "0") {
        //成功
        if (jsonParse != null) {
          return jsonParse(response.data["data"]);
        } else {
          return response.data["data"];
        }
      } else {
        //失敗
        String message = response.data["statusDesc"];
        LogUtil.v("請求服務器出錯:$message");
        //只能用 Future挑围,外層有 try catch
        return Future.error((HttpError(statusCode, message)));
      }
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      throw (HttpError.dioError(e));
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      throw (HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常礁竞,請稍后重試!"));
    }
  }

  ///異步下載文件
  ///
  ///[url] 下載地址
  ///[savePath]  文件保存路徑
  ///[onReceiveProgress]  文件保存路徑
  ///[data] post 請求參數(shù)
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[tag] 請求統(tǒng)一標識杉辙,用于取消網(wǎng)絡請求
  Future<Response> downloadAsync({
    @required String url,
    @required savePath,
    ProgressCallback onReceiveProgress,
    Map<String, dynamic> params,
    data,
    Options options,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      LogUtil.v("請求網(wǎng)絡異常模捂,請稍后重試!");
      throw (HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常蜘矢,請稍后重試狂男!"));
    }
    //設置下載不超時
    int receiveTimeout = 0;
    options ??= options == null
        ? Options(receiveTimeout: receiveTimeout)
        : options.merge(receiveTimeout: receiveTimeout);

    //設置默認值
    params = params ?? {};

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      return _client.download(url, savePath,
          onReceiveProgress: onReceiveProgress,
          queryParameters: params,
          data: data,
          options: options,
          cancelToken: cancelToken);
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      throw (HttpError.dioError(e));
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      throw (HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常,請稍后重試品腹!"));
    }
  }

  ///上傳文件
  ///
  ///[url] 網(wǎng)絡請求地址不包含域名
  ///[data] post 請求參數(shù)
  ///[onSendProgress] 上傳進度
  ///[params] url請求參數(shù)支持restful
  ///[options] 請求配置
  ///[tag] 請求統(tǒng)一標識匾浪,用于取消網(wǎng)絡請求
  Future<T> uploadAsync<T>({
    @required String url,
    FormData data,
    ProgressCallback onSendProgress,
    Map<String, dynamic> params,
    Options options,
    JsonParse<T> jsonParse,
    @required String tag,
  }) async {
    //檢查網(wǎng)絡是否連接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      LogUtil.v("請求網(wǎng)絡異常乍丈,請稍后重試!");
      throw (HttpError(HttpError.NETWORK_ERROR, "網(wǎng)絡異常,請稍后重試纯衍!"));
    }

    //設置默認值
    params = params ?? {};

    //強制 POST 請求
    options?.method = POST;

    options = options ??
        Options(
          method: POST,
        );

    url = _restfulUrl(url, params);

    try {
      CancelToken cancelToken;
      if (tag != null) {
        cancelToken =
            _cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag];
        _cancelTokens[tag] = cancelToken;
      }

      Response<Map<String, dynamic>> response = await _client.request(url,
          onSendProgress: onSendProgress,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);

      String statusCode = response.data["statusCode"];
      if (statusCode == "0") {
        //成功
        if (jsonParse != null) {
          return jsonParse(response.data["data"]);
        } else {
          return response.data["data"];
        }
      } else {
        //失敗
        String message = response.data["statusDesc"];
        LogUtil.v("請求服務器出錯:$message");
        return Future.error((HttpError(statusCode, message)));
      }
    } on DioError catch (e, s) {
      LogUtil.v("請求出錯:$e\n$s");
      throw (HttpError.dioError(e));
    } catch (e, s) {
      LogUtil.v("未知異常出錯:$e\n$s");
      throw (HttpError(HttpError.UNKNOWN, "網(wǎng)絡異常碘梢,請稍后重試想括!"));
    }
  }

  ///取消網(wǎng)絡請求
  void cancel(String tag) {
    if (_cancelTokens.containsKey(tag)) {
      if (!_cancelTokens[tag].isCancelled) {
        _cancelTokens[tag].cancel();
      }
      _cancelTokens.remove(tag);
    }
  }

  ///restful處理
  String _restfulUrl(String url, Map<String, dynamic> params) {
    // restful 請求處理
    // /gysw/search/hist/:user_id        user_id=27
    // 最終生成 url 為     /gysw/search/hist/27
    params.forEach((key, value) {
      if (url.indexOf(key) != -1) {
        url = url.replaceAll(':$key', value.toString());
      }
    });
    return url;
  }
}

這里處理了網(wǎng)絡連接判斷鉴腻、取消網(wǎng)絡請求、默認的數(shù)據(jù)格式解析等惧浴。
默認解析的數(shù)據(jù)格式:

{
    "data":{},
    "statusCode":"0",
    "statusDesc":"02032008:用戶授信未通過",
    "timestamp":1569206576392
}

大家可根據(jù)自己的需求更改成自己的數(shù)據(jù)格式處理

HttpError

import 'package:dio/dio.dart';

/// @desc  網(wǎng)絡請求錯誤
/// @time 2019/3/20 10:02 AM
/// @author Cheney
class HttpError {
  ///HTTP 狀態(tài)碼
  static const int UNAUTHORIZED = 401;
  static const int FORBIDDEN = 403;
  static const int NOT_FOUND = 404;
  static const int REQUEST_TIMEOUT = 408;
  static const int INTERNAL_SERVER_ERROR = 500;
  static const int BAD_GATEWAY = 502;
  static const int SERVICE_UNAVAILABLE = 503;
  static const int GATEWAY_TIMEOUT = 504;

  ///未知錯誤
  static const String UNKNOWN = "UNKNOWN";

  ///解析錯誤
  static const String PARSE_ERROR = "PARSE_ERROR";

  ///網(wǎng)絡錯誤
  static const String NETWORK_ERROR = "NETWORK_ERROR";

  ///協(xié)議錯誤
  static const String HTTP_ERROR = "HTTP_ERROR";

  ///證書錯誤
  static const String SSL_ERROR = "SSL_ERROR";

  ///連接超時
  static const String CONNECT_TIMEOUT = "CONNECT_TIMEOUT";

  ///響應超時
  static const String RECEIVE_TIMEOUT = "RECEIVE_TIMEOUT";

  ///發(fā)送超時
  static const String SEND_TIMEOUT = "SEND_TIMEOUT";

  ///網(wǎng)絡請求取消
  static const String CANCEL = "CANCEL";

  String code;

  String message;

  HttpError(this.code, this.message);

  HttpError.dioError(DioError error) {
    message = error.message;
    switch (error.type) {
      case DioErrorType.CONNECT_TIMEOUT:
        code = CONNECT_TIMEOUT;
        message = "網(wǎng)絡連接超時存和,請檢查網(wǎng)絡設置";
        break;
      case DioErrorType.RECEIVE_TIMEOUT:
        code = RECEIVE_TIMEOUT;
        message = "服務器異常,請稍后重試衷旅!";
        break;
      case DioErrorType.SEND_TIMEOUT:
        code = SEND_TIMEOUT;
        message = "網(wǎng)絡連接超時哑姚,請檢查網(wǎng)絡設置";
        break;
      case DioErrorType.RESPONSE:
        code = HTTP_ERROR;
        message = "服務器異常,請稍后重試芜茵!";
        break;
      case DioErrorType.CANCEL:
        code = CANCEL;
        message = "請求已被取消叙量,請重新請求";
        break;
      case DioErrorType.DEFAULT:
        code = UNKNOWN;
        message = "網(wǎng)絡異常,請稍后重試九串!";
        break;
    }
  }

  @override
  String toString() {
    return 'HttpError{code: $code, message: $message}';
  }
}

這里設置了多種錯誤的描述绞佩,大家可根據(jù)需求修改寺鸥。

LogInterceptor

import 'package:dio/dio.dart';
import 'package:flutter_common_utils/log_util.dart';

void log2Console(Object object) {
  LogUtil.v(object);
}

/// @desc  自定義日志攔截器
///@time 2019/3/18 9:15 AM
/// @author Cheney
class LogInterceptor extends Interceptor {
  LogInterceptor({
    this.request = true,
    this.requestHeader = true,
    this.requestBody = false,
    this.responseHeader = true,
    this.responseBody = false,
    this.error = true,
    this.logPrint = log2Console,
  });

  /// Print request [Options]
  bool request;

  /// Print request header [Options.headers]
  bool requestHeader;

  /// Print request data [Options.data]
  bool requestBody;

  /// Print [Response.data]
  bool responseBody;

  /// Print [Response.headers]
  bool responseHeader;

  /// Print error message
  bool error;

  /// Log printer; defaults print log to console.
  /// In flutter, you'd better use debugPrint.
  /// you can also write log in a file, for example:
  ///```dart
  ///  var file=File("./log.txt");
  ///  var sink=file.openWrite();
  ///  dio.interceptors.add(LogInterceptor(logPrint: sink.writeln));
  ///  ...
  ///  await sink.close();
  ///```
  void Function(Object object) logPrint;

  @override
  Future onRequest(RequestOptions options) async {
    logPrint('*** Request ***');
    printKV('uri', options.uri);

    if (request) {
      printKV('method', options.method);
      printKV('responseType', options.responseType?.toString());
      printKV('followRedirects', options.followRedirects);
      printKV('connectTimeout', options.connectTimeout);
      printKV('receiveTimeout', options.receiveTimeout);
      printKV('extra', options.extra);
    }
    if (requestHeader) {
      logPrint('headers:');
      options.headers.forEach((key, v) => printKV(" $key", v));
    }
    if (requestBody) {
      logPrint("data:");
      printAll(options.data);
    }
    logPrint("");
  }

  @override
  Future onError(DioError err) async {
    if (error) {
      logPrint('*** DioError ***:');
      logPrint("uri: ${err.request.uri}");
      logPrint("$err");
      if (err.response != null) {
        _printResponse(err.response);
      }
      logPrint("");
    }
  }

  @override
  Future onResponse(Response response) async {
    logPrint("*** Response ***");
    _printResponse(response);
  }

  void _printResponse(Response response) {
    printKV('uri', response.request?.uri);
    if (responseHeader) {
      printKV('statusCode', response.statusCode);
      if (response.isRedirect == true) {
        printKV('redirect', response.realUri);
      }
      if (response.headers != null) {
        logPrint("headers:");
        response.headers.forEach((key, v) => printKV(" $key", v.join(",")));
      }
    }
    if (responseBody) {
      logPrint("Response Text:");
      printAll(response.toString());
    }
    logPrint("");
  }

  printKV(String key, Object v) {
    logPrint('$key: $v');
  }

  printAll(msg) {
    msg.toString().split("\n").forEach(logPrint);
  }
}

這里默認使用 LogUtl 輸出日志,大家可根據(jù)需要換成自己的日志輸出工具類

使用示例

image

Step1: 初始化

//初始化 Http品山,
  HttpManager().init(
    baseUrl: Api.getBaseUrl(),
    interceptors: [
      HeaderInterceptor(),
      LogInterceptor(),
    ],
  );

Step2:創(chuàng)建網(wǎng)絡請求

///同步回調(diào)模式
///get 網(wǎng)絡請求
void _get(){
   HttpManager().get(
      url: "/app/info",
      params: {"iouCode": iouCode},
      successCallback: (data) {
        
      },
      errorCallback: (HttpError error) {
       
      },
      tag: "tag",
    );
}
///post 網(wǎng)絡請求
void _post(){
     HttpManager().post(
      url: "/app/info",
      data: {"iouCode": iouCode},
      successCallback: (data) {
        
      },
      errorCallback: (HttpError error) {
        
      },
      tag: "tag",
    );
}

///下載文件
void _download(){
     HttpManager().download(
      url: "/app/download",
      savePath: "/savePath",
      onReceiveProgress: (int count, int total) {
      },
      successCallback: (data) {
        
      },
      errorCallback: (HttpError error) {
        
      },
      tag: tag,
    );
}

///上傳文件
void _upload() async{
FormData data = FormData.fromMap({
        "file": await MultipartFile.fromFile(path, filename: "$photoTime"),
      });
    HttpManager().upload(
        url: "/app/upload",
        data: data,
        tag: "tag",
        successCallback: (data) {
          
        },
        errorCallback: (HttpError error) {
          
        },
      );
}


///異步模式
///get 請求
void _getAysnc() async{
     String timestamp =
        await HttpManager().getAsync(url: "/app/info", tag: "syncTime");
}

///post 請求
void _postAysnc() async{
   await HttpManager().postAsync(
        url: "app/info",
        tag: "tag",
        data: {
          'bannerTypes': ["wealthBanner"],
        },
        jsonParse: (json) => Pager(json, (data) => ImageAd(data)))
}

///下載文件
void _downloadAsync() async{
    await HttpManager().downloadAsync(
      url: "/app/download",
      savePath: "/savePath",
      onReceiveProgress: (int count, int total) {
      },
      tag: "tag",
    );
}

///上傳文件
void _uploadAsync() async{
FormData data = FormData.fromMap({
        "file": await MultipartFile.fromFile(path, filename: "$photoTime"),
      });
  await  HttpManager().uploadAsync(
        url: "/app/upload",
        data: data,
        tag: "tag",
       
      );
}

最后

??如果在使用過程遇到問題,歡迎下方留言交流肘交。

??工具類庫地址

學習資料

請大家不吝點贊笆载!因為您的點贊是對我最大的鼓勵,謝謝涯呻!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凉驻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子复罐,更是在濱河造成了極大的恐慌涝登,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件效诅,死亡現(xiàn)場離奇詭異胀滚,居然都是意外死亡,警方通過查閱死者的電腦和手機乱投,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門咽笼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戚炫,你說我怎么就攤上這事剑刑。” “怎么了嘹悼?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵叛甫,是天一觀的道長层宫。 經(jīng)常有香客問我杨伙,道長,這世上最難降的妖魔是什么萌腿? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任限匣,我火速辦了婚禮,結果婚禮上毁菱,老公的妹妹穿的比我還像新娘米死。我一直安慰自己,他們只是感情好贮庞,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布峦筒。 她就那樣靜靜地躺著,像睡著了一般窗慎。 火紅的嫁衣襯著肌膚如雪物喷。 梳的紋絲不亂的頭發(fā)上卤材,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音峦失,去河邊找鬼扇丛。 笑死,一個胖子當著我的面吹牛尉辑,可吹牛的內(nèi)容都是我干的帆精。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼隧魄,長吁一口氣:“原來是場噩夢啊……” “哼卓练!你這毒婦竟也來了?” 一聲冷哼從身側響起堤器,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昆庇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闸溃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體整吆,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年辉川,在試婚紗的時候發(fā)現(xiàn)自己被綠了表蝙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡乓旗,死狀恐怖府蛇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屿愚,我是刑警寧澤汇跨,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站妆距,受9級特大地震影響穷遂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜娱据,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一蚪黑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧中剩,春花似錦忌穿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至郊愧,卻和暖如春朴译,著一層夾襖步出監(jiān)牢的瞬間沸伏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工动分, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毅糟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓澜公,卻偏偏與公主長得像姆另,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坟乾,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容