Flutter Dio 网络请求

本来一直看书上,但是这部分讲的比较模糊不怎么好理解,所以就准备自己整理一下关于Dio网络请求的知识点。
Dio Github地址:https://github.com/flutterchina/dio
Github上列出了大多数使用场景,先好好看好好学。
有人搬运并翻译的官方文档:
https://blog.csdn.net/mqdxiaoxiao/article/details/102859897

dio: ^3.0.9

一 Json数据实体类

类比于Android原生,我们的网络请求 如果是服务器返回JSON数据首先要有一个实体类来保存数据,Android Studio上有JAVA和Kotlin的根据Json数据生成实体类插件,当然也有Dart的生成实体类插件:
FlutterJsonBeanFactory直接搜(直接搜“ FlutterJson”就可以)
生成实体类:
https://blog.csdn.net/yuzhiqiang_1993/article/details/88533166

FlutterJsonBeanFactory

二 Dio请求的基本使用

2.1 get 请求百度首页“http://www.baidu.com”,获取其内容:

这就是最简单的一个Dio使用例子

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

可以运行看一下:


百度首页

2.2 get 有Json数据的请求返回

这里我采用的是极速数据的一个免费开放api:笑话接口
get/post均可
https://api.jisuapi.com/xiaohua/text?pagenum=1&pagesize=1&sort=addtime&appkey=******
它返回的Json数据内容如下:

{
    "status": 0,
    "msg": "ok",
    "result": {
        "total": 79630,
        "pagenum": 1,
        "pagesize": 1,
        "list": [
            {
                "content": "王自健在节目中调侃,对于老婆打自己这件事没有任何不满,没有任何抱怨。 这反映了一个问题,在中国: 老婆就是用来爱的, 老公就是用来打的。 中国妇女的地位真的提高了,可喜可贺!",
                "addtime": "2020-03-28 03:20:02",
                "url": "http://m.kaixinhui.com/detail-128412.html"
            }
        ]
    }
}

根据这个数据使用FlutterJsonBeanFactory 生成数据实体类:


实体类

创建接口请求方法:

  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);
    }
  }

调用一下这个方法就可以看到请求结果了。
这里我们可以看到使用了queryParameters属性,类似于Retrofit中的@Query,将get方法“?”后边的值以map的形式传入,这样的好处是可以动态修改请求参数,灵活的修改请求方法传入的参数针对不同情况的接口调用.稍微修改一下之前的方法:

  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);
    }
  }

到这里其实我们还没有用到Json数据转化的实体类,请看:

  JisuJokeEntity jokeEntity;
  Future<void> getJiSuJoke(int pagesize) async {
    Dio dio = Dio(); //创建dio对象
    int pagenum = 1; //设置请求参数
    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);
      //请求体结果response,将数据转化为实体类
      jokeEntity =
          JisuJokeEntity().fromJson(json.decode(response.data.toString()));
      print(response);
      print(jokeEntity.result.xList[0].content);
    } catch (e) {
      print(e);
    }
  }

想要在日志里看到请求过程,只需要添加打印日志拦截即可:

   Dio dio = Dio(); //创建dio对象
   //添加请求拦截  LogInterceptor内 想看什么将什么传入ture
   dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //开启请求日志

post的用法和post传输数据官网上写的很清楚,后续补充

二 Dio的单例模式和封装

建议在项目中使用Dio单例,这样便可对同一个dio实例发起的所有请求进行一些统一的配置,比如设置公共header、请求基地址、超时时间等;

之前我们在每个请求方法中都新建了一个Dio对象,这样其实是不推荐的,因为我们需要在整个项目中统一配置添加header,或者配置BaseUrl这些,所以推荐在一个项目中只使用一个Dio对象,方便统一管理。既然是这样,这就要求我们对Dio进行封装。
这是我在网上找到了一个封装类,自己稍微修改了一下下,内置了get/post/downloadFile三个方法,待完善

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 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
    options = BaseOptions(
      //请求基地址,可以包含子路径
      baseUrl: Api.BASE_URL,
      //连接服务器超时时间,单位是毫秒.
      connectTimeout: 10000,
      //响应流上前后两次接受到数据的间隔,单位为毫秒。
      receiveTimeout: 5000,
      //Http请求头.
      headers: {
        //do something
        "version": "1.0.0"
      },
      //请求的Content-Type,默认值是"application/json; charset=utf-8",Headers.formUrlEncodedContentType会自动编码请求体.
      contentType: Headers.formUrlEncodedContentType,
      //表示期望以那种格式(方式)接受响应数据。接受四种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
      responseType: ResponseType.plain,
    );

    dio = Dio(options);

    //添加日志请求拦截 显示日志

    dio.interceptors.add(LogInterceptor(responseBody: true,requestBody: true)); //开启请求日志


    //Cookie管理 这个暂时不清楚
    //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("响应之前");
      // Do something with response data
      return response; // continue
    }, onError: (DioError e) {
     // print("错误之前");
      // Do something with response error
      return e; //continue
    }));
  }

  /*
   * get请求
   * options:单个请求自定义配置
   * data:query  ?后的数据
   *
   */
  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; 响应体
//      response.headers; 响应头
//      response.request; 请求体
//      response.statusCode; 状态码

    } 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) {
        //进度
        print("$count $total");
      });
      print('downloadFile success---------${response.data}');
    } on DioError catch (e) {
      print('downloadFile error---------$e');
      formatError(e);
    }
    return response.data;
  }

  /*
   * error统一处理
   */
  void formatError(DioError e) {
    if (e.type == DioErrorType.CONNECT_TIMEOUT) {
      // It occurs when url is opened timeout.
      print("连接超时");
    } else if (e.type == DioErrorType.SEND_TIMEOUT) {
      // It occurs when url is sent timeout.
      print("请求超时");
    } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
      //It occurs when receiving timeout
      print("响应超时");
    } else if (e.type == DioErrorType.RESPONSE) {
      // When the server response, but with a incorrect status, such as 404, 503...
      print("出现异常");
    } 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("未知错误");
    }
  }

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

然后我们需要一个统一管理URL的API

class Api {
  //应该根据当前的编译环境确定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);
      //请求体结果response,将数据转化为实体类
      jokeEntity =
          JisuJokeEntity().fromJson(json.decode(response.data.toString()));
      //print(response);
      print(jokeEntity.result.xList[0].content);
    } catch (e) {
      print(e);
    }
  }

这里有点Android MVP的那种感觉了 不过目前只是demo项目 没有完全分开,之后尝试自己搭建一套Flutter的MVP或者MVVM架构。

在封装的HttpUtil中,预留了许多空,比如请求拦截的操作呀,请求成功之后服务器的返回码判断呀,header的添加呀,各个项目都有不同所以暂时先预留空位。

HttpUtil中的请求方法中都保留了一个参数options可以为每个请求单独配置请求参数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355