본문 바로가기
Android/기타 지식

(Retrofit2) 2.사용하기

by jaesungLeee 2021. 8. 25.

Retrofit2 라이브러리를 사용하기 위해서는 이전 포스팅에서 설명한 것처럼 3가지 구성요소가 필요하다. 아래에 간단하게 설명을 적어보았다.

2021.07.18 - [[Android : Kotlin]/Retrofit2] - 1. Retrofit2 라이브러리 - 시작하기

 

1. Retrofit2 라이브러리 - 시작하기

1. Retrofit2 란? Android에서 REST API 통신을 지원하기 위한 라이브러리 Type-Safe 한 HTTP 클라이언트 라이브러리 전달받은 데이터를 Client가 필요한 형태의 객체로 전달 받을 수 있음 존재하는 HTTP 통신

jslee-tech.tistory.com

 

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으로 변환한 예시이다.

https://github.com/kor45cw/Retrofit-tutorial/blob/master/app/src/main/java/com/kor45cw/retrofitexample/Retrofit/RetroBaseApiService.java

 

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/

 

Retrofit

A type-safe HTTP client for Android and Java

square.github.io

https://jaejong.tistory.com/33

 

[안드로이드] Retrofit2 '레트로핏' - 기본 사용법

Retrofit2 - REST API 통신 라이브러리 'Retrofit' - REST통신 라이브러리 기본 개념 & 사용법 통신 라이브러리 중 가장 많이 사용되는 대표적인 라이브러리 ( Squareup 사의 라이브러리) Retrofit 이란? REST API..

jaejong.tistory.com

https://yoon-dailylife.tistory.com/84

 

Android) Retrofit2 작동 원리 알아보기

대부분의 안드로이드 개발자는 통신 라이브러리로 Retrofit을 사용하고 있습니다. 오늘은 Retrofit의 특징 및 내부 작동방식에 대해 이해해보려고 합니다. Retrofit REST API 통신을 위해 구현된 통신 라

yoon-dailylife.tistory.com

 

https://medium.com/nerd-for-tech/using-retrofit-2-for-api-calls-674f59355383

 

Using Retrofit-2 for API Calls

Retrofit is a type-safe network library that makes android applications easy to consume RESTful web services. It is built by Square and…

medium.com

https://youngest-programming.tistory.com/135

 

[안드로이드] Retrofit2 @Body @Field 차이

프로젝트에서 AWS와 노드로 서버를 구성하고 안드로이드 클라이언트 Retrofit2 통신에서 평소 @Field를 사용했는데, 계속 Json으로 서버에서 못받고 이상한 값이 넘어오는 현상이발생했다. 서버문제

youngest-programming.tistory.com