-
Notifications
You must be signed in to change notification settings - Fork 2
CallAdapter 마이그레이션
BuNa edited this page Sep 13, 2023
·
6 revisions
CallAdapter 적용에 따라, 프로젝트 반영을 위해 부나
에 의해 작성되었습니다.
기존 handleApi { ... }를 CallAdapter로 마이그레이션 하였습니다. 그에 따라 코드를 간략하게 소개합니다.
CallAdapter로 마이그레이션하면서 총 4개
의 상태
가 생겼습니다.
// 200대 응답 코드를 받은 경우
data class Success<T : Any>(val data: T) : ApiResponse<T>()
// 200대 응답 코드가 아닌 경우, 서버와의 통신은 성공함
data class Failure(val responseCode: Int, val message: String?) : ApiResponse<Nothing>()
// 서버와의 통신조차 하지 못한 경우
object NetworkError : ApiResponse<Nothing>()
// 그 외 기타 오류
data class Unexpected(val error: Throwable?) : ApiResponse<Nothing>()
위 4가지 상태는 ApiResonse를 상속받고 있습니다.
ApiResponse는 ApiModel(Response) -> Data Model로 쉽게 변환할 수 있도록 map {...}
메서드를 제공합니다.
sealed class ApiResponse<out T : Any> {
fun <R : Any> map(transform: (T) -> R): ApiResponse<R> = when (this) {
is Success -> Success(transform(data))
is Failure -> Failure(responseCode, message)
is NetworkError -> NetworkError
is Unexpected -> Unexpected(error)
}
}
기존 코드를 마이그레이션 하는 방법을 소개합니다.
- Service interface 반환 타입 변경
// Before
interface ActivityService {
@GET("/activities")
suspend fun getActivities(): Response<List<ActivitiesApiModel>>
}
// After
interface ActivityService {
@GET("/activities")
suspend fun getActivities(): ApiResponse<List<ActivitiesApiModel>>
}
- Repository 반환 타입 변경 및 handleApi {...} 함수 제거
// Before
override suspend fun getActivities(): ApiResult<List<Activity>> = withContext(dispatcher) {
handleApi(
execute = { activityService.getActivities() },
mapToDomain = List<ActivitiesApiModel>::toData,
)
}
// After
override suspend fun getActivities(): ApiResponse<List<Activity>> = withContext(dispatcher) {
activityService
.getActivities()
.map(List<ActivitiesApiModel>::toData)
}
handleApi에 lambda를 2개 넘겨줘서 가독성을 낮추는 문제 해결
- ViewModel의 분기 처리
// Before - 분기 처리 모호함
private fun fetchActivities(): Job = viewModelScope.launch {
when (val result = activityRepository.getActivities()) {
is ApiSuccess -> _activities.postValue(OnboardingUiState.from(activitiesResult.data))
is ApiError, is ApiException ->
_activities.postValue(activities.value.copy(isLoadingActivitiesFailed = true))
}
}
// After
private fun fetchActivities(): Job = viewModelScope.launch {
when (val result = activityRepository.getActivities()) {
is Success -> _activities.postValue(OnboardingUiState.from(result.data))
is NetworkError -> updateToNetworkErrorState()
is Unexpected -> updateToUnknownErrorState()
is Failure -> when (result.responseCode) {
401 -> // 로그인 화면으로 이동
else -> // updateToUnknownErrorState()
}
}
}
NetworkError와 Unexpected를 분리하여 인터넷 연결 상태에 대한 분기 처리를 확실히 할 수 있게 되었습니다.
+) 인터넷 연결 상태 불량과 같은 모든 ViewModel에서 공통으로 사용하는 UiState와 전이 함수는 BaseViewModel로 분리하면 지금보다도 보일러 플레이트 코드를 확실히 줄일 수 있을 것으로 예상됩니다.