import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; /// Exception thrown when an API request fails class ApiException implements Exception { final String message; final int? statusCode; ApiException(this.message, [this.statusCode]); @override String toString() => 'ApiException: $message (Status: $statusCode)'; } /// HTTP API client for making network requests class ApiClient { final http.Client _httpClient; final String baseUrl; final Duration timeout; ApiClient({ http.Client? httpClient, required this.baseUrl, this.timeout = const Duration(seconds: 30), }) : _httpClient = httpClient ?? http.Client(); /// Make a GET request Future> get( String endpoint, { Map? headers, Map? queryParameters, }) async { final uri = _buildUri(endpoint, queryParameters); try { final response = await _httpClient .get( uri, headers: _buildHeaders(headers), ) .timeout(timeout); return _handleResponse(response); } on TimeoutException { throw ApiException('Request timeout'); } catch (e) { throw ApiException('Network error: $e'); } } /// Make a POST request Future> post( String endpoint, { Map? headers, Map? body, Map? queryParameters, }) async { final uri = _buildUri(endpoint, queryParameters); try { final response = await _httpClient .post( uri, headers: _buildHeaders(headers), body: body != null ? jsonEncode(body) : null, ) .timeout(timeout); return _handleResponse(response); } on TimeoutException { throw ApiException('Request timeout'); } catch (e) { throw ApiException('Network error: $e'); } } /// Make a PUT request Future> put( String endpoint, { Map? headers, Map? body, Map? queryParameters, }) async { final uri = _buildUri(endpoint, queryParameters); try { final response = await _httpClient .put( uri, headers: _buildHeaders(headers), body: body != null ? jsonEncode(body) : null, ) .timeout(timeout); return _handleResponse(response); } on TimeoutException { throw ApiException('Request timeout'); } catch (e) { throw ApiException('Network error: $e'); } } /// Make a DELETE request Future> delete( String endpoint, { Map? headers, Map? queryParameters, }) async { final uri = _buildUri(endpoint, queryParameters); try { final response = await _httpClient .delete( uri, headers: _buildHeaders(headers), ) .timeout(timeout); return _handleResponse(response); } on TimeoutException { throw ApiException('Request timeout'); } catch (e) { throw ApiException('Network error: $e'); } } /// Build URI with query parameters Uri _buildUri(String endpoint, Map? queryParameters) { final path = endpoint.startsWith('/') ? endpoint : '/$endpoint'; final uri = Uri.parse('$baseUrl$path'); if (queryParameters != null && queryParameters.isNotEmpty) { return uri.replace( queryParameters: queryParameters.map( (key, value) => MapEntry(key, value.toString()), ), ); } return uri; } /// Build request headers Map _buildHeaders(Map? customHeaders) { final headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', }; if (customHeaders != null) { headers.addAll(customHeaders); } return headers; } /// Handle HTTP response Map _handleResponse(http.Response response) { if (response.statusCode >= 200 && response.statusCode < 300) { if (response.body.isEmpty) { return {}; } try { return jsonDecode(response.body) as Map; } catch (e) { throw ApiException('Invalid JSON response'); } } // Handle error responses String errorMessage = 'Request failed'; try { final errorBody = jsonDecode(response.body); errorMessage = errorBody['message'] ?? errorBody['error'] ?? errorMessage; } catch (_) { errorMessage = response.body.isNotEmpty ? response.body : 'Status code: ${response.statusCode}'; } throw ApiException(errorMessage, response.statusCode); } /// Dispose resources void dispose() { _httpClient.close(); } }