Retrofit2 정리하기

Posted by Jungwoon Blog on July 11, 2019

Retrofit2 정리하기

Retrofit은 Square사에서 제공하는 오픈소스 라이브러리로 REST API를 안드로이드에서 쉽게 이용할 수 있게 해주는 도구 입니다. 이번 포스팅에서는 REST API에 대한 간단한 소개와 Retrofit의 사용방법에 대해서 알아보도록 하겠습니다. 소스는 Kotlin으로 진행하도록 하겠습니다.

좀 더 자세한 내용을 알고 싶다면 https://square.github.io/retrofit/에서 확인하실 수 있습니다.


환경 구축

설치는 gradle(app)에 dependencies { } 안에 아래와 같이 넣어주기만 하면 바로 이용 가능해집니다.

(2019.07.11. 기준)

// Retrofit
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'

인터넷을 사용하기 때문에 AndroidManifest.xml<manifest> 사이에 아래 권한을 추가해주어야 합니다.

<uses-permission android:name="android.permission.INTERNET"/>

REST API?

REST(Representational State Transfer)는 2000년도에 로이 필딩(Roy Fielding)의 박사 논문에서 최초로 소개되었으며 HTTP를 좀 더 효율적으로 사용하기 위한 아키텍처로 발표되었습니다.

REST 구성

  • 자원(Resource) : URI
  • 행위(Verb) : HTTP Method
  • 표현(Representations)

REST 특징

  1. Uniform Interface : URI로 지정한 리소스에 대한 조작 인터페이스를 통일합니다.
  2. Stateless : 작업을 위한 상태정보를 따로 저장하고 관리하지 않습니다.
  3. Cacheable : 캐시가 가능하기 때문에 Last-modified 태그나 E-Tag를 이용하면 캐싱을 할 수 있습니다.
  4. Self-descriptiveness : REST API 메시지만 보고도 쉽게 이해 할 수 있는 구조로 되어있습니다.
  5. Client-Server : Server와 Client의 역할을 확실히 구분하고 있어서 서로간 의존성이 줄어듭니다
  6. Hierarchy : REST API Server는 다중 계층으로 구현할 수 있습니다.

HTTP Method

  • POST : 리소스 생성
  • GET : 리소스 조회 및 획득
  • PUT : 리소스 수정
  • DELETE : 리소스 삭제

REST API 가이드

  1. URI는 리소스의 정보를 표현합니다.
    • https://sample.com/cafe/menu/americano
  2. 리소스에 대한 행위는 HTTP Method(POST, GET, PUT, DELETE)로 표현합니다.
    • GET /members/insert/1 (X)
    • POST /members/1 (O)

HTTP 응답 코드

  • 200 : 클라이언트의 요청을 정상적으로 수행
  • 201 : 클라이언트의 생성 요청을 정상적으로 수행
  • 301 : 클라이언트가 요청한 리소스에 대한 URI가 변경 되었음
  • 400 : 클라이언트이 부적절한 요청을 함
  • 401 : 클라이언트가 인증되지 않은 상태에서 요청함
  • 403 : 적절하지 않은 리소스를 클라이언트가 요청함
  • 404 : 페이지를 찾을 수 없음
  • 405 : 클라이언트가 요청한 리소스에서 사용할 수 없는 Method 를 사용함
  • 500 : 서버에 문제가 발생

Retrofit 사용 방법

예제는 Kakao의 REST API를 가지고 설명하겠습니다. Kakao API 가이드 페이지를 보면 좀 더 자세한 내용을 확인할 수 있습니다.

우선 사용하고자 하는 REST API의 요청 방식과 응답 방식이 어떤지 확인을 해야 합니다. Kakao REST API 중에 이미지 검색 부분을 보면 다음과 같이 나와있습니다.

[Request]

앱 키를 헤더에 담아 GET으로 요청합니다.

GET /v2/search/image HTTP/1.1
Host: dapi.kakao.com
Authorization: KakaoAK {app_key}
설명 필수 타입
query 검색을 원하는 질의어 O String
sort 결과 문서 정렬 방식 X(accuracy) accuracy(정확도) / recency(최신순)
page 결과 페이지 번호 X(기본 1) 1 ~ 50사이 Integer
size 한 페이지에 보여질 문서의 개수 X(기본 80) 1 ~ 80사이 Integer

ex)

curl -v -X GET "https://dapi.kakao.com/v2/search/image" \
--data-urlencode "query=[검색 키워드]" \
-H "Authorization: KakaoAK [발급받은 앱 키]"

[Response]

응답 바디는 JSON 객체로 meta와 documents로 구성됩니다.

meta

설명 타입
total_count 검색어에 검색된 문서수 Integer
pageable_count total_count 중에 노출가능 문서수 Integer
is_end 현재 페이지가 마지막 페이지인지 여부, 값이 false이면 page를 증가시켜 다음 페이지를 요청할 수 있음 Boolean

document

설명 타입
collection 컬렉션 String
thumbnail_url 이미지 썸네일 URL String
image_url 이미지 URL String
width 이미지 가로 크기 Integer
height 이미지 세로 크기 Integer
display_sitename 출처명 String
doc_url 문서 URL String
datetime 문서 작성시간. ISO 8601. [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].000+[tz] String

