# Table of Contents

# Retrofit

Retrofit은 안드로이드에서의 HTTP 통신을 위한 라이브러리다.

# 설정

Retrofit을 사용하기 위해 다음 의존성을 추가한다.

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

그리고 AndroidManifest.xml에 인터넷 권한을 추가해야한다.

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

에뮬레이터에서 로컬 호스트로 통신할 때는 다음 IP를 사용한다.

val BASE_URL = "http://10.0.2.2:${YOUR_PORT}"

# 사용법

Retrofit을 사용하려면 먼저 인터페이스를 정의해야한다. HTTP 메소드에 따른 정의 방법은 다음과 같다.

# GET

HTTP GET 메소드의 경우 @GET 어노테이션을 사용한다. @Query 어노테이션으로 Query Parameter를 함께 전송할 수 있다.

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface PostApi {

    // HTTP GET with query parameter
    @GET("posts")
    fun getPosts(@Query("page") page: Int, @Query("size") size: Int): Call<GetPostsResponse>
}

@Path 어노테이션으로 Path Variable도 설정할 수 있다.

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path

interface PostApi {

    // HTTP GET with path variable
    @GET("post/{id}")
    fun getPostById(@Path("id") id: Long): Call<GetPostByIdResponse> 
}

# POST

HTTP POST 메소드의 경우 @POST 어노테이션을 사용한다. @Body 어노테이션으로 HTTP Body도 설정할 수 있다.

import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

interface AuthApi {
    @POST("/auth/login")
    fun login(@Body request: LoginRequest): Call<LoginResponse>
}
import com.google.gson.annotations.SerializedName

data class LoginRequest (
    @SerializedName("email") val email: String,
    @SerializedName("password") val password: String
)
import com.google.gson.annotations.SerializedName

data class LoginResponse (
    @SerializedName("user_id") val userId: Long,
    @SerializedName("access_token") val accessToken: String,
    @SerializedName("refresh_token") val refreshToken: String
)

HTTP Body에 실제로 포함되어 전송되는 데이터 형태는 다음과 같다.

{
    "email": "paul@gmail.com",
    "password": "1234"
}

@FormUrlEncoded, @Field 어노테이션을 사용하면 application/x-www-form-urlencoded 타입의 데이터도 전송할 수 있다.

import retrofit2.http.POST
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Field

interface PostApi {

    @FormUrlEncoded
    @POST("auth/login")
    fun login(
        @Field("email") email: String,
        @Field("password") password: String
    ): Call<LoginResponse>
}

HTTP Body에 실제로 포함되어 전송되는 데이터 형태는 다음과 같다.

email=paul@gmail.com&password=1234

@Multipart, @Part 어노테이션을 사용하면 multipart/form-data 타입의 데이터도 전송할 수 있다. 이 타입은 보통 이미지 업로드에 많이 사용된다.

import retrofit2.http.POST
import retrofit2.http.Multipart
import retrofit2.http.Part

interface PostApi {

    @Multipart
    @POST("post")
    fun addPost(
        @Part("writer_id") writerId: RequestBody,
        @Part("title") title: RequestBody,
        @Part("content") content: RequestBody,
        @Part images: List<MultipartBody.Part>?
    ): Call<AddPostResponse>
}

# PATCH

HTTP PATCH 메소드의 경우 @PATCH 어노테이션을 사용한다.

import retrofit2.http.PATCH

interface PostApi {

    @PATCH("post/{id}")
    fun updatePostById(
        @Path("id") id: Long, 
        @Body UpdatePostByIdRequest)
    : Call<UpdatePostByIdResponse>
}

# DELETE

HTTP DELETE 메소드의 경우 @DELETE 어노테이션을 사용한다.

import retrofit2.http.DELETE

interface PostApi {
    @DELETE("post/{id}")
    fun deletePostById(@Path("id") id: Long)
}

@Headers를 사용하여 헤더를 지정할 수 있다.

import retrofit2.http.Headers

@Headers({
    "Accept: application/json",
    "User-Agent: Your-App-Name",
    "Cache-Control: max-age=640000"
})
interface PostApi {

    @GET("post/{id}")
    fun getPostById(
        @Header("Authorization") String authorization,
        @Path("id") id: Long
    ): Call<GetPostByIdResponse> 
}

@Header를 사용하여 헤더를 동적으로 추가할 수 있다. 파리미터가 null이면 헤더가 추가되지 않는다.

import retrofit2.http.Header

interface PostApi {

    @GET("post/{id}")
    fun getPostById(
        @Header("Authorization") String authorization,
        @Path("id") id: Long
    ): Call<GetPostByIdResponse> 
}

# 통신하기

다음과 같이 인터페이스를 정의했다고 가정하자.

import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

interface AuthApi {
    @POST("/auth/login")
    fun login(@Body request: LoginRequest): Call<LoginResponse>
}

이 인터페이스로 다음과 같이 API 역할을 할 구현체를 생성한다.

val client = OkHttpClient.Builder().build()

val authApi = Retrofit.Builder()
    .client(client)
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(AuthApi::class.java)

이제 구현체로 통신을 할 수 있다. execute()를 사용하면 현재 스레드에서 동기적으로 통신한다.

val response: LoginResponse = authApi.execute()

enqueue()를 사용하면 별도의 작업 스레드에서 비동기적으로 통신한다. 따라서 통신이 끝났을 때 실행할 Callback을 함께 전달해야한다.

authApi.enqueue(object: Callback<LoginResponse> {

	override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
		if(response.isSuccessful()) {
			// 2xx Success
		} else { 
            // 4xx, 5xx Error
            val code = response.code()
            val errorBody = response.errorBody()
		}
	} 
	
	override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
		// Connection Error
	}   
})