Retrofit2 라이브러리를 사용하기 위해서는 이전 포스팅에서 설명한 것처럼 3가지 구성요소가 필요하다. 아래에 간단하게 설명을 적어보았다.
2021.07.18 - [[Android : Kotlin]/Retrofit2] - 1. Retrofit2 라이브러리 - 시작하기
1. DTO(POJO) Class
: Data Transfer Object, Plain Old Java Object의 약어
: JSON Object의 파싱을 위해 사용
: Response와 Request Body를 관리하는 Model Class
2. Interface
: 사용할 HTTP Method를 정의해놓는 Interface
: POST / GET / PUT / DELETE
3. Retrofit Client Class
: Interface를 사용할 Instance 정의 Class
: BaseURL과 Converter를 설정
1. Permission & Gradle
HTTP 통신을 진행하기 때문에 당연히 Manifest File에 Internet Permission이 추가되어야 한다.
또한, cleartext HTTP와 같은 cleartext 네트워크 트래픽의 사용을 위해 usesCleartextTraffic Flag를 활성화한다.
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
...
<application
...
android:usesCleartextTraffic="true" >
...
</application>
</manifest>
이후, build.gradle(:app)에서 Retrofit2 라이브러리와 GsonConverter 라이브러리를 추가한다.
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.7'
}
여기서 GsonConverter는 JSON 타입의 HTTP Response를 객체나 다른 타입으로 파싱 할 수 있게 도와주는 Converter이다.
2. DTO Class 생성
DTO Class는 Request와 Response Body를 관리하는 Model Class이다. JSON 형식에 따라 만들어줘야 하기 때문에 Android Studio에서는 Plugin으로 쉽게 Class를 만들 수 있게 제공하고 있다.
아래에 Plugin을 소개한다.
Plugin을 사용하면 보다 편리하게 DTO Class를 생성할 수 있지만 본인이 직접 DTO를 커스텀하여 제작하는 상황이 있을 수 도 있다.
data class EmailRequestDTO(
@SerializedName("id") var id : String,
@SerializedName("pw") var pw : String
)
위의 예시는 간단한 로그인 Request DTO Class이다. data class를 생성한 후 자신이 보내고자 하는 JSON 타입에 맞게 커스텀하면 된다.
한 가지 중요한 점은 Request나 Response에 대한 JSON의 Key는 Kotlin에서 직접적으로 지원이 불가능하기 때문에 Retorfit2를 사용할 때 해당 Key와 함께 적절한 JSON을 매핑시키도록 Key에 @SerializedName Annotation을 추가해야 한다. 이렇게 Annotation을 추가하면 각각의 Value를 JSON Key에 매핑할 수 있다.
위의 예시에서 Request로 전송될 JSON은 아래의 형식처럼 보일 것이다.
{ 'id'='xxx', 'pw'='xxx' }
마찬가지로 Response DTO도 생성한다. Response로 오는 JSON을 예시로 Response DTO를 생성해본다.
{
"statusCode":"200",
"statusMessage":"Login Success",
"data":[
{
"id":"xxxxxx",
"nick_name":"xxxxxx",
"email":"xxxxxx",
"profile":"xxxxxx",
}
]
}
위와 같은 형식의 JSON을 아래의 DTO Class를 생성하여 원하는 값을 파싱 할 수 있다.
data class LoginResponse(
@SerializedName("statusCode") val code : String,
@SerializedName("statusMessage") val message : String,
@SerializedName("data") val data : Data? = null
) {
data class Data(
@SerializedName("id") val userID : String,
@SerializedName("nick_name") val userNickName : String,
@SerializedName("email") val userEmail : String,
@SerializedName("profile") val userProfileImage : String,
)
}
마찬가지로 @SerializedName Annotation을 이용하여 Key와 매핑하여 사용할 수 있다. 이렇게 Response DTO까지 생성하면 원하는 Value를 쉽게 얻을 수 있다.
3. Interface 생성
Retrofit2 라이브러리에서 아래와 같은 주석을 확인할 수 있다.
Create an implementation of the API endpoints defined by the {@code service} interface.
여기서 알아야 할 점은 바로 API endpoint이다.
endpoint는 API가 필요한 자원을 받아올 수 있는 위치를 말한다. 즉, 우리가 Request를 보내면 특정 Resoucre를 Response로 받아와야 하는데, 그 Resource가 있는 곳이 바로 endpoint이다. 또한, endpoint는 특정 URL을 포함하고 있다.
아래의 예시로 확인해본다.
https://developer.android.com/studio/intro?hl=ko
https://developer.android.com/studio/install?hl=ko
https://developer.android.com/studio/projects?hl=ko
여기서 endpoint는 /intro?hl=ko, /install?hl=ko, /projects?hl=ko 일 것이다. 그럼 앞에 붙어있는 주소는 무엇일까? 바로 이 endpoint들의 Base가 되는 주소 즉, BaseURL이 된다. 이 BaseURL은 이후에 생성할 Retrofit Builder에서 사용된다.
다시 돌아와서, 우리가 이 endpoint 들에서 특정한 resource를 가져와야 한다면, 어떻게 Resource에 접근할지에 대한 HTTP 통신 방식을 정해야 하고 Method를 작성해야 할 것이다.
아래의 Interface는 Github에 공개되어 있는 Java 코드를 Kotlin으로 변환한 예시이다.
interface RetroBaseApiService {
private val String Base_URL = "http://jsonplaceholder.typicode.com";
@GET("/posts/{userId}")
fun getFirst(
@Path("userId") id : String
) : Call<ResponseGet>
@GET("/posts")
fun getSecond(
@Query("userId") id : String
) : Call<List<ResponseGet>>
@FormUrlEncoded
@POST("/posts")
fun postFirst(
@Field("title") title : String,
@Field("author") author : String
) : Call<ResponseGet>
@PUT("/posts/1")
fun putFirst(
@Body parameters : RequestPut
) : Call<ResponseGet>
@DELETE("/posts/1")
fun deleteFirst() : Call<ResponseBody>
}
첫 번째 함수는 GET방식을 이용하여 (@GET) 어떠한 Response를 받을 것이다. 자세히 보면 endpoint URL 부분에 { }를 사용하여 변수명처럼 사용하고 있는 부분이 있다. @Path라는 Annotation은 path 변수를 지원하는 Annotation이다. 함수에서 id로 들어오는 문자열을 그대로 경로로 지정해주는 역할을 한다. 만약, id로 "abcdef"가 들어온다면 최종적인 endpoint URL은 /posts/abcdef가 되어 Response를 받을 것이다.
두 번째 함수도 마찬가지로 GET 방식을 이용하여 Response를 받는다. 첫 번째 함수와의 차이점은 @Query라는 Annotation을 사용한다는 것이다. @Query는 질의에 사용되는 변수를 위한 Annotation이다. 만약, id로 "abcdef"가 들어온다면 최종적인 endpoint URL은 /posts?userId=abcdef가 될 것이다.
세 번째 함수는 POST 방식 (@POST)을 이용한다. 추가적인 Annotation으로 @FormUrlEncoded와 @Field이 있다. 먼저 @Field는 Key, Value의 형식으로 데이터를 전달할 수 있게 지원하는 Annotation이다. 또한, @Field를 사용할 때는 Form이 Encoding 되어야 하므로, @FormUrlEncoded Annotation을 필수적으로 추가해주어야 한다. 추가로, @Field는 GET 방식에서 사용이 불가능하다.
@FieldMap
@FieldMap은 Field 형식으로 넘겨주는 데이터가 여러 개일 경우 Map형식으로 묶어 보낼 때 사용한다. 이때, Parameter는 주로 HashMap 형태로 넘겨주게 된다.
ex.) @FieldMap parameters : HashMap<String, Object>
@Field와 @Query의 차이점?
@Field는 보안을 위해 url뒤에 붙지 않고 Parameter를 숨긴다.
@Query는 Parameter가 url뒤에 붙게 되어 노출된다.
네 번째 함수는 PUT 방식 (@PUT)을 사용한다. PUT은 특정 자원을 Update 할 때 사용한다. 해당 예시 코드에서는 userId가 1인 사람의 특정 정보를 Update 할 것이다. 여기서는 @Field 대신 @Body를 사용한다. @Body는 전송하는 Parameter가 JSON 형식일 때, Client에서 JSON을 직렬화하여 통째로 보낼 수 있게 한다. 즉, 형태를 매번 만들지 않고 객체를 통해 넘겨준다. 해당 예시 코드에서는 RequestPut이라는 객체를 JSON으로 직렬화 하여 넘겨주는 형태이다.
@Body와 @Field의 차이점?
@Body는 JSON 형태의 객체 자체를 전달한다.
@Field는 전송할 data를 @FormUrlEncoded하여 넘긴다.
결국, 형태의 차이이다. Field로 넘길 정보를 하나하나 적을 필요 없이 객체로 만들어 Body로 넘길 수 있다.
또한, 여러 데이터를 넘겨야 하는 경우 매번 HashMap을 사용하여 전송하는 것은 비효율적이므로 객체를 만드는 것이 효율적일 수 도 있다.
다섯 번째 함수는 DELETE 방식 (@DELETE)을 사용한다. 해당 예시 코드에서는 userId가 1인 사람의 정보를 삭제할 것이다.
모든 함수에서 Return 값으로 Call<>을 하는데 Response가 왔을 때, Callback을 지정하는 것이다. 예를 들어, 첫 번째 함수에서 Call<ResponseGet>은 GET 형식의 Request를 통해 Response가 왔을 때 해당 데이터를 받아 DTO 객체화할 클래스를 말한다.
4. Retrofit Client 생성
Retrofit 객체를 생성하는 Class를 정의한다. 여기서는 이 전에 설명한 BaseURL을 사용하여 실제 통신을 진행할 Retrofit Instance를 생성한다.
아래의 예시를 통해 확인한다.
object RetrofitClient {
private val BASE_URL = "https://www.xxx.co.kr/"
val instance : RetrofitAPI by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(RetrofitAPI::class.java)
}
}
주의 깊게 봐야 할 부분은 instance를 생성하는 부분이다. Kotlin lazy 키워드를 통해 instance를 생성과 동시에 초기화한다. 또한, GsonConverterFactory를 사용하여 JSON 컨버팅을 가능하게 한다. 최종적으로 create( ) Method를 사용하여 instance에 Retrofit과 생성한 Interface를 연결하게 되었다.
5. 최종 구현
Retrofit을 사용하기 위한 모든 준비가 끝났다. Request를 보내는 예시를 아래에서 확인할 수 있다.
val loginRequestParams = EmailRequestDTO( id = "AAA", pw= "BBB" )
RetrofitClient.instance.userLogin(loginRequestParams)
.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if (response.isSuccessful) {
if (response.code() == 200) {
val userID = loginResponse?.data?.userID!!
val userNickName = loginResponse.data.userNickName
val userProfileImage = loginResponse.data.userProfileImage
}
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Log.e("RETRO_ERR", "Error")
}
})
생성한 RetrofitClient의 Instance를 사용하여 Interface의 userLogin이라는 함수를 사용하는 예시이다. Request Parameter로 생성한 EmailRequestDTO를 사용한다.
Retrofit Client 객체에서 제공하는 enqueue( ) Method를 이용하여 HTTP 통신을 위한 Request를 비동기적으로 처리할 수 있다. 이때 onResponse( )는 Request가 성공적이고 Response가 왔을 때, onFailure( )는 Request 자체가 실패한 경우이다.
onResponse( )에서 response.isSuccessful부터는 MainThread에서 작업하게 되고 원하는 정보들을 받을 수 있다. 여기서 중요한 부분은 바로 Code이다.
HTTP 통신에서 Response는 다양한 Status Code를 갖는다. 우리가 원하는 정상적인 응답은 200일 때 처리가 되고, Response로 오는 Status Code가 어떤 건지에 따라 각각 구현을 해주는 것이 맞다고 생각한다.
아래에는 주로 발생하는 Status Code이다.
References
https://square.github.io/retrofit/
https://jaejong.tistory.com/33
https://yoon-dailylife.tistory.com/84
https://medium.com/nerd-for-tech/using-retrofit-2-for-api-calls-674f59355383
https://youngest-programming.tistory.com/135
'Android > 기타 지식' 카테고리의 다른 글
Android API Level 및 버전 코드와 다양한 SDK 버전 (2) | 2021.11.08 |
---|---|
[Firebase] 내 프로젝트에 Firebase Remote Config 적용하기 (0) | 2021.09.19 |
(Retrofit2) 1. 시작하기 (0) | 2021.07.18 |
(SharedPreferences) 2. 사용하기 (0) | 2021.07.13 |
(SharedPreferences) 1. 시작하기 (0) | 2021.07.13 |