📜  Flutter-访问REST API

📅  最后修改于: 2020-12-08 04:47:12             🧑  作者: Mango


Flutter提供了HTTP包来消耗HTTP资源。 http是一个基于Future的库,并使用await和async功能。它提供了许多高级方法,并简化了基于REST的移动应用程序的开发。

基本概念

http包提供了一个高级类和http来执行Web请求。

  • http类提供了执行所有类型的HTTP请求的功能。

  • http方法接受url和通过Dart Map的其他信息(发布数据,其他标头等)。它请求服务器并以异步/等待模式收集响应。例如,下面的代码从指定的url中读取数据,并在控制台中将其打印出来。

print(await http.read('https://flutter.dev/'));

一些核心方法如下-

  • 读取-通过GET方法请求指定的url并以Future 的形式返回响应

  • -通过GET方法请求指定的URL,并返回作为未来<响应>的响应。响应是保存响应信息的类。

  • -通过张贴所提供的数据请求通过POST方法指定的URL,并返回响应作为未来<响应>

  • -通过PUT方法请求指定的URL,并返回响应作为未来<响应>

  • -请求通过HEAD方法指定的URL,并返回响应作为未来<响应>

  • 删除-通过DELETE方法请求指定的URL,并返回响应作为未来<响应>

http还提供了更标准的HTTP客户端类client。客户端支持持久连接。当对特定服务器发出大量请求时,它将很有用。需要使用close方法正确关闭它。否则,它类似于http类。示例代码如下-

var client = new http.Client(); 
try { 
   print(await client.get('https://flutter.dev/')); 
} 
finally { 
   client.close(); 
}

访问产品服务API

让我们创建一个简单的应用程序,以从Web服务器获取产品数据,然后使用ListView展示产品。

  • 在Android studio product_rest_app中创建一个新的Flutter应用程序。

  • 将默认的启动代码(main.dart)替换为我们的product_nav_app代码。

  • 将资产文件夹从product_nav_app复制到product_rest_app,然后在pubspec.yaml文件中添加资产。

flutter: 
   assets: 
      - assets/appimages/floppy.png 
      - assets/appimages/iphone.png 
      - assets/appimages/laptop.png 
      - assets/appimages/pendrive.png 
      - assets/appimages/pixel.png 
      - assets/appimages/tablet.png
  • 在pubspec.yaml文件中配置http软件包,如下所示-

dependencies: 
   http: ^0.12.0+2
  • 在这里,我们将使用最新版本的http包。 Android Studio将发送有关pubspec.yaml已更新的程序包警报。

最新版本

  • 单击获取依赖项选项。 Android studio将从Internet获得该程序包,并为应用程序正确配置它。

  • 在main.dart文件中导入http包-

import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http;
  • 创建具有产品信息的新JSON文件products.json,如下所示:

[ 
   { 
      "name": "iPhone", 
      "description": "iPhone is the stylist phone ever", 
      "price": 1000, 
      "image": "iphone.png" 
   }, 
   { 
      "name": "Pixel", 
      "description": "Pixel is the most feature phone ever", 
      "price": 800, 
      "image": "pixel.png"
   }, 
   { 
      "name": "Laptop", 
      "description": "Laptop is most productive development tool", 
      "price": 2000, 
      "image": "laptop.png" 
   }, 
   { 
      "name": "Tablet", 
      "description": "Tablet is the most useful device ever for meeting", 
      "price": 1500, 
      "image": "tablet.png" 
   }, 
   { 
      "name": "Pendrive", 
      "description": "Pendrive is useful storage medium", 
      "price": 100, 
      "image": "pendrive.png" 
   }, 
   { 
      "name": "Floppy Drive", 
      "description": "Floppy drive is useful rescue storage medium", 
      "price": 20, 
      "image": "floppy.png" 
   } 
]
  • 创建一个新文件夹JSONWebServer并放置JSON文件products.json。

  • 使用JSONWebServer作为其根目录运行任何Web服务器并获取其Web路径。例如,http://192.168.184.1:8000/products.json。我们可以使用任何Web服务器,例如apache,nginx等,

  • 最简单的方法是安装基于节点的http服务器应用程序。请按照以下步骤安装和运行http-服务器应用程序

    • 安装Nodejs应用程序( nodejs.org )

    • 转到JSONWebServer文件夹。

cd /path/to/JSONWebServer
  • 使用npm安装http-server软件包。

npm install -g http-server
  • 现在,运行服务器。

http-server . -p 8000 

