maps/mobile/lib/core/api/api_client.dart
2026-03-30 09:22:16 +02:00

115 lines
2.9 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../constants.dart';
/// Standard error format returned by the API.
class ApiError {
final String code;
final String message;
final int statusCode;
ApiError({
required this.code,
required this.message,
required this.statusCode,
});
factory ApiError.fromResponse(Response response) {
final data = response.data;
if (data is Map<String, dynamic> && data.containsKey('error')) {
final error = data['error'] as Map<String, dynamic>;
return ApiError(
code: error['code'] as String? ?? 'UNKNOWN',
message: error['message'] as String? ?? 'Unknown error',
statusCode: response.statusCode ?? 500,
);
}
return ApiError(
code: 'UNKNOWN',
message: 'Unexpected error',
statusCode: response.statusCode ?? 500,
);
}
@override
String toString() => 'ApiError($code: $message)';
}
class ApiException implements Exception {
final ApiError error;
ApiException(this.error);
@override
String toString() => error.toString();
}
class ApiClient {
late final Dio _dio;
String _baseUrl;
ApiClient({String? baseUrl})
: _baseUrl = baseUrl ?? AppConstants.defaultBackendUrl {
_dio = Dio(BaseOptions(
baseUrl: _baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
responseType: ResponseType.json,
));
_dio.interceptors.add(InterceptorsWrapper(
onError: (error, handler) {
if (error.response != null) {
final apiError = ApiError.fromResponse(error.response!);
handler.reject(DioException(
requestOptions: error.requestOptions,
response: error.response,
error: ApiException(apiError),
));
} else {
handler.next(error);
}
},
));
}
String get baseUrl => _baseUrl;
void updateBaseUrl(String newUrl) {
_baseUrl = newUrl;
_dio.options.baseUrl = newUrl;
}
/// GET request returning parsed JSON.
Future<dynamic> get(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
final response = await _dio.get(path, queryParameters: queryParameters);
return response.data;
}
/// GET request returning raw Response (for downloads with progress).
Future<Response> getRaw(
String path, {
Map<String, dynamic>? queryParameters,
ResponseType? responseType,
void Function(int, int)? onReceiveProgress,
Options? options,
}) async {
return _dio.get(
path,
queryParameters: queryParameters,
options: options ?? Options(responseType: responseType),
onReceiveProgress: onReceiveProgress,
);
}
/// The underlying Dio instance, for advanced use cases.
Dio get dio => _dio;
}
/// Riverpod provider for the API client.
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});