精选文章

Flutter 网络层封装

2024-02-27 · 网络

网络层是应用稳定性的底座。一个好的网络封装,不是把 Diohttp 包一层,而是解决三件事:统一入口、统一错误、统一可观测。下面给出一套可直接复用的网络层范式。

1. 先定义统一返回模型

无论后端风格怎样,客户端应该先把返回统一成一个模型:

class ApiResponse<T> {
  final T? data;
  final ApiError? error;
  final int? code;
  final String? message;

  const ApiResponse({this.data, this.error, this.code, this.message});

  bool get isSuccess => error == null;
}

class ApiError {
  final int code;
  final String message;
  const ApiError(this.code, this.message);
}

2. 封装统一请求入口

所有请求通过一个 Client 走,不让业务层直接调用 Dio.get

class ApiClient {
  final Dio dio;

  ApiClient(this.dio);

  Future<ApiResponse<T>> get<T>(
    String path, {
    Map<String, dynamic>? query,
    T Function(dynamic json)? parser,
  }) async {
    try {
      final res = await dio.get(path, queryParameters: query);
      final data = parser != null ? parser(res.data) : res.data as T;
      return ApiResponse<T>(data: data, code: res.statusCode);
    } on DioException catch (e) {
      return ApiResponse<T>(error: _mapError(e), code: e.response?.statusCode);
    }
  }
}

3. 错误规范化

把各种错误映射成统一错误:

ApiError _mapError(DioException e) {
  switch (e.type) {
    case DioExceptionType.connectionTimeout:
      return const ApiError(1001, '连接超时');
    case DioExceptionType.receiveTimeout:
      return const ApiError(1002, '响应超时');
    case DioExceptionType.badResponse:
      return ApiError(e.response?.statusCode ?? 500, '服务端错误');
    default:
      return const ApiError(1000, '网络异常');
  }
}

业务层只关心 ApiError,不用面对多种异常类型。

4. 拦截器统一处理

常见的处理逻辑可以放在拦截器里:

Dio buildDio() {
  final dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 10)));
  dio.interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      options.headers['Authorization'] = 'Bearer xxx';
      handler.next(options);
    },
    onResponse: (response, handler) {
      handler.next(response);
    },
    onError: (error, handler) {
      handler.next(error);
    },
  ));
  return dio;
}

统一 token、日志、埋点,这些都应该在这一层处理。

5. 重试策略(谨慎使用)

不建议对所有请求自动重试,只对幂等请求使用:

Future<T> retry<T>(Future<T> Function() task, {int times = 2}) async {
  for (var i = 0; i <= times; i++) {
    try {
      return await task();
    } catch (_) {
      if (i == times) rethrow;
    }
  }
  throw Exception('unreachable');
}

6. 业务层使用方式

final api = ApiClient(buildDio());
final res = await api.get('/user/profile', parser: (json) => User.fromJson(json));

if (res.isSuccess) {
  // 使用 res.data
} else {
  // 处理 res.error
}

业务层再也不用 try/catch 捕获各种 Dio 异常。

7. 网络层清单

  • 所有请求走统一入口
  • 统一错误映射
  • 拦截器处理 token / 日志
  • 只对幂等请求重试
  • 业务层只处理 ApiResponse

总结

好的网络层封装会让业务代码更“干净”,而不是在每个页面里重复处理异常和状态。统一入口、统一错误、统一可观测,是长期可维护的关键。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