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 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 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 getJiSuJoke(int pagesize) async {
  Dio dio = Dio();
    int pagenum=1;
    Map 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 getJiSuJoke(int pagesize) async {
    Dio dio = Dio(); //创建dio对象
    int pagenum = 1; //设置请求参数
    Map 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 getJiSuJoke2() async {
    Map 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可以为每个请求单独配置请求参数

你可能感兴趣的:(Flutter Dio 网络请求)