该文已授权公众号 「码个蛋」,转载请指明出处

前面讲完了常用的部件,BLoC 模式,数据持久化等常用的,今天再介绍个重头戏 —— 网络请求

HttpClient

HttpClientdart 自带的网络请求方式,在 dart:io 包下。使用 HttpClient 作为请求分以下几个步骤

  1. 创建 HttpClient 实例

    HttpClient client = HttpClient();
    复制代码
  2. 打开连接,并设置一些头参数,请求参数等

    // 如果 url 中没有查询参数可直接创建
    Uri uri = Uri.parse('https://www.xxx.com');
    // 如果存在查询参数则在 Uri 中添加
    Uri uri = Uri(scheme: 'https', host: 'www.xxx.com', queryParameters: {'a': 'AAA'});
    // 打开连接
    HttpClientRequest request = await client.getUrl(uri);
    request.headers.add('token', 'Bear ${'x' * 20}'); // 添加头部 token 信息
    // 如果是 post 或者 put 请求,通过 `add` 添加请求体
    // 因为 `add` 方法需要传入 `List<int>` 参数,可以通过 utf8.encode 进行编码
    request.add(utf8.encode('{"a": "aaa"}'));
    // 也可以通过添加流的方式进行添加
    request.addStream(input);
    复制代码
  3. 连接服务器

    // 设置 request 后通过 request.close() 获取一个响应对象 HttpClientResponse,
    // 包括响应头,响应内容等
    HttpClientResponse response = await request.close();
    复制代码
  4. 读取服务器响应内容

    String responseBody = await response.transform(utf8.decoder).join();
    复制代码
  5. 关闭实例

    client.close();
    复制代码

例如我们要去请求 Bird.so 的首页并显示,我们可以这么实现

