Files
superport/lib/data/datasources/remote/api_client.dart
JiWoong Sul 198aac6525
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
test: 통합 테스트 오류 및 경고 수정
- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정
- TestDataGenerator 제거하고 직접 객체 생성으로 변경
- 모델 필드명 및 타입 불일치 수정
- 불필요한 Either 패턴 사용 제거
- null safety 관련 이슈 해결

수정된 파일:
- test/integration/screens/company_integration_test.dart
- test/integration/screens/equipment_integration_test.dart
- test/integration/screens/user_integration_test.dart
- test/integration/screens/login_integration_test.dart
2025-08-05 20:24:05 +09:00

297 lines
8.3 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../../../core/config/environment.dart';
import 'interceptors/auth_interceptor.dart';
import 'interceptors/error_interceptor.dart';
import 'interceptors/logging_interceptor.dart';
import 'interceptors/response_interceptor.dart';
/// API 클라이언트 클래스
class ApiClient {
late final Dio _dio;
static ApiClient? _instance;
factory ApiClient() {
_instance ??= ApiClient._internal();
return _instance!;
}
ApiClient._internal() {
try {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] 초기화 시작');
}
_dio = Dio(_baseOptions);
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] Dio 인스턴스 생성 완료');
debugPrint('[ApiClient] Base URL: ${_dio.options.baseUrl}');
debugPrint('[ApiClient] Connect Timeout: ${_dio.options.connectTimeout}');
debugPrint('[ApiClient] Receive Timeout: ${_dio.options.receiveTimeout}');
}
_setupInterceptors();
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] 인터셉터 설정 완료');
}
} catch (e, stackTrace) {
if (kDebugMode) {
debugPrint('[ApiClient] ⚠️ 에러 발생: $e');
debugPrint('[ApiClient] Stack trace: $stackTrace');
}
// 기본값으로 초기화
_dio = Dio(BaseOptions(
baseUrl: 'http://43.201.34.104:8080/api/v1',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
_setupInterceptors();
if (kDebugMode) {
debugPrint('[ApiClient] 기본값으로 초기화 완료');
}
}
}
/// Dio 인스턴스 getter
Dio get dio => _dio;
/// 기본 옵션 설정
BaseOptions get _baseOptions {
try {
return BaseOptions(
baseUrl: Environment.apiBaseUrl,
connectTimeout: Duration(milliseconds: Environment.apiTimeout),
receiveTimeout: Duration(milliseconds: Environment.apiTimeout),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
validateStatus: (status) {
return status != null && status < 500;
},
);
} catch (e) {
// Environment가 초기화되지 않은 경우 기본값 사용
return BaseOptions(
baseUrl: 'http://43.201.34.104:8080/api/v1',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
validateStatus: (status) {
return status != null && status < 500;
},
);
}
}
/// 인터셉터 설정
void _setupInterceptors() {
_dio.interceptors.clear();
// 로깅 인터셉터 (개발 환경에서만) - 가장 먼저 추가하여 모든 요청/응답을 로깅
try {
if (Environment.enableLogging && kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
}
} catch (e) {
// Environment 접근 실패 시 디버그 모드에서만 로깅 활성화
if (kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
}
}
// 인증 인터셉터 - 요청에 토큰 추가 및 401 처리
_dio.interceptors.add(AuthInterceptor(_dio));
// 응답 정규화 인터셉터 - 성공 응답을 일관된 형식으로 변환
_dio.interceptors.add(ResponseInterceptor());
// 에러 처리 인터셉터 - 마지막에 추가하여 모든 에러를 캐치
_dio.interceptors.add(ErrorInterceptor());
}
/// 토큰 업데이트
void updateAuthToken(String token) {
_dio.options.headers['Authorization'] = 'Bearer $token';
}
/// 토큰 제거
void removeAuthToken() {
_dio.options.headers.remove('Authorization');
}
/// GET 요청
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) {
return _dio.get<T>(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onReceiveProgress: onReceiveProgress,
);
}
/// POST 요청
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] POST 요청 시작: $path');
debugPrint('[ApiClient] 요청 데이터: $data');
}
return _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
).then((response) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] POST 응답 수신: ${response.statusCode}');
}
return response;
}).catchError((error) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[ApiClient] POST 에러 발생: $error');
if (error is DioException) {
debugPrint('[ApiClient] DioException 타입: ${error.type}');
debugPrint('[ApiClient] DioException 메시지: ${error.message}');
debugPrint('[ApiClient] DioException 에러: ${error.error}');
}
}
throw error;
});
}
/// PUT 요청
Future<Response<T>> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
return _dio.put<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
}
/// PATCH 요청
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
return _dio.patch<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
}
/// DELETE 요청
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) {
return _dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
}
/// 파일 업로드
Future<Response<T>> uploadFile<T>(
String path, {
required String filePath,
required String fileFieldName,
Map<String, dynamic>? additionalData,
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
final fileName = filePath.split('/').last;
final formData = FormData.fromMap({
fileFieldName: await MultipartFile.fromFile(
filePath,
filename: fileName,
),
...?additionalData,
});
return _dio.post<T>(
path,
data: formData,
options: Options(
headers: {
'Content-Type': 'multipart/form-data',
},
),
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
}
/// 파일 다운로드
Future<Response> downloadFile(
String path, {
required String savePath,
ProgressCallback? onReceiveProgress,
CancelToken? cancelToken,
Map<String, dynamic>? queryParameters,
}) {
return _dio.download(
path,
savePath,
queryParameters: queryParameters,
onReceiveProgress: onReceiveProgress,
cancelToken: cancelToken,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
),
);
}
}