Flutter Dio 網(wǎng)絡(luò)請求

本來一直看書上,但是這部分講的比較模糊不怎么好理解,所以就準(zhǔn)備自己整理一下關(guān)于Dio網(wǎng)絡(luò)請求的知識點(diǎn)。
Dio Github地址:https://github.com/flutterchina/dio
Github上列出了大多數(shù)使用場景,先好好看好好學(xué)。
有人搬運(yùn)并翻譯的官方文檔:
https://blog.csdn.net/mqdxiaoxiao/article/details/102859897

dio: ^3.0.9

一 Json數(shù)據(jù)實(shí)體類

類比于Android原生,我們的網(wǎng)絡(luò)請求 如果是服務(wù)器返回JSON數(shù)據(jù)首先要有一個(gè)實(shí)體類來保存數(shù)據(jù),Android Studio上有JAVA和Kotlin的根據(jù)Json數(shù)據(jù)生成實(shí)體類插件,當(dāng)然也有Dart的生成實(shí)體類插件:
FlutterJsonBeanFactory直接搜(直接搜“ FlutterJson”就可以)
生成實(shí)體類:
https://blog.csdn.net/yuzhiqiang_1993/article/details/88533166

FlutterJsonBeanFactory

二 Dio請求的基本使用

2.1 get 請求百度首頁“http://www.baidu.com”,獲取其內(nèi)容:

這就是最簡單的一個(gè)Dio使用例子

  Future<String> getBaiduContent() async {
    try {
      Response response = await Dio().get("http://www.baidu.com");
      print(response);
      return response.toString();
    } catch (e) {
      print(e);
    }
  }

可以運(yùn)行看一下:


百度首頁

2.2 get 有Json數(shù)據(jù)的請求返回

這里我采用的是極速數(shù)據(jù)的一個(gè)免費(fèi)開放api:笑話接口
get/post均可
https://api.jisuapi.com/xiaohua/text?pagenum=1&pagesize=1&sort=addtime&appkey=******
它返回的Json數(shù)據(jù)內(nèi)容如下:

{
    "status": 0,
    "msg": "ok",
    "result": {
        "total": 79630,
        "pagenum": 1,
        "pagesize": 1,
        "list": [
            {
                "content": "王自健在節(jié)目中調(diào)侃,對于老婆打自己這件事沒有任何不滿,沒有任何抱怨。 這反映了一個(gè)問題,在中國: 老婆就是用來愛的, 老公就是用來打的。 中國婦女的地位真的提高了,可喜可賀!",
                "addtime": "2020-03-28 03:20:02",
                "url": "http://m.kaixinhui.com/detail-128412.html"
            }
        ]
    }
}

根據(jù)這個(gè)數(shù)據(jù)使用FlutterJsonBeanFactory 生成數(shù)據(jù)實(shí)體類:


實(shí)體類

創(chuàng)建接口請求方法:

  Future<void> getJiSuJoke() async {
  Dio dio = Dio();
    try {
      Response response = await Dio()
        .get("https://api.jisuapi.com/xiaohua/text", queryParameters: {
        "pagenum": 1,
        "pagesize": 1,
        "sort": "rand",
        "appkey": "你的APPKEY"
      });
      print(response.data.toString());
    } catch (e) {
      print(e);
    }
  }

調(diào)用一下這個(gè)方法就可以看到請求結(jié)果了。
這里我們可以看到使用了queryParameters屬性,類似于Retrofit中的@Query,將get方法“?”后邊的值以map的形式傳入,這樣的好處是可以動(dòng)態(tài)修改請求參數(shù),靈活的修改請求方法傳入的參數(shù)針對不同情況的接口調(diào)用.稍微修改一下之前的方法:

  Future<void> getJiSuJoke(int pagesize) async {
  Dio dio = Dio();
    int pagenum=1;
    Map<String,dynamic> mData = {
      "pagenum": pagenum,
      "pagesize": pagesize,
      "sort": "rand",
      "appkey": "35dc30ebaa5940ce"
    };
    try {
      Response response = await Dio()
          .get("https://api.jisuapi.com/xiaohua/text", queryParameters: mData);
      print(response.data.toString());
    } catch (e) {
      print(e);
    }
  }

