Flutter - 访问 REST API
Flutter - 访问 REST API
Section titled “Flutter - 访问 REST API”现代移动应用程序经常通过 REST API 与后端服务交互以获取或发送数据。Flutter 提供了强大的工具来进行 HTTP 请求。
http 包
Section titled “http 包”用于基本 HTTP 请求的核心包是 package:http。它是一个基于 Future 的库,利用 Dart 的 async 和 await 来处理异步网络操作。
关键特性和方法:
- 顶层函数: 提供用于常见请求的简单函数:
-
http.get(url, headers: ...):发送 GET 请求。
-
http.post(url, headers: ..., body: ..., encoding: ...):发送 POST 请求,通常带有请求体(例如 JSON)。
-
http.put(url, headers: ..., body: ...):发送 PUT 请求。
-
http.patch(url, headers: ..., body: ...):发送 PATCH 请求。
-
http.delete(url, headers: ...):发送 DELETE 请求。
-
http.read(url, headers: ...):简单的 GET 请求,将响应体作为String返回。
Response对象: 大多数方法返回Future<Response>。Response对象包含:-
statusCode:HTTP 状态码(例如,200 表示成功,404 表示未找到)。
-
body:作为String的响应体。
-
headers:响应头部的 Map。
-
contentLength:响应体的长度。
- JSON 处理: 你通常需要
dart:convert来将 Dart 对象(例如 Map)编码为用于请求体的 JSON 字符串,并将响应体中的 JSON 字符串解码为 Dart 对象。 - 错误处理: 网络错误或非 2xx 状态码应使用
try-catch块并检查response.statusCode来处理。
import 'dart:convert'; // 用于 jsonDecode, jsonEncodeimport 'package:http/http.dart' as http;
Future<void> fetchData() async { final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); // 使用 Uri.parse try { final response = await http.get(url);
if (response.statusCode == 200) { // 成功! final data = jsonDecode(response.body) as Map<String, dynamic>; print('Title: ${data['title']}'); } else { // 服务器返回了错误的状态码 print('Request failed with status: ${response.statusCode}.'); } } catch (e) { // 处理网络错误或其他异常 print('An error occurred: $e'); }}替代方案:dio 包
Section titled “替代方案:dio 包”虽然 http 对于基本需求来说很好,但 dio 包是一个流行的替代方案,提供了更高级的功能,如拦截器(用于日志记录、身份验证)、全局配置、FormData 支持、请求取消以及更简化的错误处理。对于更复杂的 API 交互,请考虑使用 dio。
示例:从 REST API 获取产品
Section titled “示例:从 REST API 获取产品”让我们修改产品应用,使其从简单的 JSON REST 端点获取数据。
- 创建项目:
flutter create product_rest_app。 - 添加
http依赖: 在pubspec.yaml中添加http: ^1.1.0(或最新版本),然后运行flutter pub get。 - 添加资源:(可选,如果使用本地图片)创建
assets/images/目录,添加图片,并在pubspec.yaml中声明。 - 准备 JSON 数据: 创建一个
products.json文件,包含你的产品数据:
[ { "id": 1, "name": "iPhone", "description": "The stylish phone ever", "price": 1000, "image": "iphone.png" }, { "id": 2, "name": "Pixel", "description": "The most feature phone ever", "price": 800, "image": "pixel.png" }, { "id": 3, "name": "Laptop", "description": "Most productive development tool", "price": 2000, "image": "laptop.png" } // ... more products]- 搭建简单的 Web 服务器: 你需要一种方法通过 HTTP 提供此
products.json文件。 -
- Node.js
http-server:
- Node.js
-
* 如果尚未安装 Node.js,请先安装。
-
* 安装服务器: `npm install -g http-server`
-
* 在终端中导航到包含 `products.json` 的目录。
-
* 运行: `http-server . -p 8000 --cors`(`--cors` 标志对于 Web 测试很重要)。记下提供的 IP 地址(例如,`http://192.168.1.10:8000`)。
-
- Python:
-
* 导航到包含 `products.json` 的目录。
-
* 运行: `python -m http.server 8000`(Python 3)。使用 `127.0.0.1:8000` 或你机器的本地网络 IP。
-
- 其他: 使用任何简单的 Web 服务器(Apache、Nginx、VS Code Live Server 扩展)。确保模拟器/设备可以访问它。
- 定义
Product模型 (lib/product.dart): 包含一个用于解析 JSON 的工厂构造函数。
class Product { final int id; final String name; final String description; final int price; final String image;
const Product({ required this.id, required this.name, required this.description, required this.price, required this.image, });
// 用于从 JSON map 创建 Product 的工厂构造函数 factory Product.fromJson(Map<String, dynamic> json) { return Product( id: json['id'] as int, name: json['name'] as String, description: json['description'] as String, price: json['price'] as int, image: json['image'] as String, ); }}- 创建 API 服务 (
lib/api_service.dart): 封装获取逻辑。
import 'dart:convert';import 'package:http/http.dart' as http;import 'product.dart'; // 假设 product.dart 在 lib/ 中
class ApiService { // 替换为你的实际服务器 URL // 对于访问 localhost 的 Android 模拟器:使用 10.0.2.2 // 对于物理设备/iOS 模拟器:使用你机器的本地网络 IP static const String _baseUrl = 'http://10.0.2.2:8000'; // Android 模拟器示例 // static const String _baseUrl = 'http://192.168.1.10:8000'; // 本地网络 IP 示例
Future<List<Product>> fetchProducts() async { final url = Uri.parse('$_baseUrl/products.json'); try { final response = await http.get(url);
if (response.statusCode == 200) { // 如果服务器返回 OK 响应,解析 JSON。 List<dynamic> body = jsonDecode(response.body); List<Product> products = body .map((dynamic item) => Product.fromJson(item as Map<String, dynamic>)) .toList(); return products; } else { // 如果服务器没有返回 200 OK 响应, // 则抛出异常。 throw Exception('Failed to load products (Status code: ${response.statusCode})'); } } catch (e) { // 处理网络错误或解析错误 print('Error fetching products: $e'); throw Exception('Failed to load products: $e'); } }
// 根据需要添加 POST、PUT、DELETE 等方法}- 更新 UI (
lib/main.dart): 使用FutureBuilder处理异步数据获取。
import 'package:flutter/material.dart';import 'api_service.dart';import 'product.dart';// 如果需要,导入之前示例中的 ProductBox, ProductPage, RatingBox 部件
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Product REST App', theme: ThemeData(primarySwatch: Colors.blue), home: const ProductListPage(), ); }}
class ProductListPage extends StatefulWidget { const ProductListPage({Key? key}) : super(key: key);
@override _ProductListPageState createState() => _ProductListPageState();}
class _ProductListPageState extends State<ProductListPage> { late Future<List<Product>> _productsFuture; final ApiService _apiService = ApiService();
@override void initState() { super.initState(); _productsFuture = _apiService.fetchProducts(); }
void _refreshProducts() { setState(() { _productsFuture = _apiService.fetchProducts(); }); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Products from API'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _refreshProducts, // 添加刷新按钮 tooltip: 'Refresh', ) ], ), body: Center( child: FutureBuilder<List<Product>>( future: _productsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // 数据加载中 return const CircularProgressIndicator(); } else if (snapshot.hasError) { // 如果发生错误 return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Error: ${snapshot.error}'), const SizedBox(height: 10), ElevatedButton( onPressed: _refreshProducts, child: const Text('Retry'), ) ], ); } else if (snapshot.hasData) { // 如果数据成功加载 final products = snapshot.data!; if (products.isEmpty) { return const Text('No products found.'); } return ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; // 替换为你的实际 ProductBox 或 ListTile 部件 return ListTile( // leading: Image.network('$_baseUrl/images/${product.image}'), // 如果提供图片服务的示例 leading: Image.asset('assets/images/${product.image}', width: 50, errorBuilder: (c, e, s) => Icon(Icons.image, size: 50)), // 使用本地资源的示例 title: Text(product.name), subtitle: Text(product.description), trailing: Text('$${product.price}'), onTap: () { // 如果需要,导航到详情页 // Navigator.push(context, MaterialPageRoute(builder: (context) => ProductPage(item: product))); }, ); }, ); } else { // 在 FutureBuilder 的典型用法中,未来对象非空时,这种情况不应发生 return const Text('No data available.'); } }, ), ), ); }}运行应用。确保你的简单 Web 服务器正在运行且可访问。应用将显示加载指示器,然后显示从 products.json 获取的产品列表,如果请求失败则显示错误消息。