flutter入门系列教程<2>:Http请求库-dio的使用

文章目录

11.3 Http请求库-dio

本处示例来自:https://book.flutterchina.club/chapter11/dio.html

通过上一节介绍,我们可以发现直接使用HttpClient发起网络请求是比较麻烦的,很多事情得我们手动处理,如果再涉及到文件上传/下载、Cookie管理等就会非常繁琐。幸运的是,Dart社区有一些第三方http请求库,用它们来发起http请求将会简单的多,本节我们介绍一下目前人气较高的dio (opens new window)库。

dio是笔者维护的一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等。dio的使用方式随着其版本升级可能会发生变化,如果本节所述内容和最新dio功能有差异,请以最新的dio文档为准。

11.3.1 引入dio

引入dio:

dart 复制代码
dependencies:
  dio: ^x.x.x #请使用pub上的最新版本

导入并创建dio实例:

dart 复制代码
import 'package:dio/dio.dart';
Dio dio =  Dio();

接下来就可以通过 dio实例来发起网络请求了,注意,一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。

11.3.2 通过dio发起请求

发起 GET 请求 :

dart 复制代码
Response response;
response=await dio.get("/test?id=12&name=wendu")
print(response.data.toString());

对于GET请求我们可以将query参数通过对象来传递,上面的代码等同于:

dart 复制代码
response=await dio.get("/test",queryParameters:{"id":12,"name":"wendu"})
print(response);

发起一个 POST 请求:

dart 复制代码
response=await dio.post("/test",data:{"id":12,"name":"wendu"})

发起多个并发请求:

dart 复制代码
response= await Future.wait([dio.post("/info"),dio.get("/token")]);

下载文件:

dart 复制代码
response=await dio.download("https://www.google.com/",_savePath);

发送 FormData:

dart 复制代码
FormData formData = FormData.from({
   "name": "wendux",
   "age": 25,
});
response = await dio.post("/info", data: formData)

如果发送的数据是FormData,则dio会将请求header的contentType设为"multipart/form-data"。

通过FormData上传多个文件:

dart 复制代码
FormData formData = FormData.from({
   "name": "wendux",
   "age": 25,
   "file1": UploadFileInfo(File("./upload.txt"), "upload1.txt"),
   "file2": UploadFileInfo(File("./upload.txt"), "upload2.txt"),
     // 支持文件数组上传
   "files": [
      UploadFileInfo(File("./example/upload.txt"), "upload.txt"),
      UploadFileInfo(File("./example/upload.txt"), "upload.txt")
    ]
});
response = await dio.post("/info", data: formData)

值得一提的是,dio内部仍然使用HttpClient发起的请求,所以代理、请求认证、证书校验等和HttpClient是相同的,我们可以在onHttpClientCreate回调中设置,例如:

dart 复制代码
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
    //设置代理 
    client.findProxy = (uri) {
      return "PROXY 192.168.1.2:8888";
    };
    //校验证书
    httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
      if(cert.pem==PEM){
      return true; //证书一致,则允许发送数据
     }
     return false;
    };   
  };

注意,onHttpClientCreate会在当前dio实例内部需要创建HttpClient时调用,所以通过此回调配置HttpClient会对整个dio实例生效,如果应用需要多种代理或证书校验策略,可以创建不同的dio实例来分别实现。

怎么样,是不是很简单,除了这些基本的用法,dio还支持请求配置、拦截器等,官方资料比较详细,故本书不再赘述,详情可以参考dio主页:https://github.com/flutterchina/dio 。 下一节我们将使用dio实现一个分块下载器。

11.3.3 实例

我们通过Github开放的API来请求flutterchina组织下的所有公开的开源项目,实现:

在请求阶段弹出loading

请求结束后,如果请求失败,则展示错误信息;如果成功,则将项目名称列表展示出来。

代码如下:

dart 复制代码
class _FutureBuilderRouteState extends State<FutureBuilderRoute> {
  Dio _dio = Dio();

  @override
  Widget build(BuildContext context) {

    return Container(
      alignment: Alignment.center,
      child: FutureBuilder(
          future: _dio.get("https://api.github.com/orgs/flutterchina/repos"),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            //请求完成
            if (snapshot.connectionState == ConnectionState.done) {
              Response response = snapshot.data;
              //发生错误
              if (snapshot.hasError) {
                return Text(snapshot.error.toString());
              }
              //请求成功,通过项目信息构建用于显示项目名称的ListView
              return ListView(
                children: response.data.map<Widget>((e) =>
                    ListTile(title: Text(e["full_name"]))
                ).toList(),
              );
            }
            //请求未完成时弹出loading
            return CircularProgressIndicator();
          }
      ),
    );
  }
}

实战:注意点

实战以https://api.codelife.cc/todayShici?lang=cn接口为例,大家可以直接访问该接口

接口返回格式如下(直接Android Studio的devTools可查看):

1.声明返回参数model

示例返回的model声明如下

dart 复制代码
// 返回的类字段
class PoetryData {
  final String author;
  final String content;
  final String dynasty;
  final String preface;
  final String quotes;
  final String reviews;
  final String title;
  final String translate;
  final String annotation;
  final String updateTime;
  final String createTime;

  PoetryData({
    required this.author,
    required this.content,
    required this.dynasty,
    required this.preface,
    required this.quotes,
    required this.reviews,
    required this.title,
    required this.translate,
    required this.annotation,
    required this.updateTime,
    required this.createTime,
  });