_httpClientRequest() async {HttpClient client;// try catch finally 用于捕获请求过程中发生的异常,在 finally 中设置保证 client 能够关闭try {client = HttpClient();HttpClientRequest request = await client.getUrl(Uri.parse(_BIRD_SO_URL));HttpClientResponse response = await request.close();String strResponse = await response.transform(utf8.decoder).join();setState(() => _netBack = strResponse);} catch (e) {print('${e.toString()}');setState(() => _netBack = 'Fail');} finally {client.close();}}
复制代码

最后实现的效果

很显然,用 HttpClient 请求相对来说是个非常麻烦的过程,如果要涉及到文本上传之类的,那么就会更麻烦了,所以这边引入一个网络请求的插件 dio,写本文的时候版本为 2.1.0

Dio

dio 是个非常强大的网络请求库,他的方式类似 OkHttp,我们可以直接查看官方文档,使用方式非常简单,创建一个 Dio 实例,然后就可以通过 getpost 等方式发起请求,返回 Future<Response>,而且支持多个并发请求,可以设置返回响应的类型,监听上传下载进度等等,看着就很给力。对于简单的方式,这边就不做太多介绍,主要讲下拦截器,也是非常给力的一部分。比如我们需要请求这么个接口 https://randomuser.me/api/

这个接口通过 get 请求,可以加入任意的查询参数。比如我们需要实现一个请求加解密的过程,如果每次都在上传参数或者返回请求的时候去加密,解密的话,就做了非常多无用功了,那么这时候拦截器就派上用场了。先定义下加解密的规则,上传的参数统一转为小写,不存在大写,请求回的数据,不能含有 info 字段。看下如何实现

_dioRequest() async {BaseOptions options = BaseOptions(connectTimeout: 5000, receiveTimeout: 60000);Dio dio = Dio(options);dio.interceptors.add(InterceptorsWrapper(onRequest: (opt) {// 获取查询的参数Map params = opt.queryParameters;// 将所有的参数转为小写,因为查询参数通过 map 形式上传params.forEach((key, value) => opt.queryParameters[key] = '$value'.toLowerCase());// 这边还可以做些别的操作,例如需要 token 进行用户身份验证,则通过头部进行添加// opt.headers['authorization'] = 'token';// 在官网中,提供了 lock 和 unlock 的写法,被 lock 后,接下来的请求会进入队列等待,// 直到 unlock 后才能继续,可以用于几个请求,后续的需要用到前面的返回值的情况使用// 返回修改后的 RequestOptionsreturn opt;}, onResponse: (resp) {// 返回响应体后,将 info 字段的内容切除,并将 json 拼接完成resp.data = '${'${resp.data}'.split(', info').first}}';return resp;}, onError: (error) {// 发生错误时的回调return error;}));// 发送一个请求,可以查看下打印的结果Response response = await dio.get(_USER_ME_URL, queryParameters: {'a': 'AAA', 'b': 'BbBbBb'});print(response.data);print(response.request.headers);print(response.request.queryParameters);setState(() => _netBack = response.data.toString()); // 界面显示 response.data}
复制代码

看下最后的显示信息

请求体的头部成功加上了 authorization 参数,请求的参数全部变为小写,返回的信息也把 info 字段值去除。在很多时候,请求接口后,需要将 json 转换成 pojo 类来处理,可以通过 json_serializable 这个三方插件实现,这边提供文章 Flutter Json自动反序列化,当然这种方式比较麻烦,这里推荐个 Android Studio 下的插件 dart_json_format 直接搜索就可以,如果用的是 Vitual Code 或者别的不是 JetBrains 系列的,这里有个转换的网址 JsonToDart。

以上代码查看 http_main.dart 文件

实践一下下

不知道小伙还记得前面讲的 BLoC 没有,忘了可以查看 Flutter 状态管理及 BLoC,这里结合 BLoCDio 实现界面和逻辑分离的小例子,接口使用前面提到的 https://randomuser.me/api/ 接口。网络应该是比较常用的,所以对其进行一些封装还是很有必要的,这边提供下我自己封装的方法

import 'package:dio/dio.dart';// 用于错误信息回调
typedef ErrorCallback = void Function(String msg);class HttpUtils {static const GET = 'get';static const POST = 'post';static Dio _dio;static HttpUtils _instance;Dio get hp => _dio;// dio 可以在 BaseOptions 中指定域名 baseUrl,// 后续接口就不需要再添加域名了// 如果请求的接口域名发生了变化,只要把全部 url 写全,就会自动使用新的域名HttpUtils._internal(String base) {// 生成一个单例,防止多次打开关闭造成开销_dio = Dio(BaseOptions(baseUrl: base, connectTimeout: 10000, receiveTimeout: 10000));}factory HttpUtils(String base) {if (_instance == null) _instance = HttpUtils._internal(base);return _instance;}// 添加拦截器addInterceptor(List<InterceptorsWrapper> interceptors) {_dio.interceptors.clear();_dio.interceptors.addAll(interceptors);}Future<Response<T>> getRequest<T>(url, {Map params, ErrorCallback callback}) =>_request(url, GET, params: params, callback: callback);Future<Response<T>> postRequest<T>(url, {Map params, ErrorCallback callback}) =>_request(url, POST, params: params, callback: callback);Future<Response> download(url, path, {ProgressCallback receive, CancelToken token}) =>_dio.download(url, path, onReceiveProgress: receive, cancelToken: token);// T 可以指定返回的类型,String 或者 Map<String, dynamic>Future<Response<T>> _request<T>(url,String method, {Map params, // 上传的参数Options opt,ErrorCallback callback, // 错误回调ProgressCallback send, // 上传进度监听ProgressCallback receive, // 下载监听CancelToken token, // 用于取消的 token,可以多个请求绑定一个 token}) async {try {Response<T> rep;if (method == GET) {// 如果不是重新创建 Dio 实例,get 方法使用 queryParams 会出错,不懂原因,使用拼接没有问题if (params != null && params.isNotEmpty) {var sb = StringBuffer('?');params.forEach((key, value) {sb.write('$key=$value&');});// get 请求下拼接路径url += sb.toString().substring(0, sb.length - 1);}rep = await _dio.get(url, options: opt, onReceiveProgress: receive, cancelToken: token);} else if (method == POST) {// post 参数放请求体rep = params == null? await _dio.post(url, options: opt, cancelToken: token, onSendProgress: send, onReceiveProgress: receive): await _dio.post(url,data: params, options: opt, cancelToken: token, onSendProgress: send, onReceiveProgress: receive);}// 如果 statusCode 不是 200 则错误回调,返回空的 Responseif (rep.statusCode != 200 && callback != null) {callback('network error, and code is ${rep.statusCode}');return null;}return rep;} catch (e) {if (callback != null) {callback('network error, catch error: ${e.toString()}');}return null;}}
}
复制代码

封装后就可以愉快的调用了,如果有别的请求方式后期可以继续扩展。继续看代码,创建一个 application.dart 文件,用于存放全局参数

class Application {static HttpUtils http;
}
复制代码

并在 main() 方法中进行初始化,接下来就可以直接使用

void main() {Application.http = HttpUtils('https://randomuser.me');runApp(DemoApp());// 透明状态栏if (Platform.isAndroid) {SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));}
}
复制代码

看下最后的实现效果吧,刚进入没有数据则通过转圈圈提示,加载完数据后,点击头像更换下个

实现 BLoC 需要有一个管理类

class UserBloc extends BaseBloc {RandomUserModel _user;RandomUserModel get user => _user;BehaviorSubject<RandomUserModel> _controller = BehaviorSubject();Observable<RandomUserModel> get stream => Observable(_controller.stream);// 网络请求获取新的数据,并更新updateUserInfo() {Application.http.getRequest('/api').then((response) {// RandomUserModel 就是接口返回的 json 转成的 model 类RandomUserModel model = RandomUserModel.fromMap(response.data);_user = model;// add 到 controller 通知修改_controller.add(model);});}@overridevoid dispose() {_controller?.close(); // 及时销毁}
}
复制代码

设置好管理类后,就可以来编写界面了,界面也比较简单

class UserPageDemo extends StatelessWidget {// 将首字母大写String _upperFirst(String content) {assert(content != null && content.isNotEmpty);return '${content.substring(0, 1).toUpperCase()}${content.substring(1)}';}// 地址信息通用部件Widget _userLocation(String info) => Padding(padding: const EdgeInsets.only(top: 4.0),child: Text(info, style: TextStyle(color: Colors.white, fontSize: 16.0)));@overrideWidget build(BuildContext context) {UserBloc _bloc = BlocProvider.of<UserBloc>(context);_bloc.updateUserInfo();return Scaffold(// StreamBuilder 接受更新数据的 streambody: StreamBuilder(builder: (_, AsyncSnapshot<RandomUserModel> snapshot) => Container(alignment: Alignment.center,decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topCenter,end: Alignment.bottomCenter,colors: [Colors.blue[600], Colors.blue[400]])),child: !snapshot.hasData? CupertinoActivityIndicator(radius: 12.0): Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[InkWell( // 用于切换数据child: ClipOval( // 圆形头像child: FadeInImage.assetNetwork(placeholder: 'images/ava_default.png', image: snapshot.data.results[0].picture.large),),onTap: () => _bloc.updateUserInfo()), // 更新数据Padding(padding: const EdgeInsets.only(top: 20.0),child: Text('${_upperFirst(snapshot.data.results[0].name.first)} ${_upperFirst(snapshot.data.results[0].name.last)}',style: TextStyle(color: Colors.white, fontSize: 24.0)),),Text('${snapshot.data.results[0].email}',style: TextStyle(color: Colors.white, fontSize: 18.0)),_userLocation('${snapshot.data.results[0].location.street}'),_userLocation('${_upperFirst(snapshot.data.results[0].location.city)}'),_userLocation('${_upperFirst(snapshot.data.results[0].location.state)}'),]),),initialData: _bloc.user, // 注入初始值stream: _bloc.stream), // 注入更新 stream);}
}
复制代码

以上代码查看 bloc_network 包下的所有文件

当然了,福利是不可少的,但是需要你到项目中自己去找。差不多入门的部分就讲到这了,接下来考虑加个实战,总之先等等吧,我找个好的题材接口来写。

最后代码的地址还是要的:

  1. 文章中涉及的代码:demos

  2. 基于郭神 cool weather 接口的一个项目,实现 BLoC 模式,实现状态管理:flutter_weather

  3. 一个课程(当时买了想看下代码规范的,代码更新会比较慢,虽然是跟着课上的一些写代码,但是还是做了自己的修改,很多地方看着不舒服,然后就改成自己的实现方式了):flutter_shop

如果对你有帮助的话,记得给个 Star,先谢过,你的认可就是支持我继续写下去的动力~

转载于:https://juejin.im/post/5cb3d9e4f265da038e549c7d

Flutter 入门指北(Part 13)之网络相关推荐

  1. Flutter 入门指北(Part 9)之弹窗和提示(SnackBar、BottomSheet、Dialog)

    该文已授权公众号 「码个蛋」,转载请指明出处 前面的小节把常用的一些部件都介绍了,这节介绍下 Flutter 中的一些操作提示.Flutter 中的操作提示主要有这么几种 SnackBar.Botto ...

  2. Flutter 入门指北之基础部件

    作者:Kuky_xs 原文:https://www.jianshu.com/p/8ddb16902ce6 前言 主要包括 MaterialApp.Scaffold.Text.Image.Icon.Bu ...

  3. Flutter 入门指北(Part 2)之基础部件

    该文已授权公众号 「码个蛋」,转载请指明出处 上一节介绍了 Dart 的一些语法,以及配置环境的网址,这节我们就可以开始了解下 Flutter 了 主要包括 MaterialApp.Scaffold. ...

  4. 计算机学习入门指北——计科软工网络信安侧重图析、解读专业术语、岗位分类、未来规划

    申明:本博文偏技术向,主观性较强,其中部分理解必有偏差和误解,望指出改正! 计算机学习入门指北: 作为刚入学的计算机系学生,面对一片专业术语十分蒙.区块链?大数据?开源?数据库?嵌入式开发?前端后端? ...

  5. 【Linux入门指北】第一篇 初识Linux

    目录 前言 一.Linux操作系统的发展历史 1.Linux操作系统的诞生 2.Linux操作系统的发展 1.自由软件基金会(FSF) 2.GPL协议 3.GUN工程 二.Linux的不同发行版本 1 ...

  6. TensorRT详细入门指北,如果你还不了解TensorRT,过来看看吧

    首发于TensorRT详细入门指北,如果你还不了解TensorRT,过来看看吧!,最新回复以及交流请看这里~ 推荐一个深蓝学院的CUDA课程,TensorRT_Tutorial的作者伟哥讲解的,质量很 ...

  7. Blockly开发入门指北

    Blockly开发入门指北 [腾讯文档]Blockly开发入门指北 https://docs.qq.com/doc/DRWRDUU5kR2lhaGNN 写这篇文章的目的 最近公司的项目用到了Block ...

  8. Python 简单入门指北(二)

    Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...

  9. 【杭电数电实验】verilog入门指北

    verilog入门指北 前言 指北内容 面向人群 基础实验 1-15 代码参考 正文 ISE 的安装 实验的基本操作流程 可能出现的问题 创建工程闪退 希望删除某一文件,实际上并没有删除 如何编写测试 ...

最新文章

  1. 【控制】《多智能体系统一致性与复杂网络同步控制》郭凌老师-第3章-具有扩散作用的多智能体系统领导-跟随一致性
  2. Joint Consensus两阶段成员变更的单步实现
  3. linux查看nec进程状态,【linux】 /proc/PID/stat
  4. jvm 堆外内存_jvm┃java内存区域,跳槽大厂必会知识点
  5. Ubuntu18.04安装微信(方式二)
  6. Apache下设置自动将http跳转到https方法
  7. (十三)洞悉linux下的Netfilteriptables:为防火墙增添功能模块【实战】
  8. Android商品详情页上拉查看详情
  9. 51单片机红外线发射c语言,51单片机红外发射模块与红外接收模块的代码程序设计...
  10. 第一篇博客--有志者,事竟成
  11. 屌炸天,像写代码一样写PPT,reveal-md 详解
  12. web 计算器_计算器中的奢侈品——CASIO S200
  13. 复古传奇服务器维护时间,复古传奇手游刷怪时间
  14. RDS电台 TA 与 AF解释
  15. [经典面试题][淘宝]求首尾相连数组的最大子数组和
  16. storyboard搭建项目_Storyboard使用教程一
  17. JavaScript进阶(4)-dom查询
  18. mysql语句统计总数_一条sql语句实现统计查询_MySQL
  19. matlab自带滤波器函数小结(图像处理)
  20. python爬虫代码房-Python爬虫实战(3):安居客房产经纪人信息采集

热门文章

  1. 全球及中国盘式削片机行业运营模式与“十四五”投资规划建议报告2022-2027年版
  2. php 某一天时间凌晨,PHP获得今天 天凌晨时间戳的例子
  3. 在有赞工作两年半的感受
  4. Error:依赖版本不一致
  5. 实用工具---制作试卷
  6. Linux 虚拟机安装后的配置和一些命令符笔记
  7. PTA 09-排序3 Insertion or Heap Sort (25分)
  8. Linux权限管理 - 特殊权限之文件特殊权限
  9. 教程:给初学的几个小例子(待补充)
  10. poj3580 伸展树(区间翻转 区间搬移 删除结点 加入结点 成段更新)