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/'));
一些核心方法如下-
read - 通过GET方法请求指定的url,并以Future <String>的形式返回响应
get - 通过GET方法请求指定的url,并将响应作为Future <Response>返回。
post - 通过发布提供的数据,通过POST方法请求指定的url,并将响应返回为Future <Response>
put - 通过PUT方法请求指定的url,并以Future <Response>的形式返回响应
head - 通过HEAD方法请求指定的url,并将响应返回为Future <Response>
delete - 通过DELETE方法请求指定的url,并将响应作为Future <Response>返回
http还提供了更标准的HTTP客户端类client,客户端支持持久连接,当对特定服务器发出大量请求时,它将很有用,需要使用close方法正确关闭它。
var client = new http.Client(); try { print(await client.get('https://flutter.dev/')); } finally { client.close(); }
让无涯教程创建一个简单的应用程序,以从Web服务器获取产品数据,然后使用 ListView 展示产品。
在Android Studio product_rest_app 中创建一个新的 Flutter 应用。
用无涯教程的 product_nav_app 代码替换默认的启动代码(main.dart)。
将assets文件夹从 product_nav_app 复制到 product_rest_app 并将assets添加到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已更新的程序包警报。
单击"Get dependencies"选项。 Android studio将从网络上获取该软件包,并为应用程序正确配置它。
在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。
最简单的方法是安装基于节点的http-server应用程序。请按照以下步骤安装和运行http-server application应用程序
- 安装Node.js应用程序( 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<String, dynamic> 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<String, dynamic> json) { return Product( json['name'], json['description'], json['price'], json['image'], ); } }
在主类中编写两种方法-parseProducts和fetchProducts-从Web服务器获取产品信息并将其加载到List <Product>对象中。
List<Product> parseProducts(String responseBody) { final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); return parsed.map<Product>((json) =>Product.fromJson(json)).toList(); } Future<List<Product>> 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用于从网络上获取数据。
- json.decode用于将JSON数据解码为Dart Map对象,JSON数据解码后,将使用Product类的fromMap将其转换为List <Product>。
在MyApp类中,添加新的成员变量,即Future <Product>类型的产品,并将其包括在构造函数中。
class MyApp extends StatelessWidget { final Future<List<Product>> products; MyApp({Key key, this.products}) : super(key: key); ...
在MyHomePage类中,添加类型为Future <Product>的新成员变量产品,并将其包括在构造函数中。另外,删除items变量及其相关方法,getProducts方法调用,将product变量放在构造函数中,首次启动该应用程序时,它将仅允许从网络提取产品。
class MyHomePage extends StatelessWidget { final String title; final Future<ListList<Product>> products; MyHomePage({Key key, this.title, this.products}) : super(key: key); ...
在MyApp Widget的构建方法中更改homeoptions(MyHomePage)以适应上述更改-
home: MyHomePage(title: 'Product Navigation demo home page', products: products),
更改主要功能以包括Future <Product>参数-
void main() => runApp(MyApp(fetchProduct()));
创建一个新的 WidgetProductBoxList,以在主页中构建产品列表。
class ProductBoxList extends StatelessWidget { final List<Product> 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 <Product>类型的产品(对象)将其设计为单独的 Widget。
最后,修改 MyHomePage Widget的build方法,以使用Futureoptions而不是常规方法调用来获取产品信息。
Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Product Navigation")), body: Center( child: FutureBuilder<List<Product>>( 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 Widget来呈现该 Widget。FutureBuilder会尝试从其Future属性(类型Future <List <Product >>)中获取数据。如果future属性返回数据,它将使用ProductBoxList呈现窗口 Widget,否则将引发错误。
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<Product> parseProducts(String responseBody) { final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); return parsed.map<Product>((json) => Product.fromMap(json)).toList(); } Future<List<Product>> 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<List<Product>> products; MyApp({Key key, this.products}) : super(key: key); //这个小部件是您的应用程序的根。 @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<List<Product>> 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<List<Product>>( future: products, builder: (context, snapshot) { if (snapshot.hasError) print(snapshot.error); return snapshot.hasData ? ProductBoxList(items: snapshot.data) //返回 ListView 小部件: Center(child: CircularProgressIndicator()); }, ), ) ); } } class ProductBoxList extends StatelessWidget { final List<Product> 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: <Widget>[ Image.asset("assets/appimages/" + this.item.image), Expanded( child: Container( padding: EdgeInsets.all(5), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ 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<RatingBox> { 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: <Widget>[ 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: <Widget>[ Image.asset("assets/appimages/" + this.item.image), Expanded( child: Container( padding: EdgeInsets.all(5), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text(this.item.name, style:TextStyle(fontWeight: FontWeight.bold)), Text(this.item.description), Text("Price: " + this.item.price.toString()), RatingBox(), ], ) ) ) ] ), ) ); } }
最后运行应用程序以查看输出。它与无涯教程的 Navigation 示例相同,除了数据来自网络而不是在编写应用程序时输入的本地静态数据。
祝学习愉快!(内容编辑有误?请选中要编辑内容 -> 右键 -> 修改 -> 提交!)