Flutter 网络请求之Dio库

打印 上一主题 下一主题

主题 1711|帖子 1711|积分 5133

媒介

  迩来再写Flutter系列文章,在了解过状态管理之后,我们再来学习一下网络请求。
正文

  网络请求对于一个线上的App来说是必不可少的,那么Flutter中的网络请求同样也是官方的没有第三方的那么好用,这里我们使用Dio,现在来说比较好用简便的网络库。
一、配置项目

  起首我们创建一个名为study_http的项目。

创建项目之后,我们配置一下依赖库,在项目标pubspec.yaml文件中,添加如下所示代码:
  1. dependencies:
  2.   
  3.   get:
  4.   
  5.   dio: ^5.4.0
复制代码
添加位置如下图所示:

然后点击Pub get,获取并安装所添加的库,安装成功之后,项目配置完成。
二、网络请求

  下面我们来计划一个场景,页面上有一个图片和一个按钮,默认表现一个图片,点击按钮之后请求网络接口,返回一个图片,将这个请求返回的网络图片表现出来,起首我们在lib下新建一个https的目录,然后这个目录下新建一个https_page.dart文件 ,内里代码如下所示:
  1. import 'dart:convert';
  2. import 'package:flutter/material.dart';
  3. import 'package:dio/dio.dart';
  4. import 'package:get/get.dart';
  5. class HttpsPage extends StatelessWidget {
  6.   var imgPath =
  7.       "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1h31Ip.img?w=768&h=1226&m=6&x=326&y=887&s=506&d=118"
  8.           .obs;
  9.   final dio = Dio();
  10.   void request() async {
  11.     var response = await dio.get('https://www.dmoe.cc/
  12. random.php?return=json
  13. ');
  14.     //转化为Json
  15.     String jsonString = jsonEncode(response.data);
  16.     print(jsonString);
  17.     // 解析JSON字符串
  18.     Map<String, dynamic> json = jsonDecode(jsonString);
  19.     // 获取特定字段值
  20.     imgPath.value = json['imgurl'];
  21.   }
  22.   @override
  23.   Widget build(BuildContext context) {
  24.     return Scaffold(
  25.         body: Center(
  26.       child: Container(
  27.         child: Column(
  28.           mainAxisAlignment: MainAxisAlignment.center,
  29.           children: [
  30.             Obx(() => Image.network(
  31.                   imgPath.value,
  32.                   width: 200,
  33.                 )),
  34.             SizedBox(height: 10),
  35.             ElevatedButton(
  36.               onPressed: () {
  37.                 request();
  38.               },
  39.               child: Text("请求网络"),
  40.             )
  41.           ],
  42.         ),
  43.       ),
  44.     ));
  45.   }
  46. }
复制代码
  说明一下这个代码,这里使用了Get库,不了解的可以看看我上一篇文章:Flutter 状态管理之GetX库,创建了一个可观察的变量,然后写了一个请求网络的方法,使用了Dio库的Get请求,请求一个API地址,你可以将这个地址在浏览器中测试,确保它可以返回值。这是我请求的结果,如下图所示:

  通过网络请求会返回一个response 对象,我们将对象转换为Json字符串,然后再获取字符串中的imgurl的值,也就是这个图片的网络地址链接,末了再更新这个imgPath的值,Obx()包裹的内容就会刷新,下面我们运行一下看看效果:

这是默认的图片,然后点击一下请求网络的按钮,经过短暂的网络耽误之后就会加载出网络请求返回后的图片,如下图所示:

这个请求返回的图片类似于逐日一图,以是厘革很大,因此你只要有加载出来就可以,不须要跟我一样。
三、封装

  在对Dio库举行进使用用的时间,我们通常会举行封装而不是直接使用。Flutter原生的网络请求是使用HttpClient,使用起来相称繁琐,因此Dio对于HttpClient举行了封装,那么我们为什么还须要对Dio举行封装呢?这就是考虑到现实中的业务处理了,封装都是针对于现实情况来的,下面我们看看怎么封装这个Dio库。
① 单例模式

  在使用网络请求时,通常会有多个网络请求,我们可以写一个单例,将一些根本的内容写在单例内里,写几个方法供其他地方调用,下面我们起首来写一个单例在lib下新建一个net包,包下新建一个network_manager.dart文件,代码如下所示:
  1. import 'package:dio/dio.dart';
  2. /// 网络管理
  3. class NetworkManager {
  4.   static NetworkManager? _instance = NetworkManager._internal();
  5.   late Dio dio;
  6.   static NetworkManager getInstance() {
  7.     _instance ??= NetworkManager._internal();
  8.     return _instance!;
  9.   }
  10.   NetworkManager._internal() {
  11.     // 配置BaseOptions
  12.     BaseOptions options = BaseOptions(
  13.         baseUrl: "",
  14.         //连接超时
  15.         connectTimeout: const Duration(seconds: 15),
  16.         //接收超时
  17.         receiveTimeout: const Duration(seconds: 10),
  18.         //内容类型
  19.         contentType: 'application/json;Charset=UTF-8',
  20.         //响应数据类型:Json
  21.         responseType: ResponseType.json);
  22.     dio = Dio(options);
  23.   }
  24.   get(String url, {option, params}) async {
  25.     Response response;
  26.     try {
  27.       response =
  28.           await dio.get(url, options: Options(responseType: ResponseType.json));
  29.       print("response.data:${response.data}");
  30.       print("response.data:${response.statusCode}");
  31.       print("response.data:${response.statusMessage}");
  32.       print("response.data:${response.headers}");
  33.     } on Exception catch (e) {
  34.       print("Get方法出错:${e.toString()}");
  35.     }
  36.   }
  37.   
  38. }
复制代码
  下面说明一下上面代码,起首我们写了一个getInstance()方法,在这内里判断_instance 是否为空,为空则NetworkManager._internal(),对dio举行一些根本的配置,然后初始化dio 对象,不为空则,直接返回_instance 。然后写了一个get()方法,方法内里就是一个get请求,我们在之前已经页面中已经写好了,同时我们打印一下返回的数据,下面我们在前面的页面中改造一下。修改https_page.dart中的request()方法,代码如下所示:
  1.   void request() async {
  2.     NetworkManager.getInstance().get('https://www.dmoe.cc/
  3. random.php?return=json
  4. ');
  5.   }
复制代码
这里就是直接使用单例中的方法,我们就不须要再当前页面创建dio对象了,运行一下,看控制台日志,如下图所示:

现在我们的方法在单例中有效果,我们继续往下走。
② 网络拦截器

  现在的这个日志确实不怎么好看,为了解决这个问题,也为了我们看日志的时间一目了然,我们可以自定义一个拦截器,在net包下新建一个interceptor包,该包下新建一个custom_interceptor.dart文件,内里的代码如下所示:
  1. import 'dart:convert';
  2. import 'package:dio/dio.dart';
  3. import 'package:flutter/foundation.dart';
  4. ///日志拦截器
  5. class CustomInterceptor extends Interceptor {
  6.   @override
  7.   void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
  8.     StringBuffer buffer = StringBuffer();
  9.     buffer.write('⌈‾‾ Request ヾ(•ω•`)o \n');
  10.     buffer.write('| \n');
  11.     buffer.write('| - Url:   ${options.baseUrl + options.path}\n');
  12.     buffer.write('| - Method:${options.method}\n');
  13.     buffer.write('| - Header:${options.headers.toString()}\n');
  14.     final data = options.data;
  15.     if (data != null) {
  16.       if (data is Map) {
  17.         buffer.write('| - Body:  ${options.data.toString()}\n');
  18.       } else if (data is FormData) {
  19.         final formDataMap = {}
  20.           ..addEntries(data.fields)
  21.           ..addEntries(data.files);
  22.         buffer.write("| - Body:  ${formDataMap.toString()}\n");
  23.       } else {
  24.         buffer.write("| - Body:  ${data.toString()}\n");
  25.       }
  26.     }
  27.     buffer.write(
  28.         '⌊_____________________________________________________________________');
  29.     printDebugLog(buffer);
  30.     return handler.next(options);
  31.   }
  32.   @override
  33.   void onResponse(Response response, ResponseInterceptorHandler handler) {
  34.     StringBuffer buffer = StringBuffer();
  35.     buffer.write('⌈‾‾ Response O(∩_∩)O \n');
  36.     buffer.write('| \n');
  37.     buffer.write('| - Code:   ${response.statusCode}\n');
  38.     buffer.write('| - CodeMsg:${response.statusMessage}\n');
  39.     buffer.write('| - Header:\n');
  40.     response.headers.forEach((key, value) {
  41.       buffer.write('|    $key  $value\n');
  42.     });
  43.     final data = response.data;
  44.     if (data != null) {
  45.       if (data is Map) {
  46.         buffer.write('| - Data:  ${response.data.toString()}\n');
  47.         String dataJson = jsonEncode(response.data);
  48.         buffer.write('| - Json:  $dataJson\n');
  49.       } else if (data is FormData) {
  50.         final formDataMap = {}
  51.           ..addEntries(data.fields)
  52.           ..addEntries(data.files);
  53.         buffer.write("| - Data:  ${formDataMap.toString()}\n");
  54.       } else {
  55.         buffer.write("| - Data:  ${data.toString()}\n");
  56.       }
  57.     }
  58.     buffer.write(
  59.         '⌊_____________________________________________________________________');
  60.     printDebugLog(buffer);
  61.     return handler.next(response);
  62.   }
  63.   @override
  64.   void onError(DioException err, ErrorInterceptorHandler handler) {
  65.     //处理错误信息
  66.     handlerError(err);
  67.     StringBuffer buffer = StringBuffer();
  68.     buffer.write('⌈‾‾ Error (っ °Д °;)っ\n');
  69.     buffer.write('| \n');
  70.     buffer.write('| - ExceptionType:${err.type.name}\n');
  71.     buffer.write('| - ErrorMsg:     ${err.message}\n');
  72.     buffer.write('| - StatusCode:   ${err.response?.statusCode}\n');
  73.     buffer.write('| - StatusMsg:    ${err.response?.statusMessage}\n');
  74.     buffer.write(
  75.         '⌊_____________________________________________________________________');
  76.     printDebugLog(buffer);
  77.     return handler.next(err);
  78.   }
  79.   ///处理错误信息 --自行去实现里面的功能代码
  80.   void handlerError(DioException err) {
  81.     switch (err.type) {
  82.       //连接超时
  83.       case DioExceptionType.connectionTimeout:
  84.         break;
  85.       //响应超时
  86.       case DioExceptionType.receiveTimeout:
  87.         break;
  88.       //发送超时
  89.       case DioExceptionType.sendTimeout:
  90.         break;
  91.       //请求取消
  92.       case DioExceptionType.cancel:
  93.         break;
  94.       //错误响应 404 等
  95.       case DioExceptionType.badResponse:
  96.         break;
  97.       //错误证书
  98.       case DioExceptionType.badCertificate:
  99.         break;
  100.       //未知错误
  101.       default:
  102.         break;
  103.     }
  104.   }
  105.   void printDebugLog(StringBuffer buffer) {
  106.     if (kDebugMode) {
  107.       print(buffer.toString());
  108.     }
  109.   }
  110. }
复制代码
  在这内里我们继承了创建CustomInterceptor 类,继承Dio的Interceptor ,重写内里onRequest(请求前)、onResponse(响应前)、onError(错误时)的拦截方法,在内里对于相关数据信息举行打印,同时只在debug模式下打印,下面我们回到NetworkManager中,使用这个自定义拦截器。
  1. import 'interceptor/custom_interceptor.dart';
复制代码
起首导包,然后在_internal()方法中增加如下代码:
  1.     //添加日志拦截器
  2.     dio.interceptors.add(CustomInterceptor());
复制代码
添加位置如下图所示:

再将get方法中的打印注释掉

然后我们重新运行一下,请求网络接口,查看控制台日志,如下图所示:

这样看起来是否会清晰一些呢,可以自行调解,我们接着往下走。
③ 返回值封装

  对返回值的封装,我们可以分为两步,第一步就是在响应前封装,第二步在响应后转换。先来看第一步,在net包下新建一个response包,该包下新建一个base_response.dart,代码如下所示:
  1. ///自定义响应封装
  2. class BaseResponse<T> {
  3.   //状态码
  4.   final int? code;
  5.   //状态描述
  6.   final String? msg;
  7.   //数据
  8.   final T data;
  9.   BaseResponse({required this.code,required this.msg,required this.data});
  10.   @override
  11.   String toString() {
  12.     StringBuffer buffer = StringBuffer();
  13.     buffer.write('{');
  14.     buffer.write('"code":$code');
  15.     buffer.write('"msg":"$msg"');
  16.     buffer.write('"data":"$data"');
  17.     buffer.write('}');
  18.     return super.toString();
  19.   }
  20. }
复制代码
  这里就是对默认的Response举行一次封装,然后这里的data就是我们接口所拿到的返回值, 下面我们改动一下之前的自定义拦截器custom_interceptor.dart中的代码,主要就是修改onResponse()方法,代码如下:
  1.   @override
  2.   void onResponse(Response response, ResponseInterceptorHandler handler) {
  3.     //返回自定义的Base
  4.     final baseResponse = BaseResponse(code: response.statusCode, msg: response.statusMessage, data: response.data);
  5.     response.data = baseResponse;
  6.     StringBuffer buffer = StringBuffer();
  7.     buffer.write('⌈‾‾ Response O(∩_∩)O \n');
  8.     buffer.write('| \n');
  9.     buffer.write('| - Code:   ${response.statusCode}\n');
  10.     buffer.write('| - CodeMsg:${response.statusMessage}\n');
  11.     buffer.write('| - Header:\n');
  12.     response.headers.forEach((key, value) {
  13.       buffer.write('|    $key  $value\n');
  14.     });
  15.     final data = response.data;
  16.     if (data != null) {
  17.       if (data is Map) {
  18.         buffer.write('| - Data:  ${response.data.toString()}\n');
  19.         String dataJson = jsonEncode(response.data);
  20.         buffer.write('| - Json:  $dataJson\n');
  21.       } else if (data is FormData) {
  22.         final formDataMap = {}
  23.           ..addEntries(data.fields)
  24.           ..addEntries(data.files);
  25.         buffer.write("| - Data:  ${formDataMap.toString()}\n");
  26.       } else {
  27.         buffer.write("| - Data:  ${baseResponse.data.toString()}\n");
  28.       }
  29.     }
  30.     buffer.write(
  31.         '⌊_____________________________________________________________________');
  32.     printDebugLog(buffer);
  33.     return handler.next(response);
  34.   }
复制代码
焦点代码就是这一段

  将response.data封装到BaseResponse中,然后再赋值返回。然后我们再对返回值举行一个JSON转Bean的操纵,AS中提供了一个插件,FlutterJsonBeanFactory,安装。

  安装好之后,我们可以重启一下AS,然后就来根据JSON返回值构建Dart的Bean。在lib包下新建一个model包,然后鼠标右键model包,点击New → JsonToDartBeanAction,如下图所示:

输入文件名称,然后将接口返回的JOSN:
  1. {
  2.     "code": "200",
  3.     "imgurl": "https://image.baidu.com/search/down?url=https://tvax3.sinaimg.cn//large/a15b4afegy1fmvk16yis3j21hc0u0tpx.jpg",
  4.     "width": "1920",
  5.     "height": "1080"
  6. }
复制代码
赋值进去,如下图所示:

点击Make,完成构建。

  构建之后会在model包下天生一个img_entity.dart,我刚才输入的是img,_entity是这个插件本身添加的,然后会天生一个generated文件夹,内里可以看到一个img_entity.g.dart文件,内里的内容就是对你JSON和Bean之间的转化代码的天生,我们不须要关心。先不急着使用这个返回值,我们继续往下走。
④ 封装请求

  接着我们封装请求方法,针对网络请求有get、post、put等等方式,在dio库中,最终现实上调用的都是request请求,在net包下新建一个method包,该包下新建一个bese_method.dart,代码如下:
  1. enum BaseMethod {
  2.   get,
  3.   post,
  4.   put,
  5.   delete,
  6.   patch,
  7.   head
  8. }
复制代码
  这里代码很简朴,就是列举了dio的网络请求方式,然后我们回到network_manager.dart中,在内里新增一个request()方法,其他的代码也做了修改,整体代码如下所示:
  1.   class NetworkManager {
  2.   factory NetworkManager() => _getInstance();
  3.   static NetworkManager? _instance = NetworkManager._initialize();
  4.   late Dio _dio;
  5.   static NetworkManager _getInstance() {
  6.     _instance ??= NetworkManager._initialize();
  7.     return _instance!;
  8.   }
  9.   NetworkManager._initialize() {
  10.     // 配置BaseOptions
  11.     BaseOptions options = BaseOptions(
  12.         baseUrl: "",
  13.         //连接超时
  14.         connectTimeout: const Duration(seconds: 15),
  15.         //接收超时
  16.         receiveTimeout: const Duration(seconds: 10),
  17.         //内容类型
  18.         contentType: 'application/json;Charset=UTF-8',
  19.         //响应数据类型:Json
  20.         responseType: ResponseType.json);
  21.     _dio = Dio(options);
  22.     //添加日志拦截器
  23.     _dio.interceptors.add(CustomInterceptor());
  24.   }
  25.   ///网络请求
  26.   Future<T> request<T>(String path,
  27.       {BaseMethod method = BaseMethod.get, Map<String, dynamic>? params,
  28.       data, Options? options}) async {
  29.     const methodValues = {
  30.       BaseMethod.get: 'get',
  31.       BaseMethod.post: 'post',
  32.       BaseMethod.put: 'put',
  33.       BaseMethod.delete: 'delete',
  34.       BaseMethod.patch: 'patch',
  35.       BaseMethod.head: 'head',
  36.     };
  37.     options ??= Options(method: methodValues[method]);
  38.     try {
  39.       Response response;
  40.       response = await _dio.request(path,
  41.           data: data, queryParameters: params, options: options);
  42.       return response.data;
  43.     } on DioException catch (e) {
  44.       throw e;
  45.     }
  46.   }
  47. }
复制代码
下面我们再回到https_page.darat中去使用,修改request()方法,代码如下所示:
  1.   void request() async {
  2.     BaseResponse response = await NetworkManager().request('https://www.dmoe.cc/
  3. random.php?return=json
  4. ');
  5.     ImgEntity imgEntity = ImgEntity.fromJson(response.data);
  6.     imgPath.value = imgEntity.imgurl;
  7.   }
复制代码
假如有报错留意一下导包
  1. import '../model/img_entity.dart';
复制代码
运行一下,效果和之前是一样的,然后我们再来改动一下,针对于这个API地址:
  1. https://www.dmoe.cc/
  2. random.php?return=json
复制代码
我们可以分为两部门。
基础地址
  1. https://www.dmoe.cc/
复制代码
功能地址
  1. random.php?return=json
复制代码
  一般的项目中,基础地址不会经常变,也就是ip地址,而不同的功能会根据现实情况去改变接口,因此这一部门我们须要和现实方法举行绑定,下面我们在NetworkManager中增加一行代码:
  1. final _mBaseUrl = "https://www.dmoe.cc/
  2. ";
复制代码
然后修改baseUrl的值,之前是空字符串,如下图所示:

再去修改现实调用的地方,如下图所示:

  这样就对一个API地址举行了分离,这在现实开发中是很常见的做法。对于dio的封装就到这里了,肯定不是美满了,因为还有许多东西没有考虑到,我们可以根据现实中的须要再去添加,我这里就不赘述了,下面我们联合GetX去使用。
四、联合GetX使用

在https包下新建一个https_controller.dart,代码如下:
  1. import 'package:get/get.dart';import '../model/img_entity.dart';
  2. import '../net/network_manager.dart';import '../net/response/base_response.dart';class HttpsController extends GetxController {  var imgPath =      "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1h31Ip.img?w=768&h=1226&m=6&x=326&y=887&s=506&d=118"          .obs;  void request() async {    BaseResponse response = await NetworkManager().request('random.php?return=json
  3. ');    ImgEntity imgEntity = ImgEntity.fromJson(response.data);    imgPath.value = imgEntity.imgurl;  }}
复制代码
这里就是将网络请求相关的变量和方法都放到HttpsController 中,然后我们再回到HttpsPage,修改代码如下所示:
  1. import 'package:flutter/material.dart';
  2. import 'package:get/get.dart';
  3. import 'https_controller.dart';
  4. class HttpsPage extends StatelessWidget {
  5.   final httpsController = Get.put(HttpsController());
  6.   @override
  7.   Widget build(BuildContext context) {
  8.     return Scaffold(
  9.         body: Center(
  10.       child: Container(
  11.         child: Column(
  12.           mainAxisAlignment: MainAxisAlignment.center,
  13.           children: [
  14.             Obx(() => Image.network(
  15.                   httpsController.imgPath.value,
  16.                   width: 200,
  17.                 )),
  18.             SizedBox(height: 10),
  19.             ElevatedButton(
  20.               onPressed: () => httpsController.request(),
  21.               child: Text("请求网络"),
  22.             )
  23.           ],
  24.         ),
  25.       ),
  26.     ));
  27.   }
  28. }
复制代码
主要改动地方如下图所示:

这样根本上就符合现在的开发理念了,数据和UI举行分离,再次运行,效果依然一样,好了,本篇文章就到这里。
五、源码

源码地址:study_http

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表