From 71c1610c0e614256b5bcd5eb54d38baf7a0ad0b3 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 25 Feb 2026 10:05:41 -0500 Subject: [PATCH] feat: Implement ApiService with Dio for standardized API requests and responses using ApiResponse entity. --- apps/mobile/packages/core/lib/core.dart | 1 + .../core/lib/src/services/api_service.dart | 135 ++++++++++++++++++ apps/mobile/packages/core/pubspec.yaml | 1 + .../packages/domain/lib/krow_domain.dart | 3 + .../services/api_service/api_response.dart | 22 +++ 5 files changed, 162 insertions(+) create mode 100644 apps/mobile/packages/core/lib/src/services/api_service.dart create mode 100644 apps/mobile/packages/domain/lib/src/entities/core/services/api_service/api_response.dart diff --git a/apps/mobile/packages/core/lib/core.dart b/apps/mobile/packages/core/lib/core.dart index 0aa4de1d..317bfcb7 100644 --- a/apps/mobile/packages/core/lib/core.dart +++ b/apps/mobile/packages/core/lib/core.dart @@ -8,3 +8,4 @@ export 'src/presentation/mixins/bloc_error_handler.dart'; export 'src/presentation/observers/core_bloc_observer.dart'; export 'src/config/app_config.dart'; export 'src/routing/routing.dart'; +export 'src/services/api_service.dart'; diff --git a/apps/mobile/packages/core/lib/src/services/api_service.dart b/apps/mobile/packages/core/lib/src/services/api_service.dart new file mode 100644 index 00000000..5608b500 --- /dev/null +++ b/apps/mobile/packages/core/lib/src/services/api_service.dart @@ -0,0 +1,135 @@ +import 'package:dio/dio.dart'; +import 'package:krow_domain/krow_domain.dart'; + +/// A service that handles HTTP communication using the [Dio] client. +/// +/// This class provides a wrapper around [Dio]'s methods to handle +/// response parsing and error handling in a consistent way. +class ApiService { + /// Creates an [ApiService] with the given [Dio] instance. + ApiService(this._dio); + + /// The underlying [Dio] client used for network requests. + final Dio _dio; + + /// Performs a GET request to the specified [endpoint]. + Future get( + String endpoint, { + Map? params, + }) async { + try { + final Response response = await _dio.get( + endpoint, + queryParameters: params, + ); + return _handleResponse(response); + } on DioException catch (e) { + return _handleError(e); + } + } + + /// Performs a POST request to the specified [endpoint]. + Future post( + String endpoint, { + dynamic data, + Map? params, + }) async { + try { + final Response response = await _dio.post( + endpoint, + data: data, + queryParameters: params, + ); + return _handleResponse(response); + } on DioException catch (e) { + return _handleError(e); + } + } + + /// Performs a PUT request to the specified [endpoint]. + Future put( + String endpoint, { + dynamic data, + Map? params, + }) async { + try { + final Response response = await _dio.put( + endpoint, + data: data, + queryParameters: params, + ); + return _handleResponse(response); + } on DioException catch (e) { + return _handleError(e); + } + } + + /// Performs a PATCH request to the specified [endpoint]. + Future patch( + String endpoint, { + dynamic data, + Map? params, + }) async { + try { + final Response response = await _dio.patch( + endpoint, + data: data, + queryParameters: params, + ); + return _handleResponse(response); + } on DioException catch (e) { + return _handleError(e); + } + } + + /// Extracts [ApiResponse] from a successful [Response]. + ApiResponse _handleResponse(Response response) { + if (response.data is Map) { + final Map body = response.data as Map; + return ApiResponse( + code: + body['code']?.toString() ?? + response.statusCode?.toString() ?? + 'unknown', + message: body['message']?.toString() ?? 'Success', + data: body['data'], + errors: _parseErrors(body['errors']), + ); + } + return ApiResponse( + code: response.statusCode?.toString() ?? '200', + message: 'Success', + data: response.data, + ); + } + + /// Extracts [ApiResponse] from a [DioException]. + ApiResponse _handleError(DioException e) { + if (e.response?.data is Map) { + final Map body = + e.response!.data as Map; + return ApiResponse( + code: + body['code']?.toString() ?? + e.response?.statusCode?.toString() ?? + 'error', + message: body['message']?.toString() ?? e.message ?? 'Error occurred', + data: body['data'], + errors: _parseErrors(body['errors']), + ); + } + return ApiResponse( + code: e.response?.statusCode?.toString() ?? 'error', + message: e.message ?? 'Unknown error', + errors: {'exception': e.type.toString()}, + ); + } + + /// Helper to parse the errors map from various possible formats. + Map _parseErrors(dynamic errors) { + if (errors is Map) { + return Map.from(errors); + } + return const {}; + } +} diff --git a/apps/mobile/packages/core/pubspec.yaml b/apps/mobile/packages/core/pubspec.yaml index 1f36d274..ec28672d 100644 --- a/apps/mobile/packages/core/pubspec.yaml +++ b/apps/mobile/packages/core/pubspec.yaml @@ -21,3 +21,4 @@ dependencies: flutter_bloc: ^8.1.0 equatable: ^2.0.8 flutter_modular: ^6.4.1 + dio: ^5.9.1 diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index 9c67574f..ba7940b2 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -6,6 +6,9 @@ /// Note: Repository Interfaces are now located in their respective Feature packages. library; +// Core +export 'src/entities/core/services/api_service/api_response.dart'; + // Users & Membership export 'src/entities/users/user.dart'; export 'src/entities/users/staff.dart'; diff --git a/apps/mobile/packages/domain/lib/src/entities/core/services/api_service/api_response.dart b/apps/mobile/packages/domain/lib/src/entities/core/services/api_service/api_response.dart new file mode 100644 index 00000000..ee3ee6f1 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/core/services/api_service/api_response.dart @@ -0,0 +1,22 @@ +/// Represents a standardized response from the API. +class ApiResponse { + /// Creates an [ApiResponse]. + const ApiResponse({ + required this.code, + required this.message, + this.data, + this.errors = const {}, + }); + + /// The response code (e.g., '200', '404', or custom error code). + final String code; + + /// A descriptive message about the response. + final String message; + + /// The payload returned by the API. + final dynamic data; + + /// A map of field-specific error messages, if any. + final Map errors; +}