① 배운 것
Flutter 애플리케이션에서 API 통신은 필수적인 부분입니다. 하지만 API 호출에는 항상 성공과 실패의 가능성이 있으며, 이를 매번 일관되게 처리하는 것은 번거로울 수 있습니다. 오늘은 이 문제를 해결하기 위해 개발한 SafeApiCall 유틸리티를 소개하고자 합니다.
서버에서 다음과 같은 일관된 형식으로 응답을 제공한다고 가정해봅시다:
{
"success": true,
"code": 0,
"message": "string",
"data": { ... }
}
이러한 응답을 매번 처리하기 위해 중복된 코드를 작성하는 것은 비효율적이며, 오류가 발생할 가능성도 높아집니다. 이를 해결하기 위해 SafeApiCall 유틸리티를 만들었습니다.
먼저, API 응답을 래핑할 ResultWrapper 클래스를 정의합니다:
sealed class ResultWrapper<T> {
const ResultWrapper();
}
final class Success<T> extends ResultWrapper<T> {
final T data;
const Success(this.data);
}
final class Error<T> extends ResultWrapper<T> {
final String message;
const Error(this.message);
}
그리고 SafeApiCall 클래스를 다음과 같이 구현합니다:
class SafeApiCall {
SafeApiCall._privateConstructor();
static final SafeApiCall _instance = SafeApiCall._privateConstructor();
factory SafeApiCall() {
return _instance;
}
Future<ResultWrapper<T>> callApi<T>(Future<BaseResponse<T>> apiCall) async {
try {
final baseResponse = await apiCall;
if (baseResponse.success) {
return Success(baseResponse.data as T);
} else {
return Error(baseResponse.message ?? "오류가 발생했습니다.");
}
} catch (e) {
debugPrint('Error: $e');
return const Error('오류가 발생했습니다.');
}
}
}
싱글톤 패턴: SafeApiCall은 싱글톤 패턴을 사용하여 구현되어 있어, 애플리케이션 전체에서 단일 인스턴스를 공유합니다.
제네릭 사용: callApi 메서드는 제네릭을 사용하여 다양한 타입의 API 응답을 처리할 수 있습니다.
에러 처리: try-catch 블록을 사용하여 네트워크 오류나 기타 예외 상황을 일관되게 처리합니다.
타입 안전성: ResultWrapper<T>를 사용하여 컴파일 시점에 타입 안전성을 보장합니다.
getHistory({required int cursor}) async {
final response = await SafeApiCall()
.callApi(repository.getHistory(cursor: cursor));
switch (response) {
case Success():
state = response.data.history;
break;
case Error():
// 에러 처리
break;
}
}
switch 문을 사용하여 성공과 실패 케이스를 명확히 구분할 수 있습니다.SafeApiCall 내부만 수정하면 됩니다.SafeApiCall 유틸리티는 API 통신과 관련된 여러 문제를 효과적으로 해결합니다. 이를 통해 코드의 일관성, 가독성, 유지보수성을 크게 향상시킬 수 있습니다. 앞으로 프로젝트가 커지고 API 호출이 더 복잡해지더라도, SafeApiCall을 통해 안정적이고 효율적인 API 통신을 유지할 수 있을 것입니다.
② 회고 (restropective)
에러 처리를 좀 더 구체적으로(네트워크 에러, 서버에러, 기타 일반적인 에러 등등) 하면 더 safeApiCall을 쓰는 의미가 더 와닿을것 같다.
③ 개선을 위한 방법