API 호출을 위한 Retrofit 구현 방법
Build DSL 언어는 Kotlin을 채택했고, 당연히 Gradle을 이용했다.
먼저 Retrofit이 있기 전, Application에서 API를 호출하기 위해서는 HttpClient를 이용하여 요청했었다.
(다른 방법이 있기야 하겠지만.. 내가 아는 수준에서는 HttpClient를 이용해서 구현하는게 전부였다.)
그러다가, Retrofit이란걸 알게되었고 Retrofit을 써봤는데 소스도 안더러워지고 관리면에서 매우 뛰어났다.
이제 Retrofit을 구현하기 전 필요한 Library를 불러오는 코드를 작성하자.
필요한 Library는 Maven Repository 에서 찾아서 진행했다.
나는 복잡한 Api Interface를 구현하려는게 아니라, 간단하게 참조를 진행했다.
Api의 요청과 응답은 모두 JSON으로 오는것으로 사전에 규약을 했기 때문에 다른 Convert Factory 없이 Gson Convert Factory만 적용했다.
1
2
3
4
|
implementation("com.squareup.retrofit2:retrofit:2.7.2")
implementation("com.squareup.okhttp3:logging-interceptor:3.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.7.2")
implementation("com.google.code.gson:gson:2.8.5")
|
cs |
1번째 Line : Retrofit을 사용하기 위한 Library
2번째 Line : Retrofit을 구현할 때 OKHttpClient를 넣을 수 있는데, Logging Interceptor를 구현하기 위한 Library
3번째 Line : 응답이 JSON 형식으로 올 것이기 때문에 이를 번역하기 위한 Gson Converter Library
4번째 Line : 특정 모델을 구축하고 이를 JSON 으로 변환해 Log에 남길 것이기 때문에 추가한 Library
이 외에도 Call Adapter Factory를 이용해 RxJava와 같이 만들 수 있으나 나는 비동기방식을 이용하는 것이 아닌 동기방식을 이용할 것 이기 때문에 추가하지 않았다.
비동기 방식을 이용하고자 하면 'com.squareup.retrofit2:adapter-rxjava2:version'을 추가하면 된다.
이 외에도 자신이 직접 Custom해서 Adapter Factory를 구현할 수 있다.
관련된 링크는 아래를 참조하면 된다.
- Retrofit 2과 함께하는 정말 쉬운 HTTP
- RxJava 스럽게 Retrofit 사용하기
- [Retrofit] Android Retrofit Custom Call Adapter
이 외에도 XML 통신을 위해서라면 converter-simplexml Factory를 이용하면 되고, 이를 이용해 SOAP 통신도 구현할 수 있다.
SOAP 통신의 경우에는 이 후에 작성하도록 하겠다.
일단, 자신이 필요한 Library를 모두 참조했고 정상적으로 불러온 것을 확인했다면 이제 ApiServiceContext를 작성해주면 된다.
ApiServiceContext라는 Class를 하나 선언해주고, Retrofit을 Build하는 로직을 작성해주자.
나같은 경우에는 특정 함수를 통해 Retrofit을 초기화하는 것이 아닌, 해당 Class를 생성하자마자 Retrofit이 초기화되는 것을 원했기 때문에 *init 블럭에 작성했다
(*init : Class가 생성되자마자 작동되는 Block)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.time.LocalDateTime
import java.util.concurrent.TimeUnit
class ApiServiceContext(
private val baseApiUrl: String,
private val timeoutSecond: Long
) {
private lateinit var retrofit: Retrofit
init {
val baseUrl = if(!baseApiUrl.endsWith("/")) {
"$baseApiUrl/"
} else {
baseApiUrl
}
val loggingInterceptor = HttpLoggingInterceptor {
val time = LocalDateTime.now()
println("${time.hour}:${time.minute.toString().padStart(2, '0')}:${time.second.toString().padStart(2, '0')} : $it")
}.apply { level = HttpLoggingInterceptor.Level.BODY }
val client = OkHttpClient.Builder()
.connectTimeout(timeoutSecond, TimeUnit.SECONDS)
.writeTimeout(timeoutSecond, TimeUnit.SECONDS)
.readTimeout(timeoutSecond, TimeUnit.SECONDS)
.cache(null)
.addInterceptor(loggingInterceptor)
.build()
retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
|
cs |
먼저 나는 해당 Class를 생성하면서 baseUrl과 timeOut Second를 생성할 때 받아와서 사용하기로 했다.
그리고, baseUrl에 "/"로 끝나지 않으면 문제가 발생하기 때문에 혹시 생성 과정에서 잘못 넣었을 때를 대비하여 "/"가 없으면 자동으로 붙여주는 로직을 구현한 것이다.
loggingInterceptor를 구현해 Retorfit에서 나오는 요청과 응답, 상세한 값을 println 시키고 있다.(모니터링 목적)
그 후는 OkHttpClient를 구현하고, Retrofit을 Build하는 과정이다.
여기서 addConverterFactory라는 부분이 있는데 이 부분에 자신이 API에서 사용할 응답 값에 대한 Converter Factory를 넣어주면 된다.
(여기서 문자열로만 받고싶다고 하면 ScalarsConverterFactory를 이용하면 된다.)
Converter Factory는 1개만 추가가 가능한것이 아닌, 여러개 추가가 가능하다.
(API 통신 상에서 그럴리는 없겠지만.. XML도 나오고, JSON도 나오고, 일반 문자열도 나온다면 다 추가해버리면 된다.)
근데 추가할 때 주의점은 최근에 안건데 순서에 맞게끔 잘 넣어야 한다고 한다.
(Converter Factory의 순서가 틀릴경우 비정상적인 작동을 할 수 있다고 한다. 이 부분에 대해서는 자세히 찾아봐야 할 것 같다.)
이 후에 API Interface를 생성한다.
API Interface를 작성하려면, API 정보를 알아야하는데 우선 여기서는 테스트로 제공되는 API를 이용해보도록 하겠다.
(테스트로 제공되는 API 정보 : https://jsonplaceholder.typicode.com/)
위 사이트에 보면 아래 사진처럼 나와있는데, 저중에 endPoint가 '/posts'인 부분을 이용하도록 하겠다.
/posts를 GET해보니 아래와 같은 모델들이 나왔다.
1
2
3
4
5
6
|
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
|
cs |
Field는 총 4개고, baseUrl은 "https://jsonplaceholder.typicode.com/", endPoint는 "/posts" 임을 알아냈다.
이 데이터를 기반으로 Interface를 작성할 수 있으니 아래와 같이 작성하도록 하자.
TestModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import com.google.gson.annotations.SerializedName
data class TestModel(
@SerializedName("userId")
val userId: String,
@SerializedName("id")
val id: String,
@SerializedName("title")
val title: String,
@SerializedName("body")
val body: String
)
|
cs |
(*SerializedName 이란 ? Gson에서 제공되는 Annotation으로 직렬화 및 역직렬화를 사용할 때 사용된다.
이해가 잘 안간다면 JSON으로 다시 만들었을 때 아래에 있는 필드는 Annotation에 정의된 이름으로 생성되고(직렬화),
위 Model을 통해 특정 JSON 파일에서 객체로 다시 만들때는 Annotation에 정의된 이름에 있는 값이 아래에 변수에 들어가는 것(역직렬화) 라고 생각하면 된다.)
ApiInterface.kt
1
2
3
4
5
6
|
interface ApiInterface {
@GET("/posts")
fun test(): Call<List<TestModel>>
}
|
cs |
API를 호출 했을 때 위에서 선언한 Data Class 형식으로 List가 되어 내려오는 것을 알 수 있으니 위처럼 작성한 것이다.
함수 위에 있는 @GET Annotation은 test() 함수를 호출 했을 때 /posts 주소로 GET Method를 이용해 통신할 것임을 표시한 것이고,
응답 부분에 있는 Call은 해당 Interface에 있는 함수를 사용했을 때 execute()를 할지, cancel()을 할지에 대해 작성할 수 있는데 기본적으로 Retrofit Service는 Call<T> Interface를 통해 요청을 해야한다.
(이 외의 방법도 있는 것 같은데.. 아직 이 방법으로 하다가 막혀서 다른 방법을 쓸 필요도 없고 잘 모르겠다..)
여기서, 본인은 GET을 하거나 POST를 할 때 Request Body를 넣는 경우도 있고, Query Parmas를 넣는 경우도 있고, Header를 추가해야 하는 경우도 있을 것이다.
방법은 많고 실제로 잘 작동되고 있으니 구글에 'retrofit get query params'와 같은 검색 키워드를 이용한다면 쉽게 찾을 수 있을 것이다.
(여기에 작성하기에는 글 양이 너무 많아지니 다음에 시간이 된다면 따로 작성하도록 하겠다.)
이제 Interface도 모두 구현을 완료했으면, 아까 구현했던 ApiServiceContext로 돌아가 Retrofit을 실제로 해당 Interface에 맞게 Create하는 함수를 추가해주도록 하자.
ApiServiceContext.kt Append
1
2
3
|
fun getApiService(): ApiInterface {
return retrofit.create(ApiInterface::class.java)
}
|
cs |
이제 Main Application으로 돌아가 ApiServiceContext를 생성하고, getApiService() 함수를 이용해 Interface를 끌고오면 API 요청을 진행할 수 있다.
App.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
fun main(args: Array<String>) {
val apiServiceContext = ApiServiceContext("https://jsonplaceholder.typicode.com/", 60)
val call = apiServiceContext.getApiService().test().execute()
if(call.isSuccessful) {
println("***********************************")
println("************Response***************")
println("***********************************")
println(Gson().toJson(call.body()))
println("***********************************")
println("***********************************")
println("***********************************")
} else {
println("Api Request Error")
}
}
|
cs |
API를 실행하고, API의 Status Code가 Success Range라면 True를 반환하니 그에 대한 결과 값으로 Body를 println 하는 로직이다.
실제로 실행해보면 저 로그 외에도 여러가지 많은 로그가 찍혔을 텐데, 그건 아까 추가헀던 logging-interceptor에 의해 발생한 로그이니, 걱정하지 않아도 된다.