Landroid

Retrofit 응답 상태 관리 본문

안드로이드

Retrofit 응답 상태 관리

silso 2020. 10. 10. 21:35

안녕하세요~ Landroid입니다!

 

안드로이드 개발하면서 서버 통신해보신 경험 있으시죠?

 

저는 REST API를 손쉽게 다루는 Retrofit이라는 라이브러리와 비동기 상태에서 통신을 원하기 때문에 Kotlin의 Coroutine을 자주 사용합니다.

 

(이해를 돕기 위해 안드로이드가 아닌 평범한 코틀린 코드로 설명하겠습니다.)

 

fun main() = runBlocking {
    println("첫 번째 main 호출")

    GlobalScope.launch {
        val result = RetrofitClient.webservice.getTodo(1)
        println(result)
    }.join()

    println("두 번째 main 호출")
}

 

그런데 위에 코드는 응답 코드를 얻을 수 없고 응답 성공/실패 여부를 나타낼 수 없다치명적인 문제가 있습니다.

 

 

그래서 이를 해결하기 위해 아래처럼 반환 값을 Response로 감싸주면 해결할 수 있습니다.

(httplogginginterceptor를 쓰면 간단히 해결될 수 있다는 건 함정)

 

interface Webservice {
    @GET("/todos/{id}")
    suspend fun getTodo(@Path(value = "id") todoId: Int): Response<Todo>
}


fun main() = runBlocking {
    println("첫 번째 main 호출")

    GlobalScope.launch {
        val result = RetrofitClient.webservice.getTodo(1)
        
        if (result.isSuccessful) {		// 성공/실패 여부 확인 가능
            println(result.code())		// 응답코드 확인 가능
            println(result.body())
        } else {
            println(result.message())
        }
    }.join()

    println("두 번째 main 호출")
}

 

이렇게 끝나면 좋겠지만 이것도 단점이 있는데 바로 코루틴 안에서 API를 호출할 때마다 성공/실패 여부를 확인하고 성공 또는 실패일 때 처리하는 코드를 작성해야한다는 점이 있습니다.

 

 

그렇게 되면 코드도 보기 어려워지고 보일러플레이트도 발생하게 됩니다.

 

 

그래서 나온 방법 중 하나가 다음 코드와 같습니다.

 

sealed class Result<out T: Any> {
    data class Success<out T : Any>(val data: T) : Result<T>()
    data class Error(val exception: String) : Result<Nothing>()
}

 

깃헙에 돌아다니시다 보면 가끔 보실 수 있는 코드입니다.

 

 

간단하게 해석하자면 통신 응답을 Result 클래스로 해놓으면 통신 성공하면 Result.Success <T>를 반환하고 실패하면 Result.Error를 반환합니다.

 

 

이를 이용하면 앞서 언급한 단점들을 대부분 상쇄시킬 수 있습니다. 뭐.... 백날 얘기해봤자 이해하기 쉽지 않으실 테니 예제 코드를 한 번 보겠습니다.

 

// 이거 뭔지 다들 아시죠?

object RetrofitClient {
    val webservice: Webservice by lazy {
        Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com")
            .addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
            .build().create(Webservice::class.java)
    }
}

interface Webservice {
    @GET("/todos/{id}")
    suspend fun getTodo(@Path(value = "id") todoId: Int): Response<Todo>
}

data class Todo(
    var id: Int = 0,
    var title: String = "",
    var completed: Boolean = false
)

 

sealed class Result<out T: Any> {
    data class Success<out T : Any>(val data: T) : Result<T>()
    data class Error(val exception: String) : Result<Nothing>()
}

// 매개변수로 suspend 메소드를 받고 통신성공 여부에 따라 Result 값을 반환하는 함수
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<T>): Result<T> {
    return try {
        val myResp = call.invoke()

        if (myResp.isSuccessful) {
            Result.Success(myResp.body()!!)
        } else {
            Result.Error(myResp.message() ?: "Something goes wrong")
        }

    } catch (e: Exception) {
        Result.Error(e.message ?: "Internet error runs")
    }
}

 

fun main() = runBlocking {
    println("첫 번째 main 호출")

    GlobalScope.launch {
    	// Result가 성공이냐 실패냐에 따라 동작처리
        when(val result = safeApiCall{ RetrofitClient.webservice.getTodo(1) }) {
            is Result.Success -> {
                println(result.data)
            }
            is Result.Error -> {
                println(result.exception)
            }
        }
    }.join()

    println("두 번째 main 호출")
}

 

그럼에도 이해가 안되신다면 코드를 복사해서 실행시키거나 예제를 변형해서 자신만의 코드로 이해해보시길 바랍니다.

 

더보기

비하인드

처음에 위와 같은 코드 패턴을 알게 되었을 때

이 패턴의 이름을 뭐라고 부르는지 찾아보지만.......

 

아무리 찾아도 안 나옴ㅋㅋㅋ

심지어 키워드조차 몰라서

제목을 뭘로 지을까 고민하다가 지극히 평범하고 추상적이게 "Retrofit 응답 상태 관리"라고 지었습니다.ㅋㅋㅋㅋ

 

혹시 이 패턴을 뭐라고 부르는지 아시는 분들은 댓글로 남겨주세요ㅎㅎ

Comments