새소식

android studio/TIL

[TIL] Retrofit + Coroutine 사용하기 (feat. enqueue 비동기)

  • -

최근 비동기 프로그래밍인 코루틴에 대해 공부하고있다.

내가 아는 비동기 프로그래밍들에 대해 들어본 것들은 RxJava, Asyncfunction(현재 deprecated됨), Coroutine 정도였다. 

몇 일동안 코루틴의 개념에 대해 공부하였고, 사용방법, 코루틴의 다양한 활용 예시들을 보면서 작동되는 과정들을 이해하였다. 

 

아직 다양한 방법으로 활용하는 것은 무리가 있어 최근 진행하였던 프로젝트를 코루틴을 사용하여 '리팩토링' 해보았다. 

 

기존 코드에선 Retrofit을 이용하여 서버와 통신할 때 enqueue를 사용하여 비동기 처리를 해주었다. 

하지만 Coroutine을 사용하면 enqueue를 사용하지않고 Retrofit 통신이 가능하였다. 

 

 

Retrofit + enqueue 

interface GetPetService{
    @FormUrlEncoded
    @POST("/api/pet")
    suspend fun getPetRegister(
        @Field("userId") userId: String
    ):Call<petListResponseDTO>
}

Service 기본적으로 Call<T> 객체를  반환한다.

예를 들어 서버의 API String 반환한다고 가정하면, 클라이언트는 Retrofit 통해 Call <String>를받게  것이다.

Call 인터페이스는 enqueue(Callback<T> callback); 메서드를 갖고 있어야한다.

그러니 통신을  후에 받은 Call<T> 객체는 enqueue가 구현된 상태라는 것이다.

val server=  RetrofitApi2.retrofit2.create(GetPetService::class.java)

server3.getPetRegister(value).enqueue(object :retrofit2.Callback<petListResponseDTO>{
    override fun onResponse(call: Call<petListResponseDTO?>?, response: Response<petListResponseDTO?>){
    	//응답 값 활용
        }
    }

    override fun onFailure(call: Call<petListResponseDTO>, t: Throwable) {
        Log.d("에러", t.message!!)
      }

기존 코드에서는 enqueue를 사용하여 비동기처리를 해주었다. 

Retrofit은 통신의 결과에 따라 파라미터로 받는 Callback 의 메서드를 실행해준다.

성공 시 onResponse 실행하고, 실패  onFailure 실행한다.

 

(* 참고로 Callback<T> 또한 인터페이스다.

onResponse/ onFailure 메서드를 구현 해야 사용할 수 있다.)

 

 

Coroutine

interface GetPetService{
    @FormUrlEncoded
    @POST("/api/pet")
    suspend fun getPetRegister(
    @Field("userId") userId: String
    ):retrofit2.Response<petListResponseDTO>
}

위 코드와 다른 점은, suspend라는 키워드를 사용하여 이 인터페이스는 코루틴 내부에서만 동작이 가능하다는 것을 알려준다.

또한 Coroutine을 사용하기 위해 Interface에서 반환값을 Response<> 형식으로 지정해주었다.

 

**

그런데, 레트로핏 2.6.0 이상부터는 코틀린 전용 확장 함수를 지원함으로써 Call 또는 Response를 해줄 필요가 없다고 한다. 그 이유를 추측해보자면 코루틴을 사용하기 전에는 스레드에서 데이터를 UI로 보낼 때에는 콜백함수를 이용해서 메인 스레드에 전달 해주어야 하기 때문에 Call로 랩핑을 해주어야 하지만, 코루틴은 콜백함수 없이도 suspend 키워드가 있다면 Dispatchers.Main에서도 UI에서 실행할 수 있기 때문인 것 같다.

**

   lifecycleScope.launch {
            try {
                val response = withContext(Dispatchers.IO) {
                    RetrofitApi2.retrofit2.create(GetPetService::class.java).getPetRegister(value)
                #메인 쓰레드에서 UI 업데이트 
                }
                
            } catch (e: Exception) {
                Log.d("에러", e.message!!)
            }
        }

Coroutine을 사용하게 되면, enqueue() 비동기 코드를 "실행"하는 코드(onResponse/ onFailure)가 없어도 스스로 값을 가져오게 된다. 

코루틴에서는 try/catch를 사용하여 try 블록에서 예외를 던질 수 있는 코드를 작성하고, 예외가 발생하면 catch 블록에서 잡히도록 하였다. 

** 추가로 finally에서는 예외와 상관없이 해야 하는 코드를 작성한다.

 

 

 

 

아직 코루틴의 에러 핸들링에는 능숙하지 않다.

위에서는 try/catch문을 사용하였지만 코드가 많아질수록 boiler plate 코드가 늘어날 수 있고, 관리하기 힘들어 질 수 있다.또한, 범위 내에서 자식 중 예외가 발생하면 다음 자식이 실행되지 않고 실행이 종료되게 되어 개별 예외 처리를 다시 한번 해줘야 하는 필요가 생길 수 있다.

 

 

 

예외처리를 하는 방법/wrapper class에 대해 조금 더 공부를 해야할 것 같다. 

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.