[Kotlin/Android] Sealed Class를 이용하여 API 응답에 대한 결과 처리를 해보자

minH_·2024년 3월 5일

프로젝트 진행 중 서버와 통신할 때 예외 처리에 대한 중복되는 코드가 상당히 늘어났다. 따라서 API의 결과를 Sealed Class로 감싸 예외에 대한 분기를 처리하려고 한다.

sealed class ApiResult<out T> {
    data class Success<out T>(val value: T) : ApiResult<T>()
    data class Error(
        val exception: Throwable? = null,
        val message: String? = ""
    ) : ApiResult<Nothing>()
    object Empty : ApiResult<Nothing>()
}

위 ApiResult Class를 Repository에서 분기해보자.

suspend fun getSchools() = runCatching {
	ApiService.getSchools()
    
}.fold(
	onSuccess = { response ->
        if (response.isSuccessful && response.body() != null) {
            // 정상 응답
            Success(response.body())
        }
        else {
            // 실패 응답 (서버와 통신은 성공)
            Failure(response.code(), response.message())

        },
        // 서버와 통신 실패
        onFailure = { exception ->
            NetworkError()
        }
    },
)
    	
    
    

하지만 모든 repository에서 위와 같은 응답에 대한 분기 처리를 해야하기 때문에 보일러플레이트 코드가 늘어나 마음에 들지 않았다. 서버에 대한 모든 응답을 ApiResult Class를 상속받는 class/object 타입으로 처리하기 때문에, 응답값을 분기 처리하는 함수를 생성하였다.

/**
     * Retrofit API 호출 시, flow로 변환하고 성공, 실패, 빈 응답에 대한 처리를 위한 함수
     */
     fun <T> safeFlow(apiFunc: suspend () -> Response<T>): Flow<ApiResult<T>> = flow {
        try {
            val response = apiFunc.invoke()
            if (response.isSuccessful) {
                val body = response.body() ?: throw NullPointerException("Response body is null")
                emit(ApiResult.Success(body))
            }
        } catch (e: NullPointerException) {
            emit(ApiResult.Empty)
        } catch (e: HttpException) {
            emit(ApiResult.Error(e))
        } catch (e: Exception) {
            emit(ApiResult.Error(e, e.message))
        }
    }

suspend 익명 함수를 인자로 받아 처리하여 응답에 따라 ApiResult Class 형식으로 분기 처리하는 함수이다.

마지막으로 위 safeFlow()에서 생산한 값을 처리해보자.

fun handleResponse(
        emptyMsg: String = "데이터가 없습니다.",
        errorMsg: String = "인터넷 연결을 확인해주세요.",
        onError: (String) -> Unit,
        onSuccess: (T) -> Unit
    ) {
        when (this@ApiResult) {
            is Success -> onSuccess(value)
            is Error -> onError(errorMsg)
            is Empty -> onError(emptyMsg)
        }
    }

safeFlow에서 내보낸 값에 따라 성공시 서버에서 넘겨준 값을, 예외 발생시 메세지를 넘겨주는 함수이다.

이제 ViewModel에서 사용해보자.

fun getSchools() {
        viewModelScope.launch {
            memberRepository.getSchools().collectLatest {
                it.handleResponse(
                    onError = { _errorMsg.value = it },
                    onSuccess = { _applyContent.value = it }
                )
            }
        }
    }

참고 블로그

https://velog.io/@cksgodl/AndroidKotlin-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B2%B0%EA%B3%BC%EA%B0%92%EC%9D%84-Flow%EB%B3%80%ED%99%98-%EB%B0%8F-Sealed-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

https://heeeju4lov.tistory.com/34

1개의 댓글