Starting up http-server, serving . 
Available on: 
   http://192.168.99.1:8000
   http://127.0.0.1:8000 
   Hit CTRL-C to stop the server
  • 在lib文件夹中创建一个新文件Product.dart,然后将Product类移入其中。

  • 在Product类Product.fromMap中编写工厂构造函数,以将映射的数据Map转换为Product对象。通常,JSON文件将转换为Dart Map对象,然后转换为相关对象(产品)。

factory Product.fromJson(Map data) {
   return Product(
      data['name'],
      data['description'], 
      data['price'],
      data['image'],
   );
}
  • Product.dart的完整代码如下-

class Product {
   final String name; 
   final String description;
   final int price;
   final String image; 
   
   Product(this.name, this.description, this.price, this.image); 
   factory Product.fromMap(Map json) { 
      return Product( 
         json['name'], 
         json['description'], 
         json['price'], 
         json['image'], 
      );
   }
}
  • 在主类中编写两种方法-parseProducts和fetchProducts-从Web服务器获取产品信息并将其加载到List 对象中。

List parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast>(); 
   return parsed.map((json) =>Product.fromJson(json)).toList(); 
} 
Future> fetchProducts() async { 
   final response = await http.get('http://192.168.1.2:8000/products.json'); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception('Unable to fetch products from the REST API');
   } 
}
  • 请注意以下几点-

    • Future用于延迟加载产品信息。延迟加载是一种将代码的执行推迟到必要时的概念。

    • http.get用于从Internet上获取数据。

    • json.decode用于将JSON数据解码为Dart Map对象。 JSON数据解码后,将使用Product类的fromMap将其转换为List

    • 在MyApp类中,添加新的成员变量,即Future 类型的产品,并将其包括在构造函数中。

class MyApp extends StatelessWidget { 
   final Future> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   ...
  • 在MyHomePage类中,添加类型为Future 的新成员变量产品,并将其包括在构造函数中。另外,删除items变量及其相关方法,getProducts方法调用。将产品变量放置在构造函数中。首次启动应用程序时,它将仅允许从Internet提取产品。

class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   ...
  • 在MyApp小部件的构建方法中更改home选项(MyHomePage)以适应上述更改-

home: MyHomePage(title: 'Product Navigation demo home page', products: products),
  • 更改主要函数以包括Future 参数-

void main() => runApp(MyApp(fetchProduct()));
  • 创建一个新的小部件ProductBoxList,以在主页中构建产品列表。

class ProductBoxList extends StatelessWidget { 
   final List items;
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length,
         itemBuilder: (context, index) {
            return GestureDetector(
               child: ProductBox(item: items[index]), 
               onTap: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) =gt; ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
}

请注意,我们使用导航应用程序中的相同概念来列出产品,只是通过传递List 类型的产品(对象)将其设计为单独的小部件。

  • 最后,修改MyHomePage小部件的build方法,以使用Future选项而不是常规方法调用来获取产品信息。

Widget build(BuildContext context) { 
   return Scaffold(
      appBar: AppBar(title: Text("Product Navigation")),
      body: Center(
         child: FutureBuilder>(
            future: products, builder: (context, snapshot) {
               if (snapshot.hasError) print(snapshot.error); 
               return snapshot.hasData ? ProductBoxList(items: snapshot.data)
               
               // return the ListView widget : 
               Center(child: CircularProgressIndicator()); 
            }, 
         ), 
      )
   ); 
}
  • 在这里请注意,我们使用FutureBuilder小部件来呈现该小部件。 FutureBuilder将尝试从其Future属性(类型Future >)中获取数据。如果future属性返回数据,它将使用ProductBoxList呈现窗口小部件,否则将引发错误。

  • main.dart的完整代码如下-

import 'package:flutter/material.dart'; 
import 'dart:async'; 
import 'dart:convert'; 
import 'package:http/http.dart' as http; 
import 'Product.dart'; 

void main() => runApp(MyApp(products: fetchProducts())); 

List parseProducts(String responseBody) { 
   final parsed = json.decode(responseBody).cast>(); 
   return parsed.map((json) => Product.fromMap(json)).toList(); 
} 
Future> fetchProducts() async { 
   final response = await http.get('http://192.168.1.2:8000/products.json'); 
   if (response.statusCode == 200) { 
      return parseProducts(response.body); 
   } else { 
      throw Exception('Unable to fetch products from the REST API'); 
   } 
}
class MyApp extends StatelessWidget {
   final Future> products; 
   MyApp({Key key, this.products}) : super(key: key); 
   
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo', 
         theme: ThemeData( 
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title: 'Product Navigation demo home page', products: products), 
      ); 
   }
}
class MyHomePage extends StatelessWidget { 
   final String title; 
   final Future> products; 
   MyHomePage({Key key, this.title, this.products}) : super(key: key); 
   
