본문 바로가기

TIL

[TIL] kotlin Retrofit 개념, 응답 처리 메서드 ( Response<T>, Call<T> )

전에 작성했던 글 안에 Retrofit과 OkHttp에 대해서 간단하게 작성했었는데 더 자세하게 정리하기 위해 분리.

 

Retrofit ?

  • Retrofit은 Type - safe한 REST 통신 라이브러리이다. RESTful API를 호출하고 JSON 또는 XML 과 같은 응답 데이터를 쉽게 파싱할 수 있도록 도와준다.
    • 이때 REST란 Representational State Transfer의 약자로 웹 서비스를 구축하기 위한 아키텍처 스타일이며, REST는 네트워크 상에서 클라이언트와 서버 간의 통신을 위한 규칙(HTTP 메서드)과 제약 조건(상태 관리 등)을 정의한다.
    • Json - Java Script Object Notiationn은 key - value의 쌍으로 이루어져 있는 객체를 만들 때 생성하는 표현식으로, 직관적이어서 이해하고, 작성하는 것이 쉬우며 XML에 비해 용량이 적어 일반적으로 많이 사용되는 데이터를 저장하거나 전송할 때 쓰이는 문자 형식이다.
  • OkHttp 라이브러리의 상위 구현체로, OkHttp를 네트워크 계층으로 활용하고 그 위에 구축된다. API 30부터 deprecated된 Async Task를 통해 작업하는 방식이 아닌 AsyncTask 없이 Background Thread를 실행하고 Callback을 통해 Main Thread에서 UI를 업데이트할 수 있다.

 

 

OkHttp ?

  • OkHttp에 대해서도 간단하게 알아보자
    • OkHttp는 Http 클라이언트 라이브러리 이다. OkHttp는 애플리케이션에서 네트워크 통신을 처리하는 데 사용된다.
  • OkHttp의 장점 
    • HTTP/1,2,3 프로토콜을 지원하여 빠르고 효율적인 네트워크 통신을 가능하게 한다.
      • HTTP/2,3 프로토콜 ?
      • 웹 통신을 위해 최신 버전의 프로토콜이다. 빠르고 효율적인 네트워크 통신을 가능하게 한다
    • 인터셉터 지원
      • OkHttp는 인터셉터를 사용하여 요청 및 응답을 수정하고 로깅할 수 있다. 이를 통해 네트워크 요청과 Response를 받아 필요한 작업을 수행할 수 있다.
    •  Caching
      • Caching? 이전에 수신한 네트워크 응답을 로컬 또는 메모리에 저장하는 것.
        • 반복적인 요청에 대한 네트워크 비용과 시간이 절약.
        • 불안정한 네트워크 연결 상황에서도 이전에 수신되어 캐시된 응답을 사용하므로 안정성 향상.
        • 네트워크 대역폭을 절약하며 서버 부하를 줄여줌으로 시스템의 전반적인 성능 향상.
      • OkHttp는 HTTP응답을 캐싱하여 네트워크 사용량을 줄이고 응답 시간을 단축할 수 있도록 한다.
  • OkHttp가 Retrofit의 Base로 사용되는 이유.
    • OkHttp를 사용하지 않고 HTTP 통신을 하기 위해서는 다음과 같은 과정을 통해야한다
      • 1. HttpURLConnection 연결
      • 2. Buffer를 통한 입출력
      • 3. 예외 처리 등
    • 하지만 OkHttp를 사용하게 될 경우 이러한 부분을 해결할 수 있다. 이로 인해 Retrofit 라이브러리에서 내부 코드에서도 OkHttp 클라이언트를 디폴트로 선언한다.

 

 

 

 

API Service 인터페이스 ( Retrofit Service Interface )

  • API Service Interface 의 역할은 Retrofit을 사용하여 원격 API와 통신하기 위해 End Point와 메서드를 정의하여 해당 네트워크 요청 및 API 호출에 대한 응답을 처리할 수 있도록 한다.
  • API Service Interface의 구성요소
    • API End Point에 대한 요청
      • HTTP 요청 방식 ( GET, POST, PUT, DELETE 등 ) 을 통해 API 요청 경로와, 쿼리 매개변수, 요청 바디 등을 동적으로 요청 방식을 바인딩할 수 있다.
    • 쿼리 매개변수
      • @Query, @QueryMap 과 같은 형태로 API 요청 메서드의 프로퍼티 값들을 지정해준다.
    • 응답 처리
      • API 요청 메서드를 통해 반환된 응답의 수신받는 형식( Response,  Call, Deferred 등 ) 과 타입을 지정해주는 것이다.

 

 

 

