flutter_accessing_rest_apis
Flutter - 访问 REST API
Section titled “Flutter - 访问 REST API”大多数现代移动应用需要与后端服务交互来获取或发送数据。使用 JSON (JavaScript Object Notation) 通过 HTTP/S 的 REST (Representational State Transfer) API 是这种通信的常用标准。
Flutter 提供了包(package)和内置的 Dart 特性,可以轻松地消费(consume)REST API。
http 包
Section titled “http 包”在 Dart/Flutter 中进行 HTTP 请求的核心包是 http。这是一个基于 Future 的库,利用了 Dart 的 async/await 能力。
-
将
http包依赖添加到pubspec.yaml文件中: -
运行
flutter pub get。 -
在 Dart 文件中导入该包,通常使用别名:
http 包提供了针对常见 HTTP 方法的顶级函数:
http.get(Uri url, {Map<String, String>? headers}): 发送 GET 请求。返回Future<http.Response>。http.post(Uri url, {Map<String, String>? headers, Object? body, Encoding? encoding}): 发送 POST 请求。body通常是一个 JSON 编码的字符串。返回Future<http.Response>。http.put(...): 发送 PUT 请求。http.patch(...): 发送 PATCH 请求。http.delete(...): 发送 DELETE 请求。http.head(...): 发送 HEAD 请求。
所有这些方法都返回一个 Future<http.Response>。http.Response 对象包含:
statusCode: HTTP 状态码(例如,200 表示成功,404 表示未找到,500 表示服务器错误)。body: 响应体,类型为 String。headers: 响应头的 Map。contentLength: 响应体长度(字节)。request: 关于原始请求的信息。
示例:获取数据 (GET 请求)
Section titled “示例:获取数据 (GET 请求)”让我们从 JSONPlaceholder API 获取一个示例 ‘todo’ 列表。
1. 定义数据模型
Section titled “1. 定义数据模型”创建一个简单的类来表示数据结构(例如,lib/todo.dart)。添加一个 fromJson 工厂构造函数可以简化解析。
class Todo { final int userId; final int id; final String title; final bool completed;
const Todo({ required this.userId, required this.id, required this.title, required this.completed, });
// Factory constructor to create a Todo from JSON // 从 JSON 创建 Todo 的工厂构造函数 factory Todo.fromJson(Map<String, dynamic> json) { return Todo( userId: json['userId'] as int, id: json['id'] as int, title: json['title'] as String, completed: json['completed'] as bool, ); }}2. 创建服务函数
Section titled “2. 创建服务函数”创建一个函数来处理 API 调用和解析。
import 'package:http/http.dart' as http;import 'dart:convert';import 'dart:async';import 'todo.dart'; // 导入你的 Todo 模型
Future<List<Todo>> fetchTodos() async { // Define the API endpoint URL // 定义 API 端点 URL final url = Uri.parse('https://jsonplaceholder.typicode.com/todos');
try { // Make the GET request // 发起 GET 请求 final response = await http.get(url);
// Check if the request was successful (status code 200) // 检查请求是否成功(状态码 200) if (response.statusCode == 200) { // Decode the JSON response body (which is a String) // 解码 JSON 响应体(一个 String) // The result is a List<dynamic> because jsonDecode doesn't know the specific types // 结果是一个 List<dynamic>,因为 jsonDecode 不知道具体类型 List<dynamic> jsonResponse = jsonDecode(response.body);
// Map the dynamic list to a List<Todo> using the fromJson factory // 使用 fromJson 工厂方法将 dynamic 列表映射到 List<Todo> return jsonResponse.map((data) => Todo.fromJson(data)).toList(); } else { // If the server did not return a 200 OK response, // throw an exception. // 如果服务器没有返回 200 OK 响应,则抛出异常。 throw Exception('Failed to load todos. Status code: ${response.statusCode}'); } } catch (e) { // Handle potential network errors or JSON parsing errors // 处理潜在的网络错误或 JSON 解析错误 print('Error fetching todos: $e'); throw Exception('Failed to load todos: $e'); }}3. 使用 FutureBuilder 在 UI 中显示数据
Section titled “3. 使用 FutureBuilder 在 UI 中显示数据”使用 FutureBuilder 来处理 API 调用的异步特性,并显示数据、加载状态或错误状态。
import 'package:flutter/material.dart';// Import fetchTodos function and Todo model// 导入 fetchTodos 函数和 Todo 模型import 'api_service.dart';import 'todo.dart';
class TodoListScreen extends StatefulWidget { const TodoListScreen({Key? key}) : super(key: key);
@override _TodoListScreenState createState() => _TodoListScreenState();}
class _TodoListScreenState extends State<TodoListScreen> { late Future<List<Todo>> futureTodos;
@override void initState() { super.initState(); // Call the fetch function when the widget is initialized // 在 widget 初始化时调用获取函数 futureTodos = fetchTodos(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Fetched Todos'), ), body: Center( child: FutureBuilder<List<Todo>>( future: futureTodos, // The Future<List<Todo>> to observe // 要观察的 Future<List<Todo>> builder: (context, snapshot) { // Check the connection state // 检查连接状态 if (snapshot.connectionState == ConnectionState.waiting) { // If waiting, show a loading indicator // 如果正在等待,显示加载指示器 return const CircularProgressIndicator(); } else if (snapshot.hasError) { // If there's an error, display it // 如果发生错误,显示错误信息 return Text('Error: ${snapshot.error}'); } else if (snapshot.hasData) { // If data is available, display the list // 如果数据可用,显示列表 final todos = snapshot.data!; return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( leading: CircleAvatar(child: Text(todo.id.toString())), title: Text(todo.title), trailing: Icon( todo.completed ? Icons.check_box : Icons.check_box_outline_blank, color: todo.completed ? Colors.green : Colors.grey, ), ); }, ); } else { // If no data and no error (shouldn't happen with this API if successful) // 如果没有数据也没有错误(如果 API 成功,不应该发生) return const Text('No todos found.'); } }, ), ), ); }}
// --- Usage Example ---// --- 使用示例 ---// void main() => runApp(MaterialApp(home: TodoListScreen()));发送数据 (POST 请求)
Section titled “发送数据 (POST 请求)”要发送数据(例如,创建一个新资源),请使用 http.post。
Future<http.Response> createTodo(String title) async { final url = Uri.parse('https://jsonplaceholder.typicode.com/todos');
try { final response = await http.post( url, headers: <String, String>{ 'Content-Type': 'application/json; charset=UTF-8', // Set content type // 设置内容类型 }, // Encode the data to JSON format // 将数据编码为 JSON 格式 body: jsonEncode(<String, dynamic>{ 'title': title, 'userId': 1, // Example data // 示例数据 'completed': false, }), );
if (response.statusCode == 201) { // 201 Created is common for successful POST // 201 Created 通常用于成功的 POST 请求 print('Todo created successfully: ${response.body}'); return response; } else { throw Exception('Failed to create todo. Status code: ${response.statusCode}'); } } catch (e) { print('Error creating todo: $e'); throw Exception('Failed to create todo: $e'); }}错误处理和最佳实践
Section titled “错误处理和最佳实践”- 始终检查
response.statusCode: 不要假设请求成功。检查 2xx 状态码表示成功,并适当处理 4xx(客户端错误)和 5xx(服务器错误)。 - 使用
try-catch: 网络请求可能因各种原因失败(无网络、DNS 问题、超时)。将你的http调用放在try-catch块中。 - 处理 JSON 解码错误: 如果响应体不是有效的 JSON,
jsonDecode可能会抛出FormatException。将此错误包含在你的错误处理中。 - 使用数据模型: 将 JSON 解析为强类型的 Dart 对象可以使你的代码更安全、更容易使用。考虑使用
json_serializable等包来自动化生成复杂模型的fromJson/toJson方法。 - 提供用户反馈: 在获取数据时显示加载指示器 (
CircularProgressIndicator),并向用户显示清晰的错误消息。 - 考虑使用
http.Client: 对于向同一服务器发起多个请求,创建http.Client实例可以更高效,因为它能重用 TCP 连接。使用完毕后记得调用client.close()。 - 替代包: 对于更高级的功能,如拦截器、FormData 支持、请求取消以及更好的错误处理,可以考虑使用
dio等包。
访问 REST API 是许多 Flutter 应用的核心部分。http 包提供了一个坚实的基础,将其与 FutureBuilder 和适当的错误处理结合使用,可以有效地将网络数据集成到你的 UI 中。