Skip to content

flutter_accessing_rest_apis

大多数现代移动应用需要与后端服务交互来获取或发送数据。使用 JSON (JavaScript Object Notation) 通过 HTTP/S 的 REST (Representational State Transfer) API 是这种通信的常用标准。

Flutter 提供了包(package)和内置的 Dart 特性,可以轻松地消费(consume)REST API。

在 Dart/Flutter 中进行 HTTP 请求的核心包是 http。这是一个基于 Future 的库,利用了 Dart 的 async/await 能力。

  1. 将 http 包依赖添加到 pubspec.yaml 文件中:

  2. 运行 flutter pub get。

  3. 在 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: 关于原始请求的信息。

让我们从 JSONPlaceholder API 获取一个示例 ‘todo’ 列表。

创建一个简单的类来表示数据结构(例如,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,
);
}
}

创建一个函数来处理 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()));

要发送数据(例如,创建一个新资源),请使用 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');
}
}
  • 始终检查 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 中。