到這里其實(shí)我們還沒有用到Json數(shù)據(jù)轉(zhuǎn)化的實(shí)體類,請看:

  JisuJokeEntity jokeEntity;
  Future<void> getJiSuJoke(int pagesize) async {
    Dio dio = Dio(); //創(chuàng)建dio對象
    int pagenum = 1; //設(shè)置請求參數(shù)
    Map<String, dynamic> mData = {
      "pagenum": pagenum,
      "pagesize": pagesize,
      "sort": "rand",
      "appkey": "你的APPKEY"
    };
    try {
      //開始請求
      Response response = await dio
          .get("https://api.jisuapi.com/xiaohua/text",
          queryParameters: mData);
      //請求體結(jié)果response,將數(shù)據(jù)轉(zhuǎn)化為實(shí)體類
      jokeEntity =
          JisuJokeEntity().fromJson(json.decode(response.data.toString()));
      print(response);
      print(jokeEntity.result.xList[0].content);
    } catch (e) {
      print(e);
    }
  }

想要在日志里看到請求過程,只需要添加打印日志攔截即可:

   Dio dio = Dio(); //創(chuàng)建dio對象
   //添加請求攔截  LogInterceptor內(nèi) 想看什么將什么傳入ture
   dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //開啟請求日志

post的用法和post傳輸數(shù)據(jù)官網(wǎng)上寫的很清楚,后續(xù)補(bǔ)充

二 Dio的單例模式和封裝

建議在項(xiàng)目中使用Dio單例,這樣便可對同一個(gè)dio實(shí)例發(fā)起的所有請求進(jìn)行一些統(tǒng)一的配置,比如設(shè)置公共header、請求基地址、超時(shí)時(shí)間等;

之前我們在每個(gè)請求方法中都新建了一個(gè)Dio對象,這樣其實(shí)是不推薦的,因?yàn)槲覀冃枰谡麄€(gè)項(xiàng)目中統(tǒng)一配置添加header,或者配置BaseUrl這些,所以推薦在一個(gè)項(xiàng)目中只使用一個(gè)Dio對象,方便統(tǒng)一管理。既然是這樣,這就要求我們對Dio進(jìn)行封裝。
這是我在網(wǎng)上找到了一個(gè)封裝類,自己稍微修改了一下下,內(nèi)置了get/post/downloadFile三個(gè)方法,待完善

import 'package:dio/dio.dart';
import 'api.dart';


class HttpUtil {
  static HttpUtil instance;
  Dio dio;
  BaseOptions options;

  CancelToken cancelToken = CancelToken();

  static HttpUtil getInstance() {
    if (null == instance) instance = HttpUtil();
    return instance;
  }

  /*
   * config it and create
   */
  HttpUtil() {
    //BaseOptions、Options、RequestOptions 都可以配置參數(shù),優(yōu)先級別依次遞增,且可以根據(jù)優(yōu)先級別覆蓋參數(shù)
    options = BaseOptions(
      //請求基地址,可以包含子路徑
      baseUrl: Api.BASE_URL,
      //連接服務(wù)器超時(shí)時(shí)間,單位是毫秒.
      connectTimeout: 10000,
      //響應(yīng)流上前后兩次接受到數(shù)據(jù)的間隔,單位為毫秒。
      receiveTimeout: 5000,
      //Http請求頭.
      headers: {
        //do something
        "version": "1.0.0"
      },
      //請求的Content-Type,默認(rèn)值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType會(huì)自動(dòng)編碼請求體.
      contentType: Headers.formUrlEncodedContentType,
      //表示期望以那種格式(方式)接受響應(yīng)數(shù)據(jù)。接受四種類型 `json`, `stream`, `plain`, `bytes`. 默認(rèn)值是 `json`,
      responseType: ResponseType.plain,
    );

    dio = Dio(options);

    //添加日志請求攔截 顯示日志

    dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //開啟請求日志


    //Cookie管理 這個(gè)暫時(shí)不清楚
    //dio.interceptors.add(CookieManager(CookieJar()));

    //添加攔截器
    dio.interceptors
        .add(InterceptorsWrapper(onRequest: (RequestOptions options) {
      //print("請求之前");
      // Do something before request is sent
      return options; //continue
    }, onResponse: (Response response) {
     // print("響應(yīng)之前");
      // Do something with response data
      return response; // continue
    }, onError: (DioError e) {
     // print("錯(cuò)誤之前");
      // Do something with response error
      return e; //continue
    }));
  }

