Project

General

Profile

Task #28 » client.dart

Milad Khnefes, 02/01/2026 12:07 PM

 
import 'dart:async';
import 'dart:developer';
import '../constants/constants.dart';
import '../constants/urls.dart';
import 'package:http/http.dart' as http;

abstract class ApiClient {
int timeoutSeconds = 60;
http.Client client = http.Client();

Future<http.Response> get(
String partOfUrl, {
Map<String, dynamic>? queryParams,
bool isFirebaseCall = false,
bool isTesting = false,
}) async {
return sendRequest(
_buildUrlWithParams(partOfUrl, _mapToParams(queryParams)),
method: 'GET',
isFirebaseCall: isFirebaseCall,
retryOn401: true,
);
}

Future<http.Response> post(
String partOfUrl,
String body, {
bool isFirebaseCall = false,
bool isTesting = false,
}) async {
return sendRequest(
partOfUrl,
method: 'POST',
body: body,
isFirebaseCall: isFirebaseCall,
retryOn401: true,
);
}

Future<http.Response> put(
String partOfUrl,
String body, {
bool isFirebaseCall = false,
bool isTesting = false,
}) async {
return sendRequest(
partOfUrl,
method: 'PUT',
body: body,
isFirebaseCall: isFirebaseCall,
retryOn401: true,
);
}

Future<http.Response> delete(
String partOfUrl,
String body, {
bool isFirebaseCall = false,
}) async {
return sendRequest(
partOfUrl,
method: 'DELETE',
body: body,
isFirebaseCall: isFirebaseCall,
retryOn401: true,
);
}

Future<http.Response> sendRequest(
String partOfUrl, {
required String method,
String? body,
required bool retryOn401,
required bool isFirebaseCall,
}) async {
http.Response response = http.Response('{}', 408);

try {
final headers = {
'Accept-Encoding': 'gzip',
'Accept-Language': 'en',
if (method != 'GET') 'Content-Type': 'application/json',
};

final baseUrl = URLs.fullApiUrl;
final url = partOfUrl.contains(URLs.version)
? partOfUrl
: (baseUrl + partOfUrl);

response = await executeRequest(url, method, headers, body: body);

// Handle non-success codes
if (!Constants.isSuccessCode(response.statusCode)) {
Constants.logger.w(
'$method $url failed with ${response.statusCode}. Body: ${response.body}',
);
}

return response;
} catch (e) {
return response;
}
}

Future<http.Response> executeRequest(
String url,
String method,
Map<String, String> headers, {
String? body = '{}',
}) {
if (body != null && body.length > 200) {
log('$method $url\nBody: $body');
} else {
Constants.logger.f('$method $url\nBody: $body');
}

final uri = Uri.parse(url);
final timeout = Duration(seconds: timeoutSeconds);

switch (method.toUpperCase()) {
case 'GET':
return client.get(uri, headers: headers).timeout(timeout);
case 'POST':
return client.post(uri, headers: headers, body: body).timeout(timeout);
case 'PUT':
return client.put(uri, headers: headers, body: body).timeout(timeout);
case 'DELETE':
return client
.delete(uri, headers: headers, body: body)
.timeout(timeout);
default:
throw UnsupportedError('HTTP method $method not supported');
}
}

List<String>? _mapToParams(Map<String, dynamic>? params) {
return params?.entries
.map(
(e) => e.value != null && e.value.toString().isNotEmpty
? '${e.key}=${e.value}'
: '',
)
.toList();
}

String _buildUrlWithParams(String url, List<String>? params) {
if (params != null) {
params = params.where((p) => p.isNotEmpty).toList();
}
return params?.isEmpty ?? true
? url
: '$url${url.contains('?') ? '&' : '?'}${params!.join('&')}';
}
}
(11-11/12)