Flutter/Dart中使用泛型封装REST API响应的优化方案
Flutter API响应统一处理优化方案
问题背景
我在Flutter中调用一个REST API,该API会返回三种不同的响应类型:
1. 包含单个数据的响应
{ "data": { "amount": 3773.31 }, "responseId": "9167712d-f74b-412d-9677-95f7df54fa3a", "created": "2023-05-10T08:52:53.5394905Z", "status": { "code": 0, "message": "Operación completada con éxito", "isSuccess": true } }
2. 包含分页列表数据的响应
{ "data": { "pageSize": 10, "currentPage": 1, "totalItems": 20, "totalPages": 2, "items": [ { "id": 171631, "dateCreated": "2023-04-29T14:53:48.4233333", "cost": 0.28, "minutes": 300, "resource": 17 }, { "id": 171630, "dateCreated": "2023-04-29T14:37:32.6233333", "cost": 0.43, "minutes": 900, "resource": 17 }, { "id": 171629, "dateCreated": "2023-04-29T11:55:14.7733333", "cost": 0.23, "minutes": 120, "resource": 0 }, { "id": 171625, "dateCreated": "2023-04-29T10:20:05.3333333", "cost": 3.7, "minutes": 10800, "resource": 6 }, { "id": 171621, "dateCreated": "2023-04-29T09:06:57.4033333", "cost": 0.5, "minutes": 1200, "resource": 0 }, { "id": 171620, "dateCreated": "2023-04-29T08:59:16.5533333", "cost": 0.23, "minutes": 60, "resource": 0 }, { "id": 171617, "dateCreated": "2023-04-29T08:15:36.64", "cost": 0.23, "minutes": 60, "resource": 0 }, { "id": 171615, "dateCreated": "2023-04-29T07:29:29.6633333", "cost": 0.95, "minutes": 600, "resource": 0 }, { "id": 171614, "dateCreated": "2023-04-29T07:23:31.4966667", "cost": 0.58, "minutes": 300, "resource": 0 }, { "id": 171611, "dateCreated": "2023-04-28T17:55:30.0366667", "cost": 0.28, "minutes": 300, "resource": 0 } ] }, "responseId": "9fd49e0e-8543-4ad7-97ea-53d607c48f05", "created": "2023-05-10T08:53:29.382979Z", "status": { "code": 0, "message": "Operación completada con éxito", "isSuccess": true } }
3. 无数据返回的响应
{ "responseId": "a180068b-01f6-41b3-b19a-0ed2744d3a80", "created": "2023-05-10T08:54:17.7533949Z", "status": { "code": 0, "message": "Operación completada con éxito", "isSuccess": true } }
目前我通过以下代码处理响应,但每个返回列表的API都需要单独定义类似OrderListResponse的类,重复代码冗余,希望用泛型类型统一处理这类场景。
现有核心处理代码:
class ApiHelper { final String apiUrl = "myapp.com"; Future<dynamic> secureGet<T>( String url, FromJson<T> create, ) async { final String? token = await SecureStorage().getToken(); Map<String, String> headers = { HttpHeaders.contentTypeHeader: 'application/json', HttpHeaders.acceptHeader: 'application/json', HttpHeaders.authorizationHeader: 'Bearer $token', }; Uri uri = Uri.https( apiUrl, url, ); try { final response = await http.get(uri, headers: headers); return _returnResponse<T>(response, create); } on SocketException { throw FetchDataException('Sin conexión'); } on TimeoutException { throw FetchDataException('Tiempo de espera agotado'); } on Error catch (e) { throw FetchDataException('Error general: $e'); } } typedef FromJson<T> = T Function(Map<String, dynamic>); dynamic _returnResponse<T>(Response response, FromJson<T> create) { // Makes sure the HTTP request has ended with a 200 succes code switch (response.statusCode) { case 200: { // Get JSON object var data = json.decode(response.body); var apiResponse = ApiResponse<T>.fromJson(data, create); if (!apiResponse.status.isSuccess) throw ApiException(apiResponse.status.message); if (apiResponse.data != null) return create(apiResponse.data); return apiResponse.data; } case 400: throw BadRequestException(response.body); case 401: case 403: throw UnauthorisedException(response.body); case 500: case 404: default: throw FetchDataException(response.statusCode); }} class ApiResponse<T> { dynamic data; final APIStatus status; ApiResponse({ required this.status, required this.data, }); factory ApiResponse.fromJson( Map<String, dynamic> json, Function(Map<String, dynamic>) create) => new ApiResponse( status: APIStatus.fromJson(json["status"]), data: json['data'], ); } class OrderListResponse { List<Order> results = []; OrderListResponse({required this.results}); OrderListResponse.fromJson(Map<String, dynamic> json) { if (json['items'] != null) { json['items'].forEach((v) { results.add(new Order.fromJson(v)); }); } } } }
优化方案
可以通过创建泛型ListResult<T>类统一处理分页列表响应,同时调整ApiResponse和解析逻辑,彻底消除重复代码。
1. 定义泛型分页列表实体
class ListResult<T> { final int pageSize; final int currentPage; final int totalItems; final int totalPages; final List<T> items; ListResult({ required this.pageSize, required this.currentPage, required this.totalItems, required this.totalPages, required this.items, }); factory ListResult.fromJson( Map<String, dynamic> json, T Function(Map<String, dynamic>) fromJsonItem, ) { var itemsList = json['items'] as List?; List<T> items = itemsList?.map((item) => fromJsonItem(item)).toList() ?? []; return ListResult( pageSize: json['pageSize'] as int, currentPage: json['currentPage'] as int, totalItems: json['totalItems'] as int, totalPages: json['totalPages'] as int, items: items, ); } }
2. 优化ApiResponse类
将data字段改为泛型类型,明确类型约束:
class ApiResponse<T> { final T? data; final APIStatus status; final String? responseId; final String? created; ApiResponse({ this.data, required this.status, this.responseId, this.created, }); factory ApiResponse.fromJson( Map<String, dynamic> json, T? Function(Map<String, dynamic>)? fromJson, ) { T? parsedData; if (json.containsKey('data') && json['data'] != null && fromJson != null) { parsedData = fromJson(json['data'] as Map<String, dynamic>); } return ApiResponse( data: parsedData, status: APIStatus.fromJson(json["status"] as Map<String, dynamic>), responseId: json['responseId'] as String?, created: json['created'] as String?, ); } }
3. 调整_returnResponse方法
让解析逻辑适配三种响应场景,同时保证类型安全:
typedef FromJson<T> = T Function(Map<String, dynamic>); dynamic _returnResponse<T>(Response response, FromJson<T>? create) { switch (response.statusCode) { case 200: final data = json.decode(response.body) as Map<String, dynamic>; final apiResponse = ApiResponse<T>.fromJson(data, create); if (!apiResponse.status.isSuccess) { throw ApiException(apiResponse.status.message); } return apiResponse.data; case 400: throw BadRequestException(response.body); case 401: case 403: throw UnauthorisedException(response.body); case 500: case 404: default: throw FetchDataException(response.statusCode.toString()); } }
4. 使用示例
- 获取单个数据(如金额):
Future<Amount?> getAmount() async { return await secureGet<Amount>( '/api/amount', (json) => Amount.fromJson(json), ); }
- 获取分页订单列表:
Future<ListResult<Order>?> getOrders() async { return await secureGet<ListResult<Order>>( '/api/orders', (json) => ListResult.fromJson(json, (itemJson) => Order.fromJson(itemJson)), ); }
- 无数据的接口调用:
Future<void> submitAction() async { await secureGet<void>( '/api/submit', null, // 无需解析数据 ); }
优化优势
- 消除重复代码:所有分页列表都用
ListResult<T>处理,无需为每个列表接口单独创建响应类。 - 类型安全:泛型约束确保解析后的数据类型正确,避免运行时类型错误。
- 扩展性强:新增其他响应结构时,只需添加对应泛型实体类,无需修改核心API处理逻辑。
内容的提问来源于stack exchange,提问作者Marc
相关产品推荐
相关产品推荐

