Skip to content

Flutter - 访问 REST API

现代移动应用程序经常通过 REST API 与后端服务交互以获取或发送数据。Flutter 提供了强大的工具来进行 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, jsonEncode
import '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');
}
}

虽然 http 对于基本需求来说很好,但 dio 包是一个流行的替代方案,提供了更高级的功能,如拦截器(用于日志记录、身份验证)、全局配置、FormData 支持、请求取消以及更简化的错误处理。对于更复杂的 API 交互,请考虑使用 dio。

让我们修改产品应用,使其从简单的 JSON REST 端点获取数据。

  1. 创建项目: flutter create product_rest_app。
  2. 添加 http 依赖: 在 pubspec.yaml 中添加 http: ^1.1.0(或最新版本),然后运行 flutter pub get。
  3. 添加资源:(可选,如果使用本地图片)创建 assets/images/ 目录,添加图片,并在 pubspec.yaml 中声明。
  4. 准备 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
]
  1. 搭建简单的 Web 服务器: 你需要一种方法通过 HTTP 提供此 products.json 文件。
    • Node.js http-server:
  2. * 如果尚未安装 Node.js,请先安装。
  3. * 安装服务器: `npm install -g http-server`
  4. * 在终端中导航到包含 `products.json` 的目录。
  5. * 运行: `http-server . -p 8000 --cors`(`--cors` 标志对于 Web 测试很重要)。记下提供的 IP 地址(例如,`http://192.168.1.10:8000`)。
    • Python:
  6. * 导航到包含 `products.json` 的目录。
  7. * 运行: `python -m http.server 8000`(Python 3)。使用 `127.0.0.1:8000` 或你机器的本地网络 IP。
    • 其他: 使用任何简单的 Web 服务器(Apache、Nginx、VS Code Live Server 扩展)。确保模拟器/设备可以访问它。
  8. 定义 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,
);
}
}
  1. 创建 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 等方法
}
  1. 更新 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 获取的产品列表,如果请求失败则显示错误消息。