Retrofit 사용하기

 

build.gradle

  • retrofit2 와 okhttp3, OkHttp Intercepter를 사용할 경우 okhttp3 logging-interceptor을 추가해준다.
  • json 데이터의 변환이 필요하므로 gson converter 또는 moshi converter 등 의 Converter 추가
dependencies {

    // retrofit, okHttp3, gson converter Library
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.10.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
    
    // gson converter | 컨버터는 사용할 1개만 있으면 된다
    implementation("com.google.code.gson:gson:2.10.1")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    
    // moshi converter
    implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
    
    ...
    ...
}

 

 

Retrofit Instance 생성 및 apiService Interface 정의

ApiServiceInterface.kt

  • 이때 API에서 별도로 Header를 특정하고 있는 경우에는 Headers를 통해 Key를 입력하고, Header가 특정되지 않은 경우에는 Get 내부 API 요청 메서드 프로퍼티로 Key 값을 지정 해줘야 한다.
  • API 요청 메서드 프로퍼티 작성 후 응답처리 ( Call<> 또는 Response<>, Deferred<>  ) 메소드를 정의 해준다
// header의 Key를 통해 요청하는 경우
interface ApiServiceInterface {
   
    @Headers("Authorization: YOUR_API_KEY")
    @GET("End point")
    //// Response를 사용할 경우 suspend fun으로 변경. 
    fun service( 
        @Query("param0") param0: String,
        @Query("param1") param1: String,
        @Query("param2") param2: Int,
        @Query("param3") param3: Int,
    ): // Call<Model> 또는 Response<Model> 
       // 이때 Model은 API 통신을 통해 받게될 JSON 데이터 최상단에 있는 Object.
}

// header의 key를 통해 요청하지 않는 경우
interface ApiServiceInterface {
   
    @GET("End point")
    //Response를 사용할 경우 suspend fun으로 변경. 
    fun service(
    	@Query("key") key: String,
        @Query("param0") param0: String,
        @Query("param1") param1: String,
        @Query("param2") param2: Int,
        @Query("param3") param3: Int,
    ): // Call<Model> 또는 Response<Model> 
       // 이때 Model은 API 통신을 통해 받게될 JSON 데이터 최상단에 있는 Object.
}

 

Object RetrofitClient.kt

  • [ 1 ] object 안에서 by lazy {} 를 통해 apiService와 Retrofit이 최초 접근 시 한번만 초기화 되고, 이후에는 이미 생성된 인스턴스를 재사용하게끔 객체를 정의해주고 GsonBuilder를 정의해준다
    • setLenient() =Json 파싱 시 유연한 방식으로 동작하도록 설정하는 Gson 라이브러리 메소드
  • [ 2 ] Retrofit.Builder API 통신의 대상이 되는 네트워크의 BaseUrl과 ConverterFactory를 지정해준다.

 

OkHttp3 Intercepter를 사용하지 않는 경우
// OkHttp3 Intercepter를 사용하지 않는 경우
object RetrofitClient {

    //GsonBuilder를 정의
    private val retrofit: Retrofit by lazy {
        val gson = GsonBuilder().setLenient().create()

	//Retrofit.Builder 정의
        Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
    }
    
    // retrofit의 api 서비스 Interface 정의
    val apiService: RetrofitInterface by lazy {
        retrofit.create(RetrofitInterface::class.java)
    }
}

 

 

OkHttp3 Intercepter를 사용하는 경우, API Service Interface 응답 처리 메소드를 Response<T>  로 사용한다.

 

// OkHttp3 Intercepter를 사용하는 경우
object RetrofitClient {

    // OkHttpClient 객체 정의
    private val okHttpClient: OkHttpClient by lazy {
        val interceptor = HttpLoggingInterceptor()

        if (BuildConfig.DEBUG)
            interceptor.level = HttpLoggingInterceptor.Level.BODY
        else
            interceptor.level = HttpLoggingInterceptor.Level.NONE

        OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .addNetworkInterceptor(interceptor)
            .build()
    }

    
    private val retrofit: Retrofit by lazy {
    
	//GsonBuilder를 정의
        val gson = GsonBuilder().setLenient().create()

	//Retrofit.Builder 정의
        Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(okHttpClient) // okHttpClient객체 client 지정
            .build()
    }
    