  factory PoetryData.fromJson(Map<String, dynamic> json) {
    return PoetryData(
      author: json['author'],
      content: json['content'],
      dynasty: json['dynasty'],
      preface: json['preface'],
      quotes: json['quotes'],
      reviews: json['reviews'],
      title: json['title'],
      translate: json['translate'],
      annotation: json['annotation'],
      updateTime: json['updateTime'],
      createTime: json['createTime'],
    );
  }
}

// 我的 - 诗词API测试
class PoetryResponse {
  final int code;
  final String msg;
  final PoetryData data;

  PoetryResponse({
    required this.code,
    required this.msg,
    required this.data,
  });

  factory PoetryResponse.fromJson(Map<String, dynamic> json) {
    return PoetryResponse(
      code: json['code'],
      msg: json['msg'],
      data: PoetryData.fromJson(json['data']),
    );
  }
}

2.请求接口

dart 复制代码
const apiUrl = 'https://api.codelife.cc/todayShici?lang=cn';
      final response = await dio.get(apiUrl);

3.渲染组件

主页面的全部代码如下:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import '/utils/platform_check.dart';
import '/components/custom/tabBarFree.dart';
import '/model/mine/poetry.dart';
import 'package:intl/intl.dart';

// 我的 - 随机诗词 - iTab诗词源
class ITabPoetryPage extends StatefulWidget {
  const ITabPoetryPage({super.key});

  @override
  State<ITabPoetryPage> createState() => _ITabPoetryPageState();
}

class _ITabPoetryPageState extends State<ITabPoetryPage> {
  PoetryResponse? myResult;

  // 如果是web端,则不设置请求头,否则设置
  final dio = Dio(BaseOptions(
      headers: {
        if(!PlatformCheck.isWeb)
          'Content-Type': 'application/json',
        if(!PlatformCheck.isWeb)
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
      }
  ));

  @override
  void initState() {
    super.initState();
    debugPrint('init初始化,是否为web端:${PlatformCheck.isWeb}');
    getHttp();
  }

  Future<void> getHttp() async {
    try {
      // const apiUrl = 'https://v1.jinrishici.com/all.json';
      const apiUrl = 'https://api.codelife.cc/todayShici?lang=cn';
      final response = await dio.get(apiUrl);
      debugPrint('接口返回值: $response');
      setState(() {
        myResult = PoetryResponse.fromJson(response.data);
      });
    } catch (e) {
      debugPrint('报错啦啦啦啦:$e');
    }
  }

  Widget paddingWidget(Widget child) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: child,
    );
  }

  // 构建内容
  Widget buildText(String? data) {
    return data != null ? paddingWidget(Text(data)) : const Text('暂无数据');
  }

  // 刷新按钮
  Widget refreshBtn() {
    return OutlinedButton(
        onPressed: getHttp,
        child: const Text('今日诗词刷新')
    );
  }

  // 格式化时间
  String formatDateTime(String? dateTimeString) {
    if (dateTimeString == null) return '暂无时间数据';
    try {
      final dateTime = DateTime.parse(dateTimeString);
      final formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
      return formatter.format(dateTime);
    } catch (e) {
      debugPrint('日期格式化错误: $e');
      return '日期格式化错误';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: FreeTabBar(
        topWidget: myResult != null
          ? Column(
              children: [
                refreshBtn(),
                const SizedBox(height: 10,),
                // 标题
                Text(myResult!.data.title),
                // 朝代 - 作者
                Text('${myResult!.data.dynasty} · ${myResult!.data.author}',),
                // 内容
                Text(myResult!.data.content),
              ],
            ) : refreshBtn(),
        bottomWidget: Column(
          children: [
            // 创建时间
            Text(formatDateTime(myResult?.data.createTime)),
          ],
        ),
        tabs: const ['译文', '注释', '引言', '评语', '引言'],
        children: [
          // 译文
          buildText(myResult?.data.translate),
          // 注释
          buildText(myResult?.data.annotation),
          // 引言
          buildText(myResult?.data.preface),
          // 评语
          buildText(myResult?.data.reviews),
          // 引语
          buildText(myResult?.data.quotes),
        ],
      ),
    );
  }
}

效果图如下:

相关推荐
兰雪簪轩5 小时前
分布式通信平台测试报告
开发语言·网络·c++·网络协议·测试报告
司徒小夜7 小时前
HTTP与HTTPS杂谈-HTTPS防御了什么
网络·http·https
只因在人海中多看了你一眼7 小时前
B.50.10.09-RPC核心原理与电商应用
qt·网络协议·rpc
新镜9 小时前
【Flutter】RefreshIndicator 无法下拉刷新问题
flutter
星秋Eliot9 小时前
Flutter的三棵树
前端·flutter
小鸟啄米9 小时前
Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头停止移动 Stop 功能
网络协议·elixir·onvif
小鸟啄米11 小时前
Elixir通过Onvif协议控制IP摄像机,扩展ExOnvif的摄像头连续移动功能 ContinuousMove
网络协议·elixir·onvif
一只游鱼12 小时前
利用keytool实现https协议(生成自签名证书)
网络协议·http·https·keytool
学会煎墙13 小时前
3分钟快速入门WebSocket
网络·websocket·网络协议
码熔burning13 小时前
RPC 和 HTTP 的区别
网络协议·http·rpc