Flutter http插件的封装:简化网络请求代码
一、Flutter 中的网络请求基础
在 Flutter 应用开发中,网络请求是非常常见的操作。无论是获取数据展示给用户,还是将用户数据提交到服务器,都离不开网络请求。Flutter 官方提供了 http
插件来处理 HTTP 请求,它是一个强大且灵活的工具,能满足大多数网络请求场景。
在开始封装之前,我们先来了解一下如何直接使用 http
插件进行简单的网络请求。
- 添加依赖
首先,在
pubspec.yaml
文件中添加http
依赖:
dependencies:
http: ^0.13.4
然后运行 flutter pub get
来获取依赖。
- GET 请求示例
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> fetchData() async {
final response = await http.get(Uri.parse('https://example.com/api/data'));
if (response.statusCode == 200) {
final jsonData = jsonDecode(response.body);
print(jsonData);
} else {
print('Request failed with status: ${response.statusCode}.');
}
}
在这个例子中,我们使用 http.get
方法发送一个 GET 请求到指定的 URL。如果响应状态码为 200,说明请求成功,我们将响应体解析为 JSON 格式数据并打印出来;否则,打印错误信息。
- POST 请求示例
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> postData() async {
final data = {
'key1': 'value1',
'key2': 'value2'
};
final response = await http.post(
Uri.parse('https://example.com/api/submit'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
);
if (response.statusCode == 200) {
print('Data posted successfully.');
} else {
print('Request failed with status: ${response.statusCode}.');
}
}
这里我们使用 http.post
方法发送 POST 请求。设置了请求头 Content - Type
为 application/json
,并将数据转换为 JSON 格式作为请求体发送。同样,根据响应状态码判断请求是否成功。
二、为什么要封装 http 插件
虽然直接使用 http
插件能实现网络请求功能,但在实际项目中,随着业务的增长和复杂度的提高,会发现存在以下问题:
-
代码重复 在不同页面或模块中进行网络请求时,很多代码是重复的,比如设置请求头、处理响应状态码等。例如,每个请求都可能需要设置
Content - Type
为application/json
,以及对常见的状态码(如 200、404、500 等)进行类似的处理。 -
可维护性差 如果项目后期需要修改网络请求的一些通用逻辑,如添加统一的认证头、修改请求超时时间等,需要在所有使用网络请求的地方逐一修改,这不仅工作量大,还容易出错。
-
代码可读性降低 直接使用
http
插件的代码可能会在业务逻辑中显得比较突兀,使代码的整体可读性变差。例如,在一个复杂的页面逻辑中,混杂着网络请求的具体实现代码,会让开发者难以快速理解业务流程。
通过封装 http
插件,可以有效地解决这些问题。封装后的代码更加简洁、易于维护和复用,提高整个项目的开发效率和代码质量。
三、Flutter http 插件封装步骤
- 创建网络请求基类 首先,我们创建一个基类来处理一些通用的网络请求逻辑。
import 'package:http/http.dart' as http;
import 'dart:convert';
class BaseApiService {
final String baseUrl;
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
final response = await http.get(Uri.parse('$baseUrl$endpoint'));
return _handleResponse(response);
}
Future<dynamic> post(String endpoint, Map<String, dynamic> data) async {
final response = await http.post(
Uri.parse('$baseUrl$endpoint'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
);
return _handleResponse(response);
}
dynamic _handleResponse(http.Response response) {
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
}
在这个基类 BaseApiService
中,我们定义了 baseUrl
作为基础 URL,所有请求的 URL 都将基于此。get
和 post
方法分别处理 GET 和 POST 请求,它们都调用了 _handleResponse
方法来统一处理响应。_handleResponse
方法根据不同的状态码进行相应的处理,成功时解析 JSON 数据,错误时抛出异常。
- 创建具体的 API 服务类
接下来,我们创建具体的 API 服务类,继承自
BaseApiService
,用于处理特定业务的网络请求。
class UserApiService extends BaseApiService {
UserApiService() : super(baseUrl: 'https://example.com/api/');
Future<dynamic> fetchUser() async {
return get('user');
}
Future<dynamic> createUser(Map<String, dynamic> data) async {
return post('user', data);
}
}
在 UserApiService
类中,我们设置了特定的 baseUrl
为 https://example.com/api/
。然后定义了 fetchUser
和 createUser
方法,分别用于获取用户信息和创建新用户。这些方法内部调用了基类的 get
和 post
方法,使得代码更加简洁和清晰。
- 在项目中使用封装后的 API 服务 在 Flutter 项目的页面或其他逻辑中,我们可以这样使用封装后的 API 服务:
import 'package:flutter/material.dart';
import './UserApiService.dart';
class UserPage extends StatefulWidget {
@override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
UserApiService userApiService = UserApiService();
dynamic userData;
@override
void initState() {
super.initState();
_fetchUser();
}
Future<void> _fetchUser() async {
try {
userData = await userApiService.fetchUser();
setState(() {});
} catch (e) {
print('Error: $e');
}
}
Future<void> _createUser() async {
final newUserData = {
'name': 'John Doe',
'email': 'johndoe@example.com'
};
try {
await userApiService.createUser(newUserData);
print('User created successfully.');
} catch (e) {
print('Error: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (userData != null)
Text('User Name: ${userData['name']}'),
ElevatedButton(
onPressed: _createUser,
child: Text('Create User'),
),
],
),
),
);
}
}
在这个 UserPage
页面中,我们创建了 UserApiService
的实例,并在 initState
中调用 fetchUser
方法获取用户信息。createUser
方法用于创建新用户,当点击按钮时会触发该方法。通过这种方式,我们的业务逻辑代码与网络请求代码分离,使代码更易于理解和维护。
四、封装的高级特性扩展
- 添加请求拦截器 有时候我们需要在请求发送之前对请求进行一些处理,比如添加认证头、修改请求参数等。这时候可以通过添加请求拦截器来实现。
import 'package:http/http.dart' as http;
import 'dart:convert';
class BaseApiService {
final String baseUrl;
List<Function(http.Request)> requestInterceptors = [];
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
final request = http.Request('GET', Uri.parse('$baseUrl$endpoint'));
_applyRequestInterceptors(request);
final response = await http.Client().send(request);
return _handleResponse(response);
}
Future<dynamic> post(String endpoint, Map<String, dynamic> data) async {
final request = http.Request('POST', Uri.parse('$baseUrl$endpoint'));
request.headers['Content-Type'] = 'application/json; charset=UTF-8';
request.body = jsonEncode(data);
_applyRequestInterceptors(request);
final response = await http.Client().send(request);
return _handleResponse(response);
}
void _applyRequestInterceptors(http.Request request) {
for (final interceptor in requestInterceptors) {
interceptor(request);
}
}
dynamic _handleResponse(http.StreamedResponse response) async {
final responseBody = await response.stream.bytesToString();
if (response.statusCode == 200) {
return jsonDecode(responseBody);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
}
在这个扩展后的 BaseApiService
中,我们添加了一个 requestInterceptors
列表,用于存储请求拦截器函数。在 get
和 post
方法中,通过 _applyRequestInterceptors
方法应用这些拦截器。例如,我们可以这样添加一个认证头的拦截器:
class UserApiService extends BaseApiService {
UserApiService() : super(baseUrl: 'https://example.com/api/');
@override
void init() {
requestInterceptors.add((request) {
request.headers['Authorization'] = 'Bearer your_token';
return request;
});
}
}
在 UserApiService
的 init
方法中,我们添加了一个拦截器,为每个请求添加了 Authorization
头。
- 添加响应拦截器 类似地,我们也可以在响应返回之后对响应进行处理,比如统一处理错误提示、解析特定格式的数据等。
import 'package:http/http.dart' as http;
import 'dart:convert';
class BaseApiService {
final String baseUrl;
List<Function(dynamic)> responseInterceptors = [];
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
final response = await http.get(Uri.parse('$baseUrl$endpoint'));
return _handleResponse(response);
}
Future<dynamic> post(String endpoint, Map<String, dynamic> data) async {
final response = await http.post(
Uri.parse('$baseUrl$endpoint'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
);
return _handleResponse(response);
}
dynamic _handleResponse(http.Response response) {
dynamic result;
if (response.statusCode == 200) {
result = jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
for (final interceptor in responseInterceptors) {
result = interceptor(result);
}
return result;
}
}
在这个扩展中,我们添加了 responseInterceptors
列表,并在 _handleResponse
方法中应用这些拦截器。例如,我们可以添加一个拦截器来统一处理错误提示:
class UserApiService extends BaseApiService {
UserApiService() : super(baseUrl: 'https://example.com/api/');
@override
void init() {
responseInterceptors.add((data) {
if (data is Exception) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(data.toString())),
);
return null;
}
return data;
});
}
}
在 UserApiService
的 init
方法中,我们添加了一个响应拦截器。如果响应结果是一个异常,它会在屏幕上显示一个 SnackBar 提示错误信息,并返回 null
。
- 设置请求超时 为了避免网络请求长时间等待,我们可以设置请求超时时间。
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
class BaseApiService {
final String baseUrl;
Duration timeout = const Duration(seconds: 10);
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
try {
final response = await http.get(
Uri.parse('$baseUrl$endpoint'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
).timeout(timeout);
return _handleResponse(response);
} on TimeoutException {
throw Exception('Request timed out');
}
}
Future<dynamic> post(String endpoint, Map<String, dynamic> data) async {
try {
final response = await http.post(
Uri.parse('$baseUrl$endpoint'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
).timeout(timeout);
return _handleResponse(response);
} on TimeoutException {
throw Exception('Request timed out');
}
}
dynamic _handleResponse(http.Response response) {
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
}
在这个扩展中,我们定义了 timeout
变量,并在 get
和 post
方法中使用 timeout
方法来设置请求超时时间。如果请求超时,会抛出 TimeoutException
,我们在捕获该异常时抛出一个自定义的异常提示请求超时。
五、实际项目中的注意事项
- 错误处理
在封装的网络请求中,虽然我们已经对常见的状态码进行了处理,但实际项目中可能会遇到更多复杂的错误情况。比如网络连接失败、服务器返回的数据格式不符合预期等。对于这些情况,我们需要更细致的错误处理。可以在
_handleResponse
方法中进一步扩展,例如:
dynamic _handleResponse(http.Response response) {
try {
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
} on FormatException {
throw Exception('Invalid data format received');
}
}
这里我们捕获了 FormatException
,用于处理服务器返回的数据格式不符合 JSON 格式的情况。
- 性能优化
在高并发的网络请求场景下,性能优化是非常重要的。可以考虑使用
http.Client
的复用,避免每次请求都创建新的Client
实例。例如:
class BaseApiService {
final String baseUrl;
final http.Client client = http.Client();
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
final response = await client.get(Uri.parse('$baseUrl$endpoint'));
return _handleResponse(response);
}
Future<dynamic> post(String endpoint, Map<String, dynamic> data) async {
final response = await client.post(
Uri.parse('$baseUrl$endpoint'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(data),
);
return _handleResponse(response);
}
// 记得在合适的地方释放资源,比如在页面销毁时
void dispose() {
client.close();
}
dynamic _handleResponse(http.Response response) {
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
}
这样可以减少资源开销,提高性能。同时,在不需要使用 client
时,记得调用 client.close()
方法释放资源,例如在页面销毁时。
- 安全性
在处理网络请求时,安全性是至关重要的。对于敏感数据的传输,一定要使用 HTTPS 协议。此外,在请求和响应处理过程中,要防止 SQL 注入、XSS 攻击等安全漏洞。比如,在构建请求 URL 时,不要直接拼接用户输入的数据,而是使用
Uri
的参数构建方法:
Future<dynamic> get(String endpoint, Map<String, dynamic> params) async {
final uri = Uri.parse('$baseUrl$endpoint').replace(queryParameters: params);
final response = await http.get(uri);
return _handleResponse(response);
}
这样可以有效防止 SQL 注入等安全问题。
- 缓存机制
在一些情况下,我们希望对网络请求的结果进行缓存,以减少不必要的网络请求,提高应用的响应速度。可以通过在内存或本地存储中缓存数据来实现。例如,使用
shared_preferences
插件在本地存储缓存数据:
import 'package:shared_preferences/shared_preferences.dart';
class BaseApiService {
final String baseUrl;
final Duration cacheDuration = const Duration(minutes: 5);
BaseApiService({required this.baseUrl});
Future<dynamic> get(String endpoint) async {
final prefs = await SharedPreferences.getInstance();
final cachedData = prefs.getString(endpoint);
if (cachedData != null) {
final cachedTime = prefs.getDouble('${endpoint}_time')?? 0;
if (DateTime.now().millisecondsSinceEpoch - cachedTime < cacheDuration.inMilliseconds) {
return jsonDecode(cachedData);
}
}
final response = await http.get(Uri.parse('$baseUrl$endpoint'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
prefs.setString(endpoint, jsonEncode(data));
prefs.setDouble('${endpoint}_time', DateTime.now().millisecondsSinceEpoch.toDouble());
return data;
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
// 其他方法...
dynamic _handleResponse(http.Response response) {
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else if (response.statusCode == 404) {
throw Exception('Not Found');
} else if (response.statusCode == 500) {
throw Exception('Internal Server Error');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
}
}
在这个示例中,我们在 get
方法中检查本地缓存,如果缓存存在且未过期,则直接返回缓存数据;否则,发送网络请求,并在请求成功后更新缓存。
通过以上步骤和注意事项,我们可以对 Flutter 的 http
插件进行有效的封装,简化网络请求代码,提高项目的开发效率和质量,使其更适合实际项目的需求。无论是小型应用还是大型项目,良好的网络请求封装都能为开发过程带来诸多便利。同时,随着项目的发展和需求的变化,我们可以根据实际情况对封装的代码进行进一步的扩展和优化。