   // final items = Product.getProducts();
   @override 
   Widget build(BuildContext context) { 
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")), 
         body: Center(
            child: FutureBuilder>(
               future: products, builder: (context, snapshot) {
                  if (snapshot.hasError) print(snapshot.error); 
                  return snapshot.hasData ? ProductBoxList(items: snapshot.data) 
                  
                  // return the ListView widget : 
                  Center(child: CircularProgressIndicator()); 
               },
            ),
         )
      );
   }
}
class ProductBoxList extends StatelessWidget {
   final List items; 
   ProductBoxList({Key key, this.items}); 
   
   @override 
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length, 
         itemBuilder: (context, index) { 
            return GestureDetector( 
               child: ProductBox(item: items[index]), 
               onTap: () { 
                  Navigator.push(
                     context, MaterialPageRoute( 
                        builder: (context) => ProductPage(item: items[index]), 
                     ), 
                  ); 
               }, 
            ); 
         }, 
      ); 
   } 
} 
class ProductPage extends StatelessWidget { 
   ProductPage({Key key, this.item}) : super(key: key); 
   final Product item; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.item.name),), 
         body: Center( 
            child: Container(
               padding: EdgeInsets.all(0), 
               child: Column( 
                  mainAxisAlignment: MainAxisAlignment.start, 
                  crossAxisAlignment: CrossAxisAlignment.start, 
                  children: [
                     Image.asset("assets/appimages/" + this.item.image), 
                     Expanded( 
                        child: Container( 
                           padding: EdgeInsets.all(5), 
                           child: Column( 
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                              children: [ 
                                 Text(this.item.name, style: 
                                    TextStyle(fontWeight: FontWeight.bold)), 
                                 Text(this.item.description), 
                                 Text("Price: " + this.item.price.toString()), 
                                 RatingBox(), 
                              ], 
                           )
                        )
                     ) 
                  ]
               ), 
            ), 
         ), 
      ); 
   } 
}
class RatingBox extends StatefulWidget { 
   @override 
   _RatingBoxState createState() =>_RatingBoxState(); 
} 
class _RatingBoxState extends State { 
   int _rating = 0; 
   void _setRatingAsOne() {
      setState(() { 
         _rating = 1; 
      }); 
   }
   void _setRatingAsTwo() {
      setState(() {
         _rating = 2; 
      }); 
   }
   void _setRatingAsThree() { 
      setState(() {
         _rating = 3; 
      }); 
   }
   Widget build(BuildContext context) {
      double _size = 20; 
      print(_rating); 
      return Row(
         mainAxisAlignment: MainAxisAlignment.end, 
         crossAxisAlignment: CrossAxisAlignment.end, 
         mainAxisSize: MainAxisSize.max, 
         
         children: [
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton( 
                  icon: (
                     _rating >= 1 
                     ? Icon(Icons.star, ize: _size,) 
                     : Icon(Icons.star_border, size: _size,)
                  ), 
                  color: Colors.red[500], onPressed: _setRatingAsOne, iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (
                     _rating >= 2 
                     ? Icon(Icons.star, size: _size,) 
                     : Icon(Icons.star_border, size: _size, )
                  ), 
                  color: Colors.red[500], 
                  onPressed: _setRatingAsTwo, 
                  iconSize: _size, 
               ), 
            ), 
            Container(
               padding: EdgeInsets.all(0), 
               child: IconButton(
                  icon: (
                     _rating >= 3 ? 
                     Icon(Icons.star, size: _size,)
                     : Icon(Icons.star_border, size: _size,)
                  ), 
                  color: Colors.red[500], 
                  onPressed: _setRatingAsThree, 
                  iconSize: _size, 
               ), 
            ), 
         ], 
      ); 
   } 
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.item}) : super(key: key); 
   final Product item; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 140, 
         child: Card(
            child: Row( 
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: [
                  Image.asset("assets/appimages/" + this.item.image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: [ 
                              Text(this.item.name, style:TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.item.description), 
                              Text("Price: " + this.item.price.toString()), 
                              RatingBox(), 
                           ], 
                        )
                     )
                  )
               ]
            ), 
         )
      ); 
   } 
}

最后运行应用程序以查看结果。除了数据来自Internet而不是在编码应用程序时输入的本地静态数据外,它与我们的Navigation示例相同。