refactor: 코드베이스 정리 및 에러 처리 개선

- API 클라이언트 및 인증 인터셉터 에러 처리 강화
- 의존성 주입 실패 시에도 앱 실행 가능하도록 개선
- 사용하지 않는 레거시 UI 컴포넌트 및 화면 제거
- pubspec.yaml 의존성 업데이트

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-25 18:15:21 +09:00
parent 71b7b7f40b
commit ad2c699ff7
39 changed files with 193 additions and 4134 deletions

View File

@@ -9,31 +9,69 @@ import 'interceptors/logging_interceptor.dart';
class ApiClient {
late final Dio _dio;
static final ApiClient _instance = ApiClient._internal();
static ApiClient? _instance;
factory ApiClient() => _instance;
factory ApiClient() {
_instance ??= ApiClient._internal();
return _instance!;
}
ApiClient._internal() {
_dio = Dio(_baseOptions);
_setupInterceptors();
try {
_dio = Dio(_baseOptions);
_setupInterceptors();
} catch (e) {
print('Error while creating ApiClient');
print('Stack trace:');
print(StackTrace.current);
// 기본값으로 초기화
_dio = Dio(BaseOptions(
baseUrl: 'http://localhost:8080/api/v1',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
_setupInterceptors();
}
}
/// Dio 인스턴스 getter
Dio get dio => _dio;
/// 기본 옵션 설정
BaseOptions get _baseOptions => 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;
},
);
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://localhost: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() {
@@ -46,8 +84,15 @@ class ApiClient {
_dio.interceptors.add(ErrorInterceptor());
// 로깅 인터셉터 (개발 환경에서만)
if (Environment.enableLogging && kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
try {
if (Environment.enableLogging && kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
}
} catch (e) {
// Environment 접근 실패 시 디버그 모드에서만 로깅 활성화
if (kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
}
}
}

View File

@@ -5,10 +5,16 @@ import '../../../../services/auth_service.dart';
/// 인증 인터셉터
class AuthInterceptor extends Interceptor {
late final AuthService _authService;
AuthService? _authService;
AuthInterceptor() {
_authService = GetIt.instance<AuthService>();
AuthService? get authService {
try {
_authService ??= GetIt.instance<AuthService>();
return _authService;
} catch (e) {
print('Failed to get AuthService in AuthInterceptor: $e');
return null;
}
}
@override
@@ -23,10 +29,13 @@ class AuthInterceptor extends Interceptor {
}
// 저장된 액세스 토큰 가져오기
final accessToken = await _authService.getAccessToken();
if (accessToken != null) {
options.headers['Authorization'] = 'Bearer $accessToken';
final service = authService;
if (service != null) {
final accessToken = await service.getAccessToken();
if (accessToken != null) {
options.headers['Authorization'] = 'Bearer $accessToken';
}
}
handler.next(options);
@@ -39,36 +48,39 @@ class AuthInterceptor extends Interceptor {
) async {
// 401 Unauthorized 에러 처리
if (err.response?.statusCode == 401) {
// 토큰 갱신 시도
final refreshResult = await _authService.refreshToken();
final refreshSuccess = refreshResult.fold(
(failure) => false,
(tokenResponse) => true,
);
if (refreshSuccess) {
// 새로운 토큰으로 원래 요청 재시도
try {
final newAccessToken = await _authService.getAccessToken();
if (newAccessToken != null) {
err.requestOptions.headers['Authorization'] = 'Bearer $newAccessToken';
final service = authService;
if (service != null) {
// 토큰 갱신 시도
final refreshResult = await service.refreshToken();
final refreshSuccess = refreshResult.fold(
(failure) => false,
(tokenResponse) => true,
);
if (refreshSuccess) {
// 새로운 토큰으로 원래 요청 재시도
try {
final newAccessToken = await service.getAccessToken();
final response = await Dio().fetch(err.requestOptions);
handler.resolve(response);
if (newAccessToken != null) {
err.requestOptions.headers['Authorization'] = 'Bearer $newAccessToken';
final response = await Dio().fetch(err.requestOptions);
handler.resolve(response);
return;
}
} catch (e) {
// 재시도 실패
handler.next(err);
return;
}
} catch (e) {
// 재시도 실패
handler.next(err);
return;
}
// 토큰 갱신 실패 시 로그인 화면으로 이동
await service.clearSession();
// TODO: Navigate to login screen
}
// 토큰 갱신 실패 시 로그인 화면으로 이동
await _authService.clearSession();
// TODO: Navigate to login screen
}
handler.next(err);