데브식스 프로젝트를 진행하다가 동료 안드로이드 개발자분께서
view 에서만 에러처리를 하지말고 네트워크쪽 로직에서도 에러처리를 하자고 말씀하셨다.
이게 맞는 접근법인지는 모르겠지만 그말을 듣고 가만 생각해보니
view에서 발생하는 에러가 아니라 통신에서부터 에러가 생겨서 발생하는 문제를
view에서만 처리하는건 예상치 못한 상황이 발생할 수 있기도 했고
view까지 오지 않아도 사전에 에러로 인한 예기치 못한 상황을 방지 할 수 있기도 했다.
그러면서 정말 괜찮다고 느낀 방법을 소개해주셨는데 그 부분에 대해서 정리를 해보려한다.
전체적인 프로젝트 코드를 다 소개하지는 못하고 일부분만 가져와서 아이디어를 공유하는 정도로
이 글은 작성될 예정이다.
먼저 우리는 RemoteSource 라는 개념을 가져와서 쓰고 있는데
해당 부분에서 retrofit 코드 를 호출해서 사용한다.
이때 서버에서 받아올 데이터를 Response 라는 객체로 감싸주는데
기존에 retrofit에 있는 Response 객체의 확장함수를 만들어주고 그 함수를 이용하고 있다.
먼저 retrofit에 있는 Response 객체를 확장함수로 작성한 코드다.
data class ErrorResponse(
val error: Error
)
data class Error(
val code: Int, val message: String, val detail: String
)
suspend fun <T> Response<T>.executeNetworkHandling(): T? {
val handle = if (isSuccessful) {
null
} else {
val gson = Gson()
val errorResponse = gson.fromJson(errorBody()?.string(), ErrorResponse::class.java)
DefaultHandleServerStatus(errorResponse.error)
}
return body().executeErrorHandling(handle)
}
Error 라는 data class 는 api 호출을 어떤 이유로 실패하게 되면 해당 구조로 백엔드에서 응답을 해주는데
실패했을때만은 Error 의 구조로 응답을 받기위해서 따로 코드를 작성해줬다.
핵심 코드는 executeNetworkHandling 이 부분인데 자세히 살펴보자.
사실 복잡하지도 않다.
호출에 성공한다면 -> 에러 핸들링을 할 필요가 없으니 null
호출에 실패한다면 -> Error 를 담는 data class에 값을 집어넣고 DefaultHandleServerStatus 으로 핸들링한다.
라는 전략이다.
그럼 DefaultHandleServerStatus 는 뭘까
class DefaultHandleServerStatus(private val response: Error) :
HandleServerStatus {
override fun isServerFail(): HandleServerResult =
HandleServerResult(response.message, response.code == 404)
override fun isNotAutority(): HandleServerResult =
HandleServerResult(response.message, response.code == 403)
override fun isAleadyExit(): HandleServerResult =
HandleServerResult(response.message, response.code == 409)
override fun isNotCorrect(): HandleServerResult =
HandleServerResult(response.message, response.code == 400)
}
HandleServerStatus 라는 interface 를 상속받는 class 인데 error response 를 받아서 서버 상태코드에 따라
처리하도록 만들어져있다. 인터페이스는 아래와 같이 구현되어있다.
interface HandleServerStatus {
//실패
fun isServerFail(): HandleServerResult
//권한없음
fun isNotAutority(): HandleServerResult
//이미 존재
fun isAleadyExit(): HandleServerResult
//옳지 않다
fun isNotCorrect(): HandleServerResult
}
data class HandleServerResult(val message: String, val status: Boolean)
상태코드에 따라 api 통신의 상태가 어떤 상황인지 만들어준다. HandleServerResult data class 에서 status 코드가 정의해둔 상태와 같다면 status가 true 가 되고 message 를 string 으로 받아둔다.
이런것들의 처리가 끝나면 Response의 확장함수인 executeNetworkHandling 에서 body().executeErrorHandling(handle) 를 return 하게 되는데 executeErrorHandling 는 아래를 참고하자.
fun <DATA> DATA.executeErrorHandling(
handleServerStatus: HandleServerStatus? = null
): DATA {
handleServerStatus?.let {
val serverFailResult = handleServerStatus.isServerFail()
val alreadyExit = handleServerStatus.isAleadyExit()
val notAutorutyResult = handleServerStatus.isNotAutority()
val notCorrect = handleServerStatus.isNotCorrect()
return when {
serverFailResult.status -> {
throw ServerFailException(serverFailResult.message)
}
notAutorutyResult.status -> {
throw NotAutorityException(notAutorutyResult.message)
}
alreadyExit.status -> {
throw AleadyExitException(alreadyExit.message)
}
notCorrect.status -> {
throw NotCorrectException(notCorrect.message)
}
else -> this
}
} ?: kotlin.run {
return this
}
}
이곳으로 넘어오기전 만들어진 HandleServerStatus 의 데이터를 가지고 해당 에러가 발생하여 status 의 값이 true 라면
관련 예외를 throw 하라고 작성되어있다.
그리고 아래에 있는 미리 정의해둔 Exception.kt 파일에서 예외를 throw 할 수 있게 동작한다.
/**
* 서버 바디 없음
*/
class ServerErrorException(message: String) : Exception(message)
/**
*404 에러
*/
class ServerFailException(message: String) : Exception(message)
/**
*403 에러
*/
class NotAutorityException(message: String) : Exception(message)
/**
*409 에러
*/
class AleadyExitException(message: String) : Exception(message)
/**
*400 에러
*/
class NotCorrectException(message: String) : Exception(message)
이렇게 작성해주면 모든 통신마다 하나하나 예외처리를 적어주지 않고도 직관적이며
정리된 상태로 서버 예외처리를 작성할 수 있다.
실제로 호출할때는
레트로핏 api 가 작성된 api를 호출할때
retrofitAPI.addData(Entity).executeNetworkHandling
이렇게 쓸 수 있다. 확장함수를 작성하기전이라면 retrofitAPI.addData(Entity) 이렇게만 작성해서
callback 데이터를 처리했겠지만 위 코드로 작성하면 에러처리까지 처리 할 수 있게 된다.
'Android' 카테고리의 다른 글
Navigation Component 에서 그래프 여러개 관리하기 (0) | 2022.12.11 |
---|---|
멀티모듈로 배포할때 파이어베이스 관련 이슈 (0) | 2022.11.06 |
Jetpack navigation startDestination 동적으로 설정 (0) | 2022.08.27 |
안드로이드 Paging 3 (0) | 2022.07.24 |
InverseBindingAdapter 에 대해 (0) | 2022.03.28 |
BindingAdapter에 대해서 (0) | 2022.03.27 |
Android Databinding (데이터 바인딩) (0) | 2022.02.26 |
안드로이드 MVVM에서 코루틴 Flow로 이벤트를 처리하는 방법에 대해 (0) | 2022.02.21 |