environment_monitoring_app/lib/services/base_api_service.dart
2025-08-04 15:11:24 +08:00

135 lines
5.2 KiB
Dart

import 'dart:convert';
import 'dart:io';
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';
class BaseApiService {
final String _baseUrl = 'https://dev14.pstw.com.my/v1';
// 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 {
final url = Uri.parse('$_baseUrl/$endpoint');
try {
final response = await http.get(url, headers: await _getJsonHeaders());
return _handleResponse(response);
} catch (e) {
debugPrint('GET request failed: $e');
return {'success': false, 'message': 'Network error: $e'};
}
}
// Generic POST request handler for JSON data
Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> body) async {
final url = Uri.parse('$_baseUrl/$endpoint');
try {
final response = await http.post(
url,
headers: await _getJsonHeaders(),
body: jsonEncode(body),
);
return _handleResponse(response);
} catch (e) {
debugPrint('POST request failed: $e');
return {'success': false, 'message': 'Network error: $e'};
}
}
// 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 {
final url = Uri.parse('$_baseUrl/$endpoint');
debugPrint('Starting multipart upload to: $url');
try {
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: Send all text fields as a single JSON string under the key 'data'.
// This is a common pattern for APIs that handle mixed file/data uploads and
// helps prevent issues where servers fail to parse individual fields.
if (fields.isNotEmpty) {
request.fields['data'] = jsonEncode(fields);
debugPrint('Fields added as a single JSON string under the key "data".');
}
// 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...');
var streamedResponse = await request.send();
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)
debugPrint('FATAL: An error occurred during multipart upload: $e');
debugPrint('Stack trace: $s');
return {
'success': false,
'message': 'Upload failed due to a critical error. This might be caused by low device memory or a network issue.'
};
}
}
// 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}'};
}
}
}