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 특징
- Uniform Interface : URI로 지정한 리소스에 대한 조작 인터페이스를 통일합니다.
- Stateless : 작업을 위한 상태정보를 따로 저장하고 관리하지 않습니다.
- Cacheable : 캐시가 가능하기 때문에 Last-modified 태그나 E-Tag를 이용하면 캐싱을 할 수 있습니다.
- Self-descriptiveness : REST API 메시지만 보고도 쉽게 이해 할 수 있는 구조로 되어있습니다.
- Client-Server : Server와 Client의 역할을 확실히 구분하고 있어서 서로간 의존성이 줄어듭니다
- Hierarchy : REST API Server는 다중 계층으로 구현할 수 있습니다.
HTTP Method
- POST : 리소스 생성
- GET : 리소스 조회 및 획득
- PUT : 리소스 수정
- DELETE : 리소스 삭제
REST API 가이드
- URI는 리소스의 정보를 표현합니다.
- https://sample.com/cafe/menu/americano
- 리소스에 대한 행위는 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()
...
}
}
})