Flutter 基于retrofit的网络库二次封装

1,671 阅读3分钟

在 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 应用提供坚实的网络支持。