Flutter技术入门与实战(第2版)
上QQ阅读APP看书,第一时间看更新

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。