在 Flutter 开发中,Retrofit 作为一款强大的类型安全 HTTP 客户端,为我们提供了便捷的网络请求方式。然而,为了更好地管理项目中的网络请求,我们通常需要对 Retrofit 进行二次封装,以实现统一的错误处理、数据转换、以及自定义配置等功能。
本文将详细介绍如何基于 Retrofit 对 Flutter 网络请求模块进行二次封装,并提供完整的代码示例。
为什么要二次封装?
- 统一错误处理: 将所有网络请求的错误处理集中在一个地方,方便统一管理和扩展。
- 数据转换: 提供统一的数据转换机制,将网络返回的数据转换为我们需要的模型。
- 自定义配置: 可以自定义请求头、超时时间、日志输出等配置项。
- 提高可维护性: 通过封装,可以降低代码耦合度,提高代码的可读性和可维护性。
封装步骤
1. 引入依赖
dependencies:
retrofit: any
json_annotation: any
dio: any
dev_dependencies:
retrofit_generator: any
build_runner: any
json_serializable: any
2. 创建 BaseDio
class BaseDio {
BaseDio._internal();
static BaseDio? _instance;
static BaseDio getInstance() {
_instance ??= BaseDio._internal();
return _instance!;
}
Dio getDio() {
final Dio dio = Dio();
dio.options = BaseOptions(
receiveTimeout: const Duration(seconds: 60),
connectTimeout: const Duration(seconds: 60));
// 添加拦截器,如 token之类,需要全局使用的参数
dio.interceptors.add(HeaderInterceptor());
// 添加响应拦截器, 统一处理业务的错误信息
dio.interceptors.add(ResponseInterceptor());
// 添加日志格式化工具类
dio.interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseBody: true,
responseHeader: false,
error: true,
compact: false,
maxWidth: 180,
));
return dio;
}
}
-
HeaderInterceptor: 用于添加公共请求头,如 token、版本号等。
-
ResponseInterceptor: 用于处理网络请求的响应结果,包括成功和失败情况。
-
PrettyDioLogger: 用于打印网络请求日志,方便调试。
拦截器的封装
- HeaderInterceptor的实现:主要实现添加token等需要全局使用的参数
class HeaderInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
String? authorization = "await getAuthorization()";
options.headers["Authorization"] = "Bearer $authorization";
handler.next(options);
}
}
- ResponseInterceptor的实现:主要实现对返回值的处理,这里暂时使用toast进行了提示,具体可以根据业务进行不同的处理
class ResponseInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (response.statusCode == 200) {
// 接口请求成功,统一处理业务数据
if (response.data is Map<String, dynamic>) {
BaseModel baseError = BaseModel.failureFromJson(response.data);
if (baseError.code == 200) {
handler.next(response);
} else {
// toast弹窗, 业务数据失败,统一处理/或者按照需求对相应的code做特殊处理
Fluttertoast.showToast(msg: baseError.message);
}
} else {
handler.next(response);
}
} else {
// 接口请求失败,统一处理业务数据
BaseModel baseError = BaseModel.customError(
code: response.statusCode!,
message: response.statusMessage ?? "unkown error");
Fluttertoast.showToast(msg: baseError.message);
}
}
}
3. 创建 BaseModel
// 模型转换器类型
typedef JsonConverter<T> = T Function(dynamic json);
class BaseModel<T> {
final int code;
final String message;
final T? data;
BaseModel._internal({required this.code, required this.message, this.data});
factory BaseModel.customError({required int code, required String message}) =>
BaseModel._internal(code: code, message: message);
factory BaseModel.failureFromJson(Map<String, dynamic> json) {
return BaseModel._internal(
code: json['code'], message: json['message'], data: null);
}
factory BaseModel.fromJson({
required Map<String, dynamic> json,
required JsonConverter<T> converter,
}) {
return BaseModel._internal(
code: json['code'],
message: json['message'],
data: converter(json['data']));
}
}
-
BaseModel 用于封装网络请求的返回结果,包括状态码、消息和数据。
-
JsonConverter 是一个泛型类型转换函数,用于将 JSON 数据转换为对应的 Dart 对象。
4. 创建 DataConverter
class DataConverter {
// 转换基础类型
static T basicConverter<T>(dynamic data) {
if (data is T) {
return data;
}
throw Exception(
'Data type mismatch. Expected $T but got ${data.runtimeType}');
}
// 转换列表类型
static List<T> listConverter<T>(
dynamic data,
T Function(Map<String, dynamic>) itemConverter,
) {
if (data is! List) {
throw Exception('Expected List but got ${data.runtimeType}');
}
return data
.map((item) => itemConverter(item as Map<String, dynamic>))
.toList();
}
// 转换对象类型
static T mapConverter<T>(
dynamic data,
T Function(Map<String, dynamic>) itemConverter,
) {
if (data is! Map) {
throw Exception('Expected Map but got ${data.runtimeType}');
}
return itemConverter(data as Map<String, dynamic>);
}
}
- DataConverter 提供了常用的数据转换方法,如列表转换、对象转换等。
5. 创建 ApiClient
part 'api_client.g.dart';
@RestApi(baseUrl: 'https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/')
abstract class ApiClient {
factory ApiClient({Dio? dio, String? baseUrl}) {
dio ??= BaseDio.getInstance().getDio();
return _ApiClient(dio, baseUrl: baseUrl);
}
@GET('/tasks')
Future<List<Task>> getTasks(
JsonConverter<List<Task>> converter,
);
}
ApiClient 定义了具体的 API 接口,每个接口对应一个网络请求
使用示例
List<Task> tasks = await ApiClient()
.getTasks((data) => DataConverter.listConverter(data, Task.fromJson));
总结
通过上述步骤,我们成功地对 Retrofit 进行二次封装,实现了以下功能:
- 统一的网络请求配置: 在 BaseDio 中配置了全局的网络请求参数。
- 统一的错误处理: 在 ResponseInterceptor 中对网络请求的错误进行了统一处理。
- 类型安全的数据转换: 使用 JsonConverter 和 BaseModel 保证了数据转换的类型安全。
- 可扩展性: 可以根据项目需求自定义拦截器、数据模型和转换逻辑。
注意:
- 错误处理: 可以根据具体业务需求,在 ResponseInterceptor 中添加更复杂的错误处理逻辑,例如显示错误提示、重试请求等。
- 数据转换: JsonConverter 可以根据不同的数据结构自定义转换逻辑。
- 日志输出: PrettyDioLogger 可以帮助我们调试网络请求,但需要注意在发布版本中关闭日志输出。
通过对 Retrofit 进行二次封装,我们可以构建一个稳定、高效、易维护的网络请求模块,为 Flutter 应用提供坚实的网络支持。