본문 바로가기

안드로이드/Network

Retrofit

Retrofit은 OkHttp 기반의 라이브러리로 OkHttp보다 간편하게 사용할 수 있고, 가독성 높은 구조로 네트워크 통신을 가능하게 해준다.

 

특징

  • API 인터페이스 : 요청과 응답을 인터페이스로 쉽게 구현할 수 있다.
  • 어노테이션 : HTTP Method와 Header 등 다양한 작업을 어노테이션으로 정의할 수 있다.
  • 비동기 처리 : Call 객체 및 Coroutine, RxJava와 같이 비동기 처리 가능
  • 직렬화/역직렬화 : Converter Factory 등록으로 데이터를 JSON으로 직렬화 및 역직렬화를 손쉽게 변환할 수 있다.
  • OkHttp Interceptor 추가 작업 
  • 다른 HTTP 라이브러리에 비해 속도가 빠르다.

 

1️⃣ Retrofit Builder

class RetrofitManager {
    fun getRetrofit(){
        Retrofit.Builder()
            .baseUrl(URL)
            .addConverterFactory(Converter.Factory)
            .client(OkHttpClient)
            .build()
    }
}

 

기본 URL 등록

baseUrl에 메인 URL(Host, Port) 등록

class RetrofitManager {
    fun getRetrofit(){
        Retrofit.Builder()
            .baseUrl(URL)
            .build()
    }
    
    companion object{
        private const val URL = "https://api.github.com/"
    }
}

 

ConverterFactory 등록

  • GsonConverterFactory
  • Kotlin Serialization
  • Moshi

 

OkHttp Interceptor

API Key와 같이 매번 헤더에 전송해야 하는 경우와 로깅 등 추가적인 작업을 원하는 경우 OkHttp의 Interceptor를 Retrofit과 같이 사용할 수  있다.

class RetrofitManager {
    fun getRetrofit(){
        Retrofit.Builder()
            .baseUrl(URL)
            .client(provideOkhttpClient(HeaderInterceptor()))
            .build()
    }
    
    private fun provideOkhttpClient(interceptor: HeaderInterceptor): OkHttpClient =
        OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .build()
       
    class HeaderInterceptor : Interceptor {
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain)
                : Response = with(chain) {
            val newRequest = request().newBuilder()
                .addHeader("Authorization", "Bearer ${BuildConfig.GIT_TOKEN}")
                .build()

            proceed(newRequest)
        }
    }
    
    companion object{
        private const val URL = "https://api.github.com/"
    }
}

 

2️⃣ API 인터페이스

Retrofit은 요청과 응답을 아래와 같이 인터페이스로 간단하고 가독성 있게 구현할 수 있으며, 다양한 어노테이션으로 요청 형식을 정의할 수 있다.

 

@GET

interface ApiService {

    @GET("repos/OWNER/REPO/issues")
    suspend fun getIssues(): Response<List<Issues>>
}

@POST

interface ApiService {
    @FormUrlEncoded
    @POST("repos/OWNER/REPO/issues")
    suspend fun postFieldIsuue(
        @Field("title") title: String,
        @Field("body") body: String?,
        @Field("assignees") assignees: List<String>
    ): Response<Issues>
    
    @POST("repos/OWNER/REPO/issues")
    suspend fun postBodyIssue(
        @Body postIssueData: PostIssueData
    ): Response<Issues>
    
}
  • @Body
    데이터를 객체에 담아 전송
  • @Field
    Body 데이터를 각각 전송할 때
  • @FormUrlEncoded
    데이터를 URL 인코딩 형태로 만들어 전송할 때 사용
    (@Field 의 데이터를 형식에 맞춰 requset body에 담아 보내야하기 때문에 형식에 맞추는 인코딩 작업을 해주어야한다.)

 

@PUT

interface ApiService {
    @PUT("repos/OWNER/REPO/issues/{issue_number}/lock")
    suspend fun lockIssue(){
        @Path("issue_number") number: Int,
        @Body lockReason: LockReason = LockReason()
    }
}

data class LockReason{
    val lock_reason: String = "off-topic"
}
  • @Path
    URL에 들어가는 값을 지정한다.
    ex) https://api.github.com/repos/OWNER/REPO/issues/1/lock

 

@PATCH

일부만 수정

interface ApiService {
    @PATCH("repos/OWNER/REPO/issues/{issue_number}")
    suspend fun closeIssue(
        @Path("issue_number") number: Int,
        @Body closeIssue: CloseIssue = CloseIssue()
    ): Response<Issues>
}

data class CloseIssue(
    val state: String = "closed"
)

 

@DELETE

interface ApiService {
    @DELETE("repos/OWNER/REPO/issues/{issue_number}/lock")
    suspend fun unlockIssue(){
        @Path("issue_number") number: Int
    }
}

 

기타 어노테이션

  • @Quary
interface ApiService {
    @GET("repos/OWNER/REPO/issues/")
    fun queryIssue(
        @Query("key") query: String = "value"
    ): Response<Issue>
}


URL의 Quary Parameter로 형식은 아래와 같다.

ex) https://api.github.com/repos/OWNER/REPO/issues/?key=value

 

  • @QuaryMap
interface ApiService {
    @GET("repos/OWNER/REPO/issues/")
    fun queryIssue(
        @QueryMap values: Map<String,String>
    ): Response<Issue>
}

Query가 여러 개인 경우 사용

 

  • @Header
interface ApiService {
    @GET("repos/OWNER/REPO/issues/")
    fun queryIssue(
        @Header("Authorization") token: String,
        @QueryMap values: Map<String,String>
    ): Response<Issue>
}

 

파일 전송

@Multipart
@POST("/api/posts/")
fun writePost(
    @Part photo : MultipartBody.Part,
    @Part("caption") caption : RequestBody?, // 사진과 함께 보내는 추가적인 데이터
    @Part("location") location : RequestBody
):Call<Any>
val photofile = File(경로)
val photo = RequestBody.create("image/jpeg".toMediaTypeOrNull(), photofile)
val filePart : MultipartBody.Part = MultipartBody.Part.createFormData("photo","photo.jpg", photo)
val caption = RequestBody.create(
    "text/plain".toMediaTypeOrNull(),
    text
)
val location = RequestBody.create(
    "text/plain".toMediaTypeOrNull(),
   	text
)

retrofit.writePost(filePart, caption, location)

 

3️⃣ Data Transfer Object

data class Issues(
    @SerializedName("id") val id: Long?,
    @SerializedName("node_id") val nodeId: String?,
    @SerializedName("number") val number: Int?,
    @SerializedName("title") val title: String?,
    @SerializedName("body") val body: String?,
    @SerializedName("user") val user: User,
    @SerializedName("labels") val labels: List<Label>,
    @SerializedName("created_at") val createdAt: String?,
    @SerializedName("closed_at") val closedAt: String?,
    @SerializedName("updated_at") val updatedAt: String?,
    @SerializedName("comments") val comments: Int?,
    @SerializedName("assignee") val assignee: Assignee?,
    @SerializedName("assignees") val assignees: List<Assignee>,
    @SerializedName("locked") val locked: Boolean?,
    @SerializedName("state") val state: String?,
    @SerializedName("milestone") val milestone: Milestone?,
)
  • @SerializedName
    Response JSON 객체의 Key 이름을 작성 

'안드로이드 > Network' 카테고리의 다른 글

OkHttp  (0) 2025.05.13
HttpUrlConnection  (0) 2025.05.12
[안드로이드] 서버 통신 Retrofit2  (0) 2023.11.06