마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

레트로핏 인터셉터에서 status code 핸들링?

0 추천

status code가 200, 401, 500 등등에 따라서 액티비티나 프래그먼트에 토스트를 띄워주고싶습니다

class MyInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)

        val httpStatusCode = response.code

        when (httpStatusCode) {
            200 -> {
                throw CustomHttpException("api 성공 테스트")
            }

            401 -> {
                throw CustomHttpException("401 에러")
            }
        }

        return response
    }
}

class CustomHttpException(message: String) : Exception(message)
@HiltViewModel
class SearchViewModel @Inject constructor(
    private val searchRepository : SearchRepository
) : ViewModel() {
    fun getSearchResult(type : SearchType, searchText : String, page : Int) =
        searchRepository.getSearchResult(type, searchText, page)
            .catch { exception ->
                if (exception is CustomHttpException) { }
            }

}

현재 이렇게 써줘봤는데 앱이 크래시 납니다 throw하는 부분을 가리키고 있구요 
캐치를 못하는거같은데.. 일부러 테스트 하려고 200일때도 throw를 해봤습니다
어떻게 인터셉터에서 status code에 따라서 상태를 캐치하고 토스트나 팝업 등 띄워줄 수 있을까요?

 

수원통학러 (3,570 포인트) 님이 2023년 9월 24일 질문
글자 제한으로 repository 코드는 여기다 남기겠습니다
class SearchRepository(
    private val service: ApiService
) {
    fun getSearchResult(type : SearchType, searchText : String, page: Int): Flow<List<KakaoSearchResult>> {
        val searchResultList = mutableListOf<KakaoSearchResult>()

        return flow {
            runCatching {
                if (type == SearchType.IMAGE) {
                    val response =
                        service.getSearchImage(query = searchText, page = page).mapper(type)

                    searchResultList.add(Header(page))
                    searchResultList.addAll(response)
                } else {
                    val response =
                        service.getSearchVideo(query = searchText, page = page).mapper(type)

                    searchResultList.addAll(response)
                }
            }.onFailure {
                it.message
            }.onSuccess {
                emit(searchResultList)
            }
        }
    }
}

1개의 답변

0 추천
에러코드를 처리하고 싶으면 인터셉터보다는 Retrofit 인터페이스에서 Reponse를 리턴하게 만들어 보세요.

https://square.github.io/retrofit/2.x/retrofit/retrofit2/Response.html

Response 안에는 에러코드가 들어갑니다.

그리고 님이 원하는 응답맵핑은 인터셉터 보다는 어댑터를 이용하는게 적합해 보입니다.

그리고 엄재웅님이 만든 sandwich라는 라이브러리 코드를 확인해 보시면 어떻게 어댑터를 활용해서 응답을 내가 원하는 구조로 변경하는지 알 수 있습니다.

https://github.com/skydoves/sandwich
spark (228,010 포인트) 님이 2023년 9월 25일 답변
저도 처음엔 그생각을 해봤는데, 그럼 액티비티 또는 프래그먼트마다
status에 따른 분기 코드를 매번 작성해야되지 않나해서요
baseactivity, basefragment에 써줘야될까요?
코드리뷰를 받았을때 문제 사항이 될까 싶은 생각도 듭니다
Callback을 사용한다면 UI 핸들링을 편하게 하기 위해 wrapping클래스를 사용하는게 편하겠죠.
제가 말한 어댑터는 Retrofit 어댑터예요.
https://github.com/skydoves/retrofit-adapters
그리고 Gson같은 라이브러리로 JSON response를 파싱하는 것도 이미 어댑터를 사용하는 거구요.
그리고 status 따라 다른 동작을 하게 하려면 분기하는 코드는 어떻게든 들어가야만 하겠죠.
private fun getSearchImageResult(searchText: String, page: Int, onError: (String?) -> Unit): Flow<List<KakaoSearchResult>> {
        val searchResultList = mutableListOf<KakaoSearchResult>()

        return flow {
            service.getSearchImage(query = searchText, page = page)
                .suspendOnSuccess {
                    val response = this.response.body()?.mapper(SearchType.IMAGE) ?: arrayListOf()
                    searchResultList.add(Header(page))
                    searchResultList.addAll(response)
                    emit(searchResultList)
                }.onError {
                    onError(this.response.code().toString())
                }.onException {
                    Log.e("ljyljyljy","익셉션 ${this.exception.message}")
                }

        }
    }

이렇게 알려주신 샌드위치 라이브러리 사용했습니다

fun getSearchResult(searchText: String, page: Int, errorCallback: (String?) -> Unit): Flow<List<KakaoSearchResult>> {
        val searchImageResult = getSearchImageResult(searchText, page, onError = errorCallback)
        val searchVideoResult = getSearchVideoResult(searchText, page)

        return searchImageResult.combine(searchVideoResult) { imageResult, videoResult ->
            val combinedResult = mutableListOf<KakaoSearchResult>()
            combinedResult.addAll(imageResult)
            combinedResult.addAll(videoResult)

            combinedResult
        }
    }

이런식으로 repository에서 error를 fragment까지 던져서 status code를 토스트까지 띄우는건 성공했는데요 이게 맞는 방식인가요? 아니면 좀더 깔끔한 방법이 있을까요?
많이 사용하는 방법 중의 하나는 Kotlin.Result(https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/)이나 Either(https://subscription.packtpub.com/book/programming/9781787126367/5/ch05lvl1sec77/either) 같은 타입을 써서 에러와 성공한 데이터를 구분해서 리턴하도록 하는 겁니다.
이걸 대체하려고 샌드위치 라이브러리 쓴거아닌가요?
해당 라이브러리의 역할이 그런 거죠. 제 답의 포인트는 님이 원하는 것은 API response를 님이 원하는 정보를 포함한 데이터를 가진 구조로 변환하는 것이예요. 따라서 어떤 형태로 처리하든지 맵핑을 해야하고 여기에 초첨을 맞추시면 원하시는 결과물에 대한 답을 찾으실 수 있을 거라고 생각합니다. 그리고 더 깔끔한 방법이라는 건 개인마다 보는 관점의 차이가 있습니다. 어떤 사람들은 더 짧은 코드를 말하기도 하고, 누군가는 길어지더라도 이해가 직관적으로 되는 코드, 누군가는 테스트가 용이한 코드 등등. 따라서 깔끔하다는 것의 정의를 좀더 명료하게 하신 다음 코드를 거기에 초점을 맞추시고 이것 저것 시도해 보시면 좋을 것 같습니다.
...