    // retrofit의 api 서비스 Interface 정의
    val apiService: RetrofitInterface by lazy {
        retrofit.create(RetrofitInterface::class.java)
    }
}

 

응답 처리 메소드 ( API Response Handling Method )

  • 응답 처리 메소드는 Retrofit에서 제공하는 Response<T>, Call<T> 과 Coroutine 라이브러리에서 제공해주는 Deferred<T> 가 일반적으로 사용된다.
  • Retrofit2 에서 제공해주는 Call과 Response에 대해서 알아보자.

 

Call < T >

  • 자체적으로 HTTP 요청과 응답 쌍을 생성한다 → OkHttp를 사용하지 않고 사용할 때 주로 사용된다.
  • 수신 받을 Data 객체를 T 에 넣어주어 Call<수신받을객체> 형식으로 메소드 사용.
  • execute()를 사용해 동기적으로 실행 또는 enqueue()를 사용해 비동기적으로 실행이 가능하다.
    • execute()
      • execute()는 동기적으로 요청을 보내고 응답을 반환한다. Retrofit의 동기 호출은 메인 쓰레드에서 실행되기 때문에 execute()가 실행되는 동안 UI가 차단되어 유저와 UI 간의 상호작용이 불가능하기 때문에 권장되지 않는 방법이다
    • enqueue()
      • enqueue() 는 비동기적으로 요청을 보내고 응답을 반환하므로 execute()처럼 메인 쓰레드에서 실행되지 않기 때문에 유저와 UI 간의 상호작용은 메인 쓰레드에서 정상적으로 작동될 수 있다.
      • 비동기적인 응답처리를 위해 응답 성공 또는 실패 시 동작을 정의할 콜백객체 ( Callback<T> ) 를 구현해주어야 한다.
class testA : Fragment() {

// 콜백 구현
	call.enqueue(object : Callback<User> {
  	  override fun onResponse(call: Call<User>, response: Response<User>) {
        	if (response.isSuccessful) {
            	    val user: User? = response.body()
            	    // 성공적인 응답 처리
        	}else {
           	     // 오류 응답 처리
        	}
    	}

     	  override fun onFailure(call: Call<User>, t: Throwable) {
        	// 네트워크 오류 처리
    	}
    }
}

 

Response<T>

  • 응답 처리 메서드로 Response<T> 을 사용하게 될 경우 보통 OkHttp3와 함께 사용한다.
  • 응답 데이터로는 response.body() 메서드를 통해 수신된 응답 데이터를 가져올 수 있다.
  • suspend 를 통해 코루틴 사용으로 비동기적인 처리가 가능하다.
  • 에러 처리 try , catch 사용 - enqueue 에 비해 간결한 코드 작성 가능.
class testFragment : Fragment() {
	private lateinit var apiServiceinterface : ApiServiceInterface
	
    	...
    
 	...

	// 파라미터를 받지않고 apiServiceInterface.service() 괄호 안에서 하드코딩 하는 방법도 있다
	fun api(key: String, param0 : String param2: Int) = globalScope.launch(Dispatchers.IO){
		try {
    		val response:Response<T> = apiServiceInterface.service(key,param0,"test",param2,5)
			if (response.isSuccessful){
            	response.body()?.let{ body ->
                	//성공 처리
                }
            }else {
            	val errorBody: ResponseBody? = response.errorBody()
                // 실패 처리
            }
    	}catch (e: Exception){
        	// 예외 처리		
        }
	}
}

 

 

 

 

 


 

[오늘 복습한 내용]

1. Retrofit, OkHttp

 

 


[오류,에러 등등]

1. 특별한 오류는 없었다.

 


[느낀 점]

1. 작동 개념을 이해하기가 어려울 때 천천히 코드를 하나씩 뜯어보면 답을 찾는 것 같다.

 

2. 어렵다

 

3. 남한테 쉽게 이해할 수 있게 설명하기가 너 어렵다

 

 


[Reference]

 

// Call vs Response

https://velog.io/@jeongminji4490/Retrofit-Call-vs-Response-and-Kotlin-Result