网络请求
网络请求是现代应用的核心功能。本章将介绍如何在 Flutter 中进行 HTTP 请求、处理响应数据和错误。
HTTP 基础
添加依赖
在 pubspec.yaml 中添加 http 包:
dependencies:
http: ^1.0.0
发送 GET 请求
import 'package:http/http.dart' as http;
import 'dart:convert';
// 基本 GET 请求
Future<void> fetchData() async {
final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
try {
final response = await http.get(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print('标题:${data['title']}');
} else {
print('请求失败:${response.statusCode}');
}
} catch (e) {
print('错误:$e');
}
}
发送 POST 请求
Future<void> createPost() async {
final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
try {
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'title': '新文章',
'body': '文章内容',
'userId': 1,
}),
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
print('创建成功,ID:${data['id']}');
}
} catch (e) {
print('错误:$e');
}
}
其他请求方法
// PUT 请求(完整更新)
final response = await http.put(
Uri.parse('https://api.example.com/posts/1'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': '更新的标题',
'body': '更新的内容',
}),
);
// PATCH 请求(部分更新)
final response = await http.patch(
Uri.parse('https://api.example.com/posts/1'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': '新标题',
}),
);
// DELETE 请求
final response = await http.delete(
Uri.parse('https://api.example.com/posts/1'),
);
Dio
Dio 是一个强大的 HTTP 客户端,支持拦截器、全局配置、FormData 等高级功能。
添加依赖
dependencies:
dio: ^5.0.0
基本用法
import 'package:dio/dio.dart';
// 创建 Dio 实例
final dio = Dio();
// GET 请求
Future<void> fetchWithDio() async {
try {
final response = await dio.get(
'https://jsonplaceholder.typicode.com/posts',
queryParameters: {
'userId': 1,
},
);
print('状态码:${response.statusCode}');
print('数据:${response.data}');
} on DioException catch (e) {
if (e.response != null) {
print('服务器错误:${e.response?.statusCode}');
print('错误信息:${e.response?.data}');
} else {
print('网络错误:${e.message}');
}
}
}
// POST 请求
Future<void> postWithDio() async {
try {
final response = await dio.post(
'https://jsonplaceholder.typicode.com/posts',
data: {
'title': '新文章',
'body': '文章内容',
'userId': 1,
},
);
print('创建成功:${response.data}');
} catch (e) {
print('错误:$e');
}
}
全局配置
final dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
拦截器
拦截器可以在请求发送前和响应返回后进行处理:
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// 请求前处理
print('发送请求:${options.uri}');
// 添加 token
options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
},
onResponse: (response, handler) {
// 响应后处理
print('收到响应:${response.statusCode}');
handler.next(response);
},
onError: (error, handler) {
// 错误处理
print('请求错误:${error.message}');
if (error.response?.statusCode == 401) {
// Token 过期,刷新 token
refreshToken().then((newToken) {
// 重新发送请求
final options = error.requestOptions;
options.headers['Authorization'] = 'Bearer $newToken';
dio.fetch(options).then((response) {
handler.resolve(response);
});
});
} else {
handler.next(error);
}
},
),
);
// 日志拦截器
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
数据模型
手动解析 JSON
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
// 使用
Future<User> fetchUser(int id) async {
final response = await dio.get('/users/$id');
return User.fromJson(response.data);
}
使用 json_serializable(推荐)
添加依赖:
dependencies:
json_annotation: ^4.8.0
dev_dependencies:
json_serializable: ^6.7.0
build_runner: ^2.4.0
创建模型:
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
运行代码生成:
flutter pub run build_runner build
API 服务封装
创建一个统一的 API 服务类:
class ApiService {
static final ApiService _instance = ApiService._internal();
late final Dio _dio;
factory ApiService() => _instance;
ApiService._internal() {
_dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
),
);
_setupInterceptors();
}
void _setupInterceptors() {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
final token = StorageService.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// 处理 token 过期
}
handler.next(error);
},
),
);
}
Future<Response> get(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
return await _dio.get(path, queryParameters: queryParameters);
}
Future<Response> post(
String path, {
dynamic data,
}) async {
return await _dio.post(path, data: data);
}
Future<Response> put(
String path, {
dynamic data,
}) async {
return await _dio.put(path, data: data);
}
Future<Response> delete(String path) async {
return await _dio.delete(path);
}
}
使用示例
class UserRepository {
final _api = ApiService();
Future<List<User>> getUsers() async {
final response = await _api.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
}
Future<User> getUser(int id) async {
final response = await _api.get('/users/$id');
return User.fromJson(response.data);
}
Future<User> createUser(User user) async {
final response = await _api.post('/users', data: user.toJson());
return User.fromJson(response.data);
}
}
文件上传
Future<void> uploadFile(String filePath) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
'description': '上传的文件',
});
try {
final response = await dio.post('/upload', data: formData);
print('上传成功:${response.data}');
} catch (e) {
print('上传失败:$e');
}
}
// 上传进度
Future<void> uploadWithProgress(String filePath) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
});
await dio.post(
'/upload',
data: formData,
onSendProgress: (sent, total) {
final progress = sent / total;
print('上传进度:${(progress * 100).toStringAsFixed(0)}%');
},
);
}
文件下载
Future<void> downloadFile(String url, String savePath) async {
await dio.download(
url,
savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = received / total;
print('下载进度:${(progress * 100).toStringAsFixed(0)}%');
}
},
);
print('下载完成:$savePath');
}
错误处理
统一错误处理
class ApiException implements Exception {
final int code;
final String message;
ApiException(this.code, this.message);
@override
String toString() => 'ApiException($code): $message';
}
Future<T> handleApiRequest<T>(Future<T> Function() request) async {
try {
return await request();
} on DioException catch (e) {
if (e.response != null) {
switch (e.response?.statusCode) {
case 400:
throw ApiException(400, '请求参数错误');
case 401:
throw ApiException(401, '未授权,请重新登录');
case 403:
throw ApiException(403, '禁止访问');
case 404:
throw ApiException(404, '资源不存在');
case 500:
throw ApiException(500, '服务器错误');
default:
throw ApiException(
e.response?.statusCode ?? 0,
e.response?.statusMessage ?? '未知错误',
);
}
} else {
throw ApiException(-1, '网络连接失败');
}
} catch (e) {
throw ApiException(-2, '未知错误:$e');
}
}
// 使用
Future<User> fetchUser(int id) async {
return handleApiRequest(() async {
final response = await dio.get('/users/$id');
return User.fromJson(response.data);
});
}
小结
本章我们学习了:
- HTTP 基础:GET、POST、PUT、DELETE 请求
- Dio 使用:配置、拦截器、错误处理
- 数据模型:手动解析和 json_serializable
- API 封装:统一的 API 服务层
- 文件操作:上传和下载
- 错误处理:统一的错误处理机制
练习
- 创建一个天气应用,从 API 获取天气数据
- 实现一个图片上传功能,支持进度显示
- 封装一个完整的 API 服务层
- 实现离线缓存功能
下一步
下一章我们将学习 本地存储,掌握数据持久化技术。