  /*
   * get請求
   * options:單個(gè)請求自定義配置
   * data:query  ?后的數(shù)據(jù)
   *
   */
  get(url, {data, options, cancelToken}) async {
    Response response;
    try {
      response = await dio.get(url,
          queryParameters: data, options: options, cancelToken: cancelToken);
     // print('get success---------${response.statusCode}');
     // print('get success---------${response.data}');

//      response.data; 響應(yīng)體
//      response.headers; 響應(yīng)頭
//      response.request; 請求體
//      response.statusCode; 狀態(tài)碼

    } on DioError catch (e) {
      print('get error---------$e');
      formatError(e);
    }
    return response;
  }

  /*
   * post請求
   *
   * formData:POST傳遞form表單
   */
  post(url, {queryData,formData, options, cancelToken}) async {
    Response response;
    try {
      response = await dio.post(url,data: formData,
          queryParameters: queryData, options: options, cancelToken: cancelToken);
      print('post success---------${response.data}');
    } on DioError catch (e) {
      print('post error---------$e');
      formatError(e);
    }
    return response;
  }

  /*
   * 下載文件
   */
  downloadFile(urlPath, savePath) async {
    Response response;
    try {
      response = await dio.download(urlPath, savePath,
          onReceiveProgress: (int count, int total) {
        //進(jìn)度
        print("$count $total");
      });
      print('downloadFile success---------${response.data}');
    } on DioError catch (e) {
      print('downloadFile error---------$e');
      formatError(e);
    }
    return response.data;
  }

  /*
   * error統(tǒng)一處理
   */
  void formatError(DioError e) {
    if (e.type == DioErrorType.CONNECT_TIMEOUT) {
      // It occurs when url is opened timeout.
      print("連接超時(shí)");
    } else if (e.type == DioErrorType.SEND_TIMEOUT) {
      // It occurs when url is sent timeout.
      print("請求超時(shí)");
    } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
      //It occurs when receiving timeout
      print("響應(yīng)超時(shí)");
    } else if (e.type == DioErrorType.RESPONSE) {
      // When the server response, but with a incorrect status, such as 404, 503...
      print("出現(xiàn)異常");
    } else if (e.type == DioErrorType.CANCEL) {
      // When the request is cancelled, dio will throw a error with this type.
      print("請求取消");
    } else {
      //DEFAULT Default error type, Some other Error. In this case, you can read the DioError.error if it is not null.
      print("未知錯(cuò)誤");
    }
  }

  /*
   * 取消請求
   *
   * 同一個(gè)cancel token 可以用于多個(gè)請求,當(dāng)一個(gè)cancel token取消時(shí),所有使用該cancel token的請求都會(huì)被取消。
   * 所以參數(shù)可選
   */
  void cancelRequests(CancelToken token) {
    token.cancel("cancelled");
  }
}

然后我們需要一個(gè)統(tǒng)一管理URL的API

class Api {
  //應(yīng)該根據(jù)當(dāng)前的編譯環(huán)境確定BASE_URL 
  static const String BASE_URL = "https://api.jisuapi.com/";
  static const String JISUJOKE = "xiaohua/text";
}

然后就可以用了,還是剛剛的笑話接口:

  Future<void> getJiSuJoke2() async {
    Map<String, dynamic> mData = {
      "pagenum": 1,
      "pagesize": 1,
      "sort": "rand",
      "appkey": APPKEY
    };
    try {
      //開始請求
      var response =await HttpUtil().post(Api.JISUJOKE,
          queryData: mData);
      //請求體結(jié)果response,將數(shù)據(jù)轉(zhuǎn)化為實(shí)體類
      jokeEntity =
          JisuJokeEntity().fromJson(json.decode(response.data.toString()));
      //print(response);
      print(jokeEntity.result.xList[0].content);
    } catch (e) {
      print(e);
    }
  }

這里有點(diǎn)Android MVP的那種感覺了 不過目前只是demo項(xiàng)目 沒有完全分開,之后嘗試自己搭建一套Flutter的MVP或者M(jìn)VVM架構(gòu)。

在封裝的HttpUtil中,預(yù)留了許多空,比如請求攔截的操作呀,請求成功之后服務(wù)器的返回碼判斷呀,header的添加呀,各個(gè)項(xiàng)目都有不同所以暫時(shí)先預(yù)留空位。

HttpUtil中的請求方法中都保留了一個(gè)參數(shù)options可以為每個(gè)請求單獨(dú)配置請求參數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容