2.6 HTTP请求
HTTP通常用于前后端的数据交互协议。Flutter请求网络有以下几种方式:
❑HTTP
❑HttpClient
❑Dio
运行本节的示例,需要首先启动后端Node测试程序,进入flutter_node_server程序,执行npm start命令启动程序。使用Node程序需要在本机安装Node环境,Node可到网址https://nodejs.org/zh-cn/下载。后端Node测试程序请查看本书随书源码。
2.6.1 HTTP请求方式
在使用HTTP方式请求网络时,首先需要在pubspec.yaml里加入http库,然后在示例程序里导入HTTP包。如下所示:
import 'package:http/http.dart' as http;
请看下面的完整示例代码,示例中发起了一个HTTP的get请求,并将返回的结果信息打印到控制台里:
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'http请求示例', home: Scaffold( appBar: AppBar( title: Text('http请求示例'), ), body: Center( child: RaisedButton( onPressed: () { //请求后台url路径(IP + PORT + 请求接口) var url = 'http://127.0.0.1:3000/getHttpData'; //向后台发起get请求response为返回对象 http.get(url).then((response) { print("状态: ${response.statusCode}"); print("正文: ${response.body}"); }); }, child: Text('发起http请求'), ), ), ), ); } }
请求界面如图2-9所示。
图2-9 HTTP请求示例效果图
点击“发起http请求”按钮,程序开始请求指定的url,如果服务器正常返回数据,则状态码为200。控制台输出内容如下:
flutter: 状态: 200 flutter: 正文:{"code":"0","message":"success","data":[{"name":"张三"},{"name":"李四"},{"name":"王五"}]}
注意 服务器返回状态200,同时返回正文。正文为后台返回的Json数据。后端测试程序由Node编写,确保本地环境安装有Node即可。
2.6.2 HttpClient请求方式
在使用HttpClient方式请求网络时,需要导入io及convert包,如下所示:
import 'dart:convert'; import 'dart:io';
请看下面的完整示例代码,示例中使用HttpClient请求了一条天气数据,并将返回的结果信息打印到控制台里。具体请求步骤参见代码注释。
import 'package:flutter/material.dart'; import 'dart:convert'; import 'dart:io'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // 获取数据,此方法需要异步执行async/await void getHttpClientData() async { try { // 实例化一个HttpClient对象 HttpClient httpClient = HttpClient(); //发起请求 (IP + PORT + 请求接口) HttpClientRequest request = await httpClient.getUrl( Uri.parse("http://127.0.0.1:3000/getHttpClientData")); // 等待服务器返回数据 HttpClientResponse response = await request.close(); // 使用utf8.decoder从response里解析数据 var result = await response.transform(utf8.decoder).join(); // 输出响应头 print(result); // httpClient关闭 httpClient.close(); } catch (e) { print("请求失败:$e"); } finally { } } @override Widget build(BuildContext context) { return MaterialApp( title: 'HttpClient请求', home: Scaffold( appBar: AppBar( title: Text('HttpClient请求'), ), body: Center( child: RaisedButton( child: Text("发起HttpClient请求"), onPressed: getHttpClientData, ), ), ), ); } }
请求界面如图2-10所示。
图2-10 HttpClient请求示例效果图
点击“发起HttpClient请求”按钮,程序开始请求指定的url,如果服务器正常返回数据,则状态码为200。控制台输出内容如下:
flutter: {"code":"0","message":"success","data":[{"name":"张三","sex":"男","age": "20"},{"name":"李四","sex":"男","age":"30"},{"name":"王五","sex":"男","age":"28"}]}
注意 返回的数据是Json格式,所以后续还需要做Json处理。另外还需要使用utf8.decoder从response里解析数据。
2.6.3 Dio请求方式
Dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等。
接下来是一个获取商品列表数据的示例,使用Dio向后台发起Post请求,同时传入店铺Id参数,服务端接收参数并返回商品列表详细数据,前端接收并解析Json数据,然后将Json数据转换成数据模型,最后使用列表渲染数据。具体步骤如下所示:
步骤1打开pubspec.yaml文件,添加Dio库。
步骤2在工程lib目录创建如下目录及文件:
. ├—— main.dart //主程序 ├—— model //数据模型层 | └—— good_list_model.dart //商品列表模型 ├—— pages //视图层 | └—— good_list_page.dart //商品列表页面 └—— service //服务层 └—— http_service.dart //http请求服务
步骤3打开main.dart文件,编写应用入口程序,在Scaffold的body中添加商品列表页面组件GoodListPage,代码如下所示:
import 'package:flutter/material.dart'; import 'pages/good_list_page.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Dio请求', home: Scaffold( appBar: AppBar( title: Text('Dio请求'), ), body: GoodListPage(), ), ); } }
步骤4打开http_service.dart文件,添加request方法。方法传入url及请求参数,创建Dio对象,调用其post方法发起Post请求。请求返回对象为Response,根据其状态码判断是否返回成功,如果statusCode为200则表示数据返回成功,代码如下所示:
import 'dart:io'; import 'package:dio/dio.dart'; import 'dart:async'; // Dio请求方法封装 Future request(url, {formData}) async { try { Response response; Dio dio = Dio(); dio.options.contentType = ContentType.parse('application/x-www-form-urlencoded'); // 发起POST请求,传入url及表单参数 response = await dio.post(url, data: formData); // 成功返回 if (response.statusCode == 200) { return response; } else { throw Exception('后端接口异常,请检查测试代码和服务器运行情况...'); } } catch (e) { return print('error:::${e}'); } }
步骤5打开good_list_model.dart文件编写商品列表数据模型,数据模型字段是根据前后端协商定义的。数据模型类里主要完成了由Json转换成Model及由Model转换成Json两个功能,代码如下所示:
// 商品列表数据模型 class GoodListModel{ // 状态码 String code; // 状态信息 String message; // 商品列表数据 List<GoodModel> data; // 构造方法,初始化时传入空数组[]即可 GoodListModel(this.data); // 通过传入Json数据转换成数据模型 GoodListModel.fromJson(Map<String,dynamic> json){ code = json['code']; message = json['message']; if(json['data'] != null){ data = List<GoodModel>(); // 循环迭代Json数据并将其每一项数据转换成GoodModel json['data'].forEach((v){ data.add(GoodModel.fromJson(v)); }); } } // 将数据模型转换成Json Map<String,dynamic> toJson(){ final Map<String,dynamic> data = Map<String,dynamic>(); data['code'] = this.code; data['message'] = this.message; if(this.data != null){ data['data'] = this.data.map((v) => v.toJson()).toList(); } return data; } } // 商品信息模型 class GoodModel{ // 商品图片 String image; // 原价 int oriPrice; // 现有价格 int presentPrice; // 商品名称 String name; // 商品Id String goodsId; // 构造方法 GoodModel({this.image,this.oriPrice,this.presentPrice,this.name,this.goodsId}); // 通过传入Json数据转换成数据模型 GoodModel.fromJson(Map<String,dynamic> json){ image = json['image']; oriPrice = json['oriPrice']; presentPrice = json['presentPrice']; name = json['name']; goodsId = json['goodsId']; } // 将数据模型转换成Json Map<String,dynamic> toJson(){ final Map<String,dynamic> data = new Map<String,dynamic>(); data['image'] = this.image; data['oriPrice'] = this.oriPrice; data['presentPrice'] = this.presentPrice; data['name'] = this.name; data['goodsId'] = this.goodsId; return data; } }
注意 数据模型中的字段一定要和后端返回的字段一一对应,否则会导致数据转换失败。
步骤6编写商品列表界面。打开good_list_page.dart文件,添加GoodListPage组件,此组件需要继承StatefulWidget有状态组件。在initState初始化状态方法里添加请求商品数据方法getGoods,在getGoods方法里调用request方法,传入url及店铺Id参数。接着发起Post请求,后端返回Json数据,然后使用GoodListModel.fromJson方法将Json数据转换成数据模型,此时表示数据获取并转换成功。接下来一定要设置当前商品列表状态值以完成界面的刷新处理。最后在界面中添加List组件完成数据的渲染功能。处理细节请参见如下代码:
import 'package:flutter/material.dart'; import 'dart:convert'; import '../model/good_list_model.dart'; import '../service/http_service.dart'; // 商品列表页面 class GoodListPage extends StatefulWidget { _GoodListPageState createState() => _GoodListPageState(); } class _GoodListPageState extends State<GoodListPage> { // 初始化数据模型 GoodListModel goodsList = GoodListModel([]); // 滚动控制 var scrollController = ScrollController(); @override void initState() { super.initState(); // 获取商品数据 getGoods(); } // 获取商品数据 void getGoods() async { // 请求url var url = 'http://127.0.0.1:3000/getDioData'; // 请求参数:店铺Id var formData = {'shopId': '001'}; // 调用请求方法传入url及表单数据 await request(url, formData: formData).then((value) { // 返回数据进行Json解码 var data = json.decode(value.toString()); // 打印数据 print('商品列表数据Json格式:::' + data.toString()); // 设置状态刷新数据 setState(() { // 将返回的Json数据转换成Model goodsList = GoodListModel.fromJson(data); }); }); } // 商品列表项 Widget _ListWidget(List newList, int index) { return Container( padding: EdgeInsets.only(top: 5.0, bottom: 5.0), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1.0, color: Colors.black12), )), // 水平方向布局 child: Row( children: <Widget>[ // 返回商品图片 _goodsImage(newList, index), SizedBox( width: 10, ), // 右侧使用垂直布局 Column( children: <Widget>[ _goodsName(newList, index), _goodsPrice(newList, index), ], ), ], ), ); } // 商品图片 Widget _goodsImage(List newList, int index) { return Container( width: 150, height: 150, child: Image.network(newList[index].image,fit: BoxFit.fitWidth,), ); } // 商品名称 Widget _goodsName(List newList, int index) { return Container( padding: EdgeInsets.all(5.0), width: 200, child: Text( newList[index].name, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 18), ), ); } // 商品价格 Widget _goodsPrice(List newList, int index) { return Container( margin: EdgeInsets.only(top: 20.0), width: 200, child: Row( children: <Widget>[ Text( '价格:¥${newList[index].presentPrice}', style: TextStyle(color: Colors.red), ), Text( '¥${newList[index].oriPrice}', ), ], ), ); } @override Widget build(BuildContext context) { // 通过商品列表数组长度判断是否有数据 if(goodsList.data.length > 0){ return ListView.builder( // 滚动控制器 controller: scrollController, // 列表长度 itemCount: goodsList.data.length, // 列表项构造器 itemBuilder: (context, index) { // 列表项,传入列表数据及索引 return _ListWidget(goodsList.data, index); }, ); } // 商品列表没有数据时返回空容器 return Container(); } }
注意 请求数据方法getGoods需要放在initState里执行,getGoods需要使用异步处理async/await。返回的Json数据转换成数据模型后一定要调用setState方法使得界面进行刷新处理。在列表渲染之前需要判断商品列表长度是否大于0。
Dio获取商品数据界面如图2-11所示。
图2-11 Dio请求示例效果图
当启动Dio请求示例程序,后台返回的数据有商品名称、商品图片路径、商品原价、商品市场价等信息,如下所示:
flutter: 商品列表数据Json格式:::{code: 0, message: success, data: [{name: 苹果 屏幕尺寸: 13.3英寸 处理器: Intel Core i5-8259, image: http://127.0.0.1:3000/images/ goods/001/cover.jpg, presentPrice: 13999, goodsId: 001, oriPrice: 15999}, {name: 外星 人alienware 全新m15 R2九代酷睿i7六核GTX1660Ti独显144Hz游戏笔记本电脑戴尔DELL15M-R4725, image: http://127.0.0.1:3000/images/goods/002/cover.jpg, presentPrice: 19999, goodsId: 002, oriPrice: 23999}, {name: Dell/戴尔 灵越15(3568) Ins15E-3525独显i5游戏本 超薄笔记本电脑, image: http://127.0.0.1:3000/images/goods/003/cover.jpg, presentPrice: 6600, goodsId: 003, oriPrice: 8999}, {name: 联想ThinkPad E480 14英寸超薄轻薄便携官方旗 舰店官网正品IBM全新办公用 商务大学生手提笔记本电脑E470新款, image: http://127.0.0.1:3000/ images/goods/004/cover.jpg, presentPrice: 5699, goodsId: 004, oriPrice: 7800}, {name: 苹果 屏幕尺寸:13.3英寸 处理器:Intel Core i<…>
注意 运行此示例,在查看前端控制台信息的同时还需要查看Node端控制台输出的信息,后台会打印启铺的Id。