major refactor
This commit is contained in:
@@ -6,10 +6,16 @@ typedef UnauthorizedHandler = Future<void> Function();
|
||||
|
||||
class ApiService {
|
||||
final String baseUrl;
|
||||
final http.Client _client;
|
||||
final Duration timeout;
|
||||
TokenProvider? _getToken;
|
||||
UnauthorizedHandler? _onUnauthorized;
|
||||
|
||||
ApiService({required this.baseUrl});
|
||||
ApiService({
|
||||
required this.baseUrl,
|
||||
http.Client? client,
|
||||
this.timeout = const Duration(seconds: 30),
|
||||
}) : _client = client ?? http.Client();
|
||||
|
||||
void setTokenProvider(TokenProvider provider) {
|
||||
_getToken = provider;
|
||||
@@ -19,6 +25,10 @@ class ApiService {
|
||||
_onUnauthorized = handler;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_client.close();
|
||||
}
|
||||
|
||||
Map<String, String> _buildHeaders(Map<String, String>? extra) {
|
||||
final token = _getToken?.call();
|
||||
final headers = {'accept': 'application/json', ...?extra};
|
||||
@@ -29,10 +39,12 @@ class ApiService {
|
||||
}
|
||||
|
||||
Future<dynamic> get(String endpoint, {Map<String, String>? headers}) async {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
);
|
||||
final response = await _client
|
||||
.get(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
)
|
||||
.timeout(timeout);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -42,23 +54,27 @@ class ApiService {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final hasBody = data != null;
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
);
|
||||
final response = await _client
|
||||
.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
)
|
||||
.timeout(timeout);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
Future<dynamic> postForm(String endpoint, Map<String, String> data) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'accept': 'application/json',
|
||||
}),
|
||||
body: data, // http package handles form-encoding for Map<String, String>
|
||||
);
|
||||
final response = await _client
|
||||
.post(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'accept': 'application/json',
|
||||
}),
|
||||
body: data, // http package handles form-encoding for Map<String, String>
|
||||
)
|
||||
.timeout(timeout);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -68,11 +84,13 @@ class ApiService {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final hasBody = data != null;
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
);
|
||||
final response = await _client
|
||||
.put(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(hasBody ? _jsonHeaders(headers) : headers),
|
||||
body: hasBody ? jsonEncode(data) : null,
|
||||
)
|
||||
.timeout(timeout);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -80,10 +98,12 @@ class ApiService {
|
||||
String endpoint, {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final response = await http.delete(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
);
|
||||
final response = await _client
|
||||
.delete(
|
||||
Uri.parse('$baseUrl$endpoint'),
|
||||
headers: _buildHeaders(headers),
|
||||
)
|
||||
.timeout(timeout);
|
||||
return _processResponse(response);
|
||||
}
|
||||
|
||||
@@ -92,7 +112,7 @@ class ApiService {
|
||||
}
|
||||
|
||||
Future<dynamic> _processResponse(http.Response res) async {
|
||||
final body = res.body.isNotEmpty ? jsonDecode(res.body) : null;
|
||||
final body = _decodeBody(res);
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return body;
|
||||
}
|
||||
@@ -103,4 +123,23 @@ class ApiService {
|
||||
|
||||
throw Exception('API error ${res.statusCode}: $body');
|
||||
}
|
||||
|
||||
dynamic _decodeBody(http.Response res) {
|
||||
if (res.body.isEmpty) return null;
|
||||
|
||||
final contentType = res.headers['content-type'] ?? '';
|
||||
final shouldTryJson = contentType.contains('application/json') ||
|
||||
contentType.contains('+json') ||
|
||||
res.body.trimLeft().startsWith('{') ||
|
||||
res.body.trimLeft().startsWith('[');
|
||||
|
||||
if (!shouldTryJson) return res.body;
|
||||
|
||||
try {
|
||||
return jsonDecode(res.body);
|
||||
} catch (_) {
|
||||
// Avoid turning a server-side error body into a client-side crash.
|
||||
return res.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user