- Flutter 教程
- Flutter - 首页
- Flutter - 简介
- Flutter - 安装
- 在 Android Studio 中创建简单的应用程序
- Flutter - 架构应用程序
- Dart 编程语言简介
- Flutter - Widget 简介
- Flutter - 布局简介
- Flutter - 手势简介
- Flutter - 状态管理
- Flutter - 动画
- Flutter - 编写 Android 特定代码
- Flutter - 编写 iOS 特定代码
- Flutter - 包简介
- Flutter - 访问 REST API
- Flutter - 数据库概念
- Flutter - 国际化
- Flutter - 测试
- Flutter - 部署
- Flutter - 开发工具
- Flutter - 编写高级应用程序
- Flutter - 结论
- Flutter 有用资源
- Flutter - 快速指南
- Flutter - 有用资源
- Flutter - 讨论
Flutter - 访问 REST API
Flutter 提供了 http 包来使用 HTTP 资源。http 是一个基于 Future 的库,并使用了 await 和 async 特性。它提供了许多高级方法,简化了基于 REST 的移动应用程序的开发。
基本概念
http 包提供了一个高级类 http 来执行 Web 请求。
http 类提供执行所有类型 HTTP 请求的功能。
http 方法接受一个 url,并通过 Dart Map 传递其他信息(发布数据、附加头等)。它向服务器发送请求,并在异步/等待模式下收集响应。例如,以下代码从指定的 url 读取数据并在控制台中打印它。
print(await http.read('https://flutterdart.cn/'));
一些核心方法如下:
read - 通过 GET 方法请求指定的 url,并将响应作为 Future<String> 返回
get - 通过 GET 方法请求指定的 url,并将响应作为 Future<Response> 返回。Response 是一个包含响应信息的类。
post - 通过 POST 方法请求指定的 url,发布提供的数据,并将响应作为 Future<Response> 返回
put - 通过 PUT 方法请求指定的 url,并将响应作为 Future<Response> 返回
head - 通过 HEAD 方法请求指定的 url,并将响应作为 Future<Response> 返回
delete - 通过 DELETE 方法请求指定的 url,并将响应作为 Future<Response> 返回
http 还提供了一个更标准的 HTTP 客户端类,client。client 支持持久连接。当需要向特定服务器发出大量请求时,它将非常有用。它需要使用 close 方法正确关闭。否则,它类似于 http 类。示例代码如下:
var client = new http.Client(); try { print(await client.get('https://flutterdart.cn/')); } finally { client.close(); }
访问产品服务 API
让我们创建一个简单的应用程序,从 Web 服务器获取产品数据,然后使用 ListView 显示这些产品。
在 Android Studio 中创建一个新的 Flutter 应用程序,product_rest_app。
将默认的启动代码 (main.dart) 替换为我们的 product_nav_app 代码。
将 product_nav_app 中的 assets 文件夹复制到 product_rest_app 中,并在 pubspec.yaml 文件中添加 assets。
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 放置其中。
运行任何 Web 服务器,并将 JSONWebServer 作为其根目录,并获取其 Web 路径。例如,http://192.168.184.1:8000/products.json。我们可以使用任何 Web 服务器,如 apache、nginx 等。
最简单的方法是安装基于 Node 的 http-server 应用程序。按照以下步骤安装和运行 http-server 应用程序
安装 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 对象,然后转换为相关的对象(Product)。
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 用于从 Internet 获取数据。
json.decode 用于将 JSON 数据解码为 Dart Map 对象。解码 JSON 数据后,它将使用 Product 类的 fromMap 转换为 List<Product>。
在 MyApp 类中,添加一个新的成员变量 products,类型为 Future<Product>,并在构造函数中包含它。
class MyApp extends StatelessWidget { final Future<List<Product>> products; MyApp({Key key, this.products}) : super(key: key); ...
在 MyHomePage 类中,添加一个新的成员变量 products,类型为 Future<Product>,并在构造函数中包含它。此外,删除 items 变量及其相关方法,getProducts 方法调用。将 products 变量放在构造函数中。这将允许仅在应用程序首次启动时从 Internet 获取产品。
class MyHomePage extends StatelessWidget { final String title; final Future<ListList<Product>> products; MyHomePage({Key key, this.title, this.products}) : super(key: key); ...
更改 MyApp 小部件的 build 方法中的 home 选项(MyHomePage)以适应上述更改:
home: MyHomePage(title: 'Product Navigation demo home page', products: products),
更改 main 函数以包含 Future<Product> 参数:
void main() => runApp(MyApp(fetchProduct()));
创建一个新的 widget,ProductBoxList,在主页上构建产品列表。
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> 的 products(对象)。
最后,修改 MyHomePage 小部件的 build 方法,使用 Future 选项而不是普通方法调用来获取产品信息。
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 小部件来渲染小部件。FutureBuilder 将尝试从其 future 属性(类型为 Future<List<Product>>)中获取数据。如果 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<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); // 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<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) // return the ListView widget : 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 示例相同,只是数据来自 Internet,而不是在编写应用程序时输入的本地静态数据。