165 lines
7.0 KiB
Dart
165 lines
7.0 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:async/async.dart'; // Used for TimeoutException
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:environment_monitoring_app/auth_provider.dart';
|
|
// --- ADDED: Import for the service that manages active server configurations ---
|
|
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
|
// --- ADDED: Import for the new service that manages the retry queue ---
|
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
|
|
|
|
|
class BaseApiService {
|
|
// --- ADDED: An instance of the service to get the active URL dynamically ---
|
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
|
|
|
// --- REMOVED: This creates an infinite loop with RetryService ---
|
|
// final RetryService _retryService = RetryService();
|
|
|
|
|
|
// Private helper to construct headers with the auth token
|
|
Future<Map<String, String>> _getHeaders() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final String? token = prefs.getString(AuthProvider.tokenKey);
|
|
// For multipart requests, 'Content-Type' is set by the http client.
|
|
// We only need Authorization here.
|
|
return {
|
|
if (token != null) 'Authorization': 'Bearer $token',
|
|
};
|
|
}
|
|
|
|
// Private helper for JSON headers
|
|
Future<Map<String, String>> _getJsonHeaders() async {
|
|
final headers = await _getHeaders();
|
|
headers['Content-Type'] = 'application/json';
|
|
return headers;
|
|
}
|
|
|
|
// Generic GET request handler
|
|
Future<Map<String, dynamic>> get(String endpoint) async {
|
|
try {
|
|
// --- MODIFIED: Fetches the active base URL before making the request ---
|
|
final baseUrl = await _serverConfigService.getActiveApiUrl();
|
|
final url = Uri.parse('$baseUrl/$endpoint');
|
|
final response = await http.get(url, headers: await _getJsonHeaders())
|
|
.timeout(const Duration(seconds: 60)); // --- MODIFIED: Added 60 second timeout ---
|
|
return _handleResponse(response);
|
|
} catch (e) {
|
|
debugPrint('GET request failed: $e');
|
|
return {'success': false, 'message': 'Network error or timeout: $e'};
|
|
}
|
|
}
|
|
|
|
// Generic POST request handler for JSON data
|
|
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> body) async {
|
|
try {
|
|
// --- MODIFIED: Fetches the active base URL before making the request ---
|
|
final baseUrl = await _serverConfigService.getActiveApiUrl();
|
|
final url = Uri.parse('$baseUrl/$endpoint');
|
|
final response = await http.post(
|
|
url,
|
|
headers: await _getJsonHeaders(),
|
|
body: jsonEncode(body),
|
|
).timeout(const Duration(seconds: 60)); // --- MODIFIED: Added 60 second timeout ---
|
|
return _handleResponse(response);
|
|
} catch (e) {
|
|
// --- MODIFIED: Create a local instance of RetryService to break the circular dependency ---
|
|
final retryService = RetryService();
|
|
debugPrint('POST request to $endpoint failed, queueing for retry. Error: $e');
|
|
await retryService.addApiToQueue(
|
|
endpoint: endpoint,
|
|
method: 'POST',
|
|
body: body,
|
|
);
|
|
return {'success': false, 'message': 'Request failed and has been queued for manual retry.'};
|
|
}
|
|
}
|
|
|
|
// Generic handler for multipart (file upload) requests
|
|
Future<Map<String, dynamic>> postMultipart({
|
|
required String endpoint,
|
|
required Map<String, String> fields,
|
|
required Map<String, File> files,
|
|
}) async {
|
|
try {
|
|
// --- MODIFIED: Fetches the active base URL before making the request ---
|
|
final baseUrl = await _serverConfigService.getActiveApiUrl();
|
|
final url = Uri.parse('$baseUrl/$endpoint');
|
|
debugPrint('Starting multipart upload to: $url');
|
|
|
|
var request = http.MultipartRequest('POST', url);
|
|
|
|
// Get and add headers (Authorization token)
|
|
final headers = await _getHeaders();
|
|
request.headers.addAll(headers);
|
|
debugPrint('Headers added to multipart request.');
|
|
|
|
// --- CORRECTED ---
|
|
// This adds each field directly to the request body, which is the standard
|
|
// for multipart/form-data and matches what the PHP backend expects.
|
|
if (fields.isNotEmpty) {
|
|
request.fields.addAll(fields);
|
|
debugPrint('Fields added directly to multipart request: $fields');
|
|
}
|
|
|
|
// Add files
|
|
for (var entry in files.entries) {
|
|
debugPrint('Adding file: ${entry.key}, path: ${entry.value.path}');
|
|
request.files.add(await http.MultipartFile.fromPath(
|
|
entry.key,
|
|
entry.value.path,
|
|
filename: path.basename(entry.value.path),
|
|
));
|
|
}
|
|
debugPrint('${files.length} files added to the request.');
|
|
|
|
debugPrint('Sending multipart request...');
|
|
// --- MODIFIED: Added 60 second timeout ---
|
|
var streamedResponse = await request.send().timeout(const Duration(seconds: 60));
|
|
debugPrint('Received response with status code: ${streamedResponse.statusCode}');
|
|
|
|
final responseBody = await streamedResponse.stream.bytesToString();
|
|
// We create a standard http.Response to use our standard handler
|
|
return _handleResponse(http.Response(responseBody, streamedResponse.statusCode));
|
|
|
|
} catch (e, s) { // Catching both Exception and Error (e.g., OutOfMemoryError)
|
|
// --- MODIFIED: Create a local instance of RetryService to break the circular dependency ---
|
|
final retryService = RetryService();
|
|
debugPrint('Multipart request to $endpoint failed, queueing for retry. Error: $e');
|
|
debugPrint('Stack trace: $s');
|
|
await retryService.addApiToQueue(
|
|
endpoint: endpoint,
|
|
method: 'POST_MULTIPART',
|
|
fields: fields,
|
|
files: files,
|
|
);
|
|
return {'success': false, 'message': 'Upload failed and has been queued for manual retry.'};
|
|
}
|
|
}
|
|
|
|
// Centralized response handling
|
|
Map<String, dynamic> _handleResponse(http.Response response) {
|
|
debugPrint('Handling response. Status: ${response.statusCode}, Body: ${response.body}');
|
|
try {
|
|
final Map<String, dynamic> responseData = jsonDecode(response.body);
|
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
// Assuming the API returns a 'status' field in the JSON body
|
|
if (responseData['status'] == 'success' || responseData['success'] == true) {
|
|
return {'success': true, 'data': responseData['data'], 'message': responseData['message']};
|
|
} else {
|
|
return {'success': false, 'message': responseData['message'] ?? 'An unknown API error occurred.'};
|
|
}
|
|
} else {
|
|
// Handle server errors (4xx, 5xx)
|
|
return {'success': false, 'message': responseData['message'] ?? 'Server error: ${response.statusCode}'};
|
|
}
|
|
} catch (e) {
|
|
// Handle cases where the response body is not valid JSON
|
|
debugPrint('Failed to parse server response: $e');
|
|
return {'success': false, 'message': 'Failed to parse server response. Body: ${response.body}'};
|
|
}
|
|
}
|
|
} |