跳到主要内容

网络请求

网络请求是现代应用的核心功能。本章将介绍如何在 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);
});
}

小结

本章我们学习了:

  1. HTTP 基础:GET、POST、PUT、DELETE 请求
  2. Dio 使用:配置、拦截器、错误处理
  3. 数据模型:手动解析和 json_serializable
  4. API 封装:统一的 API 服务层
  5. 文件操作:上传和下载
  6. 错误处理:统一的错误处理机制

练习

  1. 创建一个天气应用,从 API 获取天气数据
  2. 实现一个图片上传功能,支持进度显示
  3. 封装一个完整的 API 服务层
  4. 实现离线缓存功能

下一步

下一章我们将学习 本地存储,掌握数据持久化技术。