ex)

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "meta": {
    "total_count": 422583,
    "pageable_count": 3854,
    "is_end": false
  },
  "documents": [
    {
      "collection": "news",
      "thumbnail_url": "https://search2.kakaocdn.net/argon/130x130_85_c/36hQpoTrVZp",
      "image_url": "http://t1.daumcdn.net/news/201706/21/kedtv/20170621155930292vyyx.jpg",
      "width": 540,
      "height": 457,
      "display_sitename": "한국경제TV",
      "doc_url": "http://v.media.daum.net/v/20170621155930002",
      "datetime": "2017-06-21T15:59:30.000+09:00"
    },
    ...
  ]
}

Retrofit2는 GSON을 제공하기 때문에 응답바디와 동일하게 data class를 만들어두면 응답 바디에 있는 json을 자동으로 파싱하여 객체로 받을 수 있습니다.

우선 Kotlin의 data class를 이용하여 아래와 같이 응답 바디와 동일한 구조로 만들어 주겠습니다.

** GSON : JSON 구조를 JAVA 객체로 직렬화, 역직렬화 해주는 라이브러리 입니다.

data class Image (
    val meta: ImageMeta,
    val documents: List<ImageDocument>
)

data class ImageMeta(
    val total_count: Int, // 검색어에 검색된 문서수
    val pageable_count: Int, // total_count 중에 노출가능 문서수
    val is_end: Boolean // 현재 페이지가 마지막 페이지인지 여부
)

data class ImageDocument(
    val collection: String, // 컬렉션
    val thumbnail_url: String, // 이미지 썸네일 URL
    val image_url: String, // 이미지의 URL
    val width: Int, // 이미지의 가로 크기
    val height: Int, // 이미지의 세로 크기
    val display_sitename: String, // 출처명
    val doc_url : String, // 문서 URL
    val datetime: String, // 이미지 등록일 ISO 8601. [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].000+[tz],
    override var favorite: Boolean = false
)

[Image.kt]

그 다음 해야할건 Retrofit Interface를 만들어야 합니다.

import com.byjw.jungwoon.util.retrofit.scheme.kakaoApi.Image
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query

interface RetrofitService {

    @Headers("Authorization: KakaoAK [발급받은 앱 키]")
    @GET("/v2/search/image")
    fun requestSearchImage(
        @Query("query") keyword: String,
        @Query("sort") sort: String = "recency",
        @Query("page") page: Int,
        @Query("size") size: Int = 10
    ): Call<Image>

}

[RetrofitService.kt]

HTTP Method 별로 어노테이션을 사용해 서버에 요청을 할 수 있습니다.

  • @GET : 리소스 조회 및 획득
  • @POST : 리소스 생성
  • @PUT : 리소스 수정
  • @DELETE : 리소스 삭제

호출하고자 하는 함수를 지정한 다음에 URL에서 아래와 같이 URL에 파라미터가 있는 경우

https://dapi.kakao.com/v2/search/image?query='Android Doll'&sort='recency'&page=1

@Query() 어노테이션을 사용해서 값을 넣어줍니다.

@Query("query") keyword: String,
@Query("sort") sort: String,
@Query("page") page: Int

만약 아래와 같이 계층 구조로 URL이 되어 있으면

https://sample.com/members/alex/address

https://sample.com/members/jayden/address

아래와 같이 이용할 수 있습니다.

@GET("members/{user}/address")
fun getUserAddress(@Path("user") userName: String): Call<Address>

그 다음아래와 같이 서비스를 만들어 줍니다. 여기서는 싱글톤으로 사용하기 위해서 object 로 만들었습니다.

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object SearchRetrofit {
    // 위에서 만든 RetrofitService를 연결해줍니다.
    fun getService(): RetrofitService = retrofit.create(RetrofitService::class.java)

    private val retrofit =
        Retrofit.Builder()
            .baseUrl("https://dapi.kakao.com") // 도메인 주소
            .addConverterFactory(GsonConverterFactory.create()) // GSON을 사요아기 위해 ConverterFactory에 GSON 지정
            .build()
}

[SearchRetrofit.kt]

이렇게 되면 SerarchRetofit을 통해서 쉽게 REST API를 호출할 수 있습니다.


// 위에서 만든 Image.kt의 data class로 받기 때문에 Image 객체를 통해 손쉽게 접근할 수 있습니다.
val response = SearchRetrofit.getService().requestSearchImage(keyword = "iPhone", page = 1).execute()
if (response.isSuccessful) {
    val image = response.body()
    ...
}

안드로이드에서는 MainThread에서 네트워크 작업을 못하기 때문에 아래 소스와 같이 enqueue를 호출하던지, 별도의 스레드를 만들던지 해서 비동기 처리를 해주어야 합니다.

SearchRetrofit.getService().requestSearchImage(keyword = "iPhone", page = 1).enqueue(object : Callback<Image> {
    override fun onFailure(call: Call<Image>, t: Throwable) {}

    override fun onResponse(call: Call<Image>, response: Response<Image>) {
        if (response.isSuccessful) {
            val image = response.body()
            ...
        }
    }

})