[Android] 레트로핏(Retrofit2) 사용법

2021. 11. 21. 00:00·Android
Android Studio 4.2.2
Android SDK 28
Kotlin 1.5.21

 

 

Retrofit2

http 통신을 위한 라이브러리. 안드로이드에서 REST API를 간편하게 사용할 수 있는 클라이언트 API이다. (공식 설명글)

사용법

의존성 추가

build.gradle (:app)

dependencies {

    implementation 'com.squareup.retrofit2:retrofit:2.9.0' // http 통신 라이브러리
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // gson

}

레트로핏 객체 생성

레트로핏 객체를 싱글톤으로 사용하기 위해 Application을 상속받는 클래스 App에서 다음과 같이 레트로핏 객체를 리턴하는 메소드를 선언해준다.

<manifest>

    <application
        android:name=".App" // App 클래스 사용 설정
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round">

        ...
    </application>

</manifest>
val BASE_URL = "http://00.00.00.00/"
lateinit var retrofit: Retrofit

@JvmName("getRetrofit1")
fun getRetrofit(): Retrofit {

    if (!this::retrofit.isInitialized) {
        val client = OkHttpClient.Builder()
            .readTimeout(50000, TimeUnit.MILLISECONDS) // 읽기 최대 시간 설정
            .connectTimeout(50000, TimeUnit.MILLISECONDS) // 연결 최대 시간 설정
            .build()
        retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
    }
    return retrofit
}

API 인터페이스 생성

사용할 각 API에 맞는 메소드를 정의한다. 레트로핏은 REST API 사용을 전제로 하고 있어, GET, POST, PATCH, PUT, DELETE 메소드 등으로 요청을 보낼 수 있다.

 

메소드는 다음과 같은 형식으로 정의한다.

@요청 메소드("API 요청 경로")
fun 메소드명(): Call<응답 받을 데이터 클래스>

예를 들어 GET으로 http://00.00.00.00/group 경로에 요청한다면 다음과 같다. 응답값은 CustomResponse형식으로 가정했다.

@GET("/group")
fun getGroupList(): Call<GroupListResponse>

Annotation

API 인터페이스를 생성할 때 레트로핏에서 제공하는 annotation으로 간편하게 메소드를 정의할 수 있다.

요청 메소드 관련

아래는 REST API에서 가장 많이 쓰는 요청 메소드이다.

@GET : Read 데이터 조회

@POST : Create 데이터 생성

@PATCH : Update 한 데이터의 일부 수정

@PUT : Update 한 데이터 전체 수정

@DELETE : Delete 데이터 삭제

PATCH vs PUT
유저가 아이디, 이름, 닉네임 세가지 데이터를 가지고 있을 때, PATCH와 PUT의 차이는 다음과 같다.
PATCH : 유저A의 닉네임만 변경하고자 할 때
PUT : 유저A의 아이디, 이름, 닉네임 모두를 변경할 때

요청 값 관련

@Query

url 상에서 key-value 형태의 요청값

ex)http://00.00.00.00/dictionary?word=안녕

@GET("/dictionary")
fun findWord(@Query("word") word: String): Call<CustomResponse>

@SerializedName

Json 형태의 데이터와 객체 형태 데이터간 키값 매칭

 

예를 들어 다음과 같은 Json이 있고 이를 객체로 만들 때 다음과 같이 데이터 클래스를 정의한다.

{
    "id" : "win123",
    "email" : "win@email.com",
    "nickname" : "win"
}
data class User(
    @SerializedName("id")
    val id: String,
    @SerializedName("email")
    val mail: String,
    @SerializedName("nickname")
    val name: String,
)

@Expose

객체를 Json으로 만들 때, 해당 값이 null일 경우 자동 생략

 

예를 들어 데이터 클래스가 다음과 같고 name변수에만 값이 존재한다면 다음과 같은 Json이 만들어진다.

data class User(
    @Expose
    @SerializedName("id")
    val id: String?,
    @Expose
    @SerializedName("email")
    val mail: String?,
    @SerializedName("nickname")
    val name: String,
)
val user = User(null, null, "blue")
{
    "nickname" : "blue"
}

@Body

html에서 body부분에 위치하는 요청값. 커스텀 데이터 클래스를 사용하고자 할 때 사용.

 

예를 들어, html body에 userId, scheduleName, date, time을 넣어 요청할 때 다음과 같이 한다.

data class Schedule(
    @SerializedName("userId")
    val userId: String,
    @SerializedName("scheduleName")
    val scheduleName: String,
    @SerializedName("date")
    val date: Int,
    @Expose
    @SerializedName("time")
    val time: Int,
)
@POST("/schedule")
fun postSchedule(@Body schedule: Schedule): Call<CustomResponse>

@Field

html에서 body부분에 위치하는 요청값으로 url 상에서는 표시되지 않는다. body에 넣을 값이 적어 굳이 커스텀 데이터 클래스를 만들지 않고 간단하게 요청하고 싶을 때 사용한다. 반드시 @FromUrlEncoded도 같이 써줘야 한다.

 

예를 들어, html body에 nickname을 넣어 요청할 때 다음과 같이 한다.

@FromUrlEncoded
@POST("/schedule")
fun postSchedule(@Field schedule: Schedule): Call<CustomResponse>

@FromUrlEncoded

요청 시 본문에 url 인코딩 사용한다는 표시. @Field로 요청을 보낼 때 함께 사용.

 

@Path

요청 경로에 값을 넣어야 하는 경우 사용한다. 요청 경로에는 {}로 감싼 변수명을 작성하고 이 변수명을 @Path 괄호 안에 명시하면 된다.

@PATCH("/user/{userIdx}/password")
fun patchPassword(@Path("userIdx") userIdx: Int): Call<DefaultResponse>

@Multipart

멀티파트의 방식으로 요청을 보낼 때 사용.

보통 파일을 담아 보낼 때 사용한다.

@Multipart
@POST("/diary")
fun postDiary(@Part("image") image: MultipartBody.Part): Call<DefaultResponse>

@Part

멀티파트 요청할 때 바디 또는 헤더에 넣을 데이터에 붙이는 어노테이션

Body 커스텀 클래스를 만들어 @Body를 사용하고자 해도 멀티파트 요청 시에는 반드시 @Part로 따로따로 나눠서 보내야 한다.

@Multipart
@POST("/resource/image")
fun postImages(
    @Part images: ArrayList<MultipartBody.Part>,
    @Part("size") size: Int,
    @Part("date") date: String
): Call<DefaultResponse>

API 인터페이스 객체 생성

어떤 메소드가 있어야 하는지 인터페이스에서 정의했다면, 직접 메소드를 구현하고 사용하기 위해 인터페이스 객체를 생성한다.

val api: ApiInterface = App.getRetrofit().create(ApiInterface::class.java)

API 메소드 구현

인터페이스에서 정의한 메소드를 구현한다. 아래는 getGroupList() 메소드를 간단히 구현한 예시이다.

api.getGroupList().enqueue(object : Callback<GroupListResponse> {
    override fun onResponse(
        call: Call<GroupListResponse>,
        response: Response<GroupListResponse>
    ) {
        /* 요청 성공 시 동작 */
        
        val res: GroupListResponse? = response.body()
        if (res == null) { 
            /* 응답을 받지 못했을 경우 동작 */
            return
        }

        /* 응답을 받았을 경우 동작 */
        
    }

    override fun onFailure(call: Call<GroupListResponse>, t: Throwable) {
        /* 요청 실패 시 동작 */
        Log.e(TAG, "onFailure: getGroupList(): ${t.message.toString()}", )
    }

})

예시

단일 이미지 포함한 요청

GET 예시: 데이터 조회

명세서

요청 경로

METHOD URL
GET http://00.00.00.00/news/{category}

요청 내용

분류 이름 타입 필수여부 설명
Query page int Y 페이지 번호

응답 메세지

이름 타입 필수여부 설명
id int Y 뉴스 아이디
title String Y 뉴스 제목
image String N 뉴스 썸네일
content String N 본문 미리보기 내용
date String Y 작성일
[
    {
        "id": 1028,
        "title": "22일부터 기온 ‘뚝’…파카 준비하세요",
        "image": "http://00.00.00.00/image/1028",
        "content": "다음 주 월요일 오후부터 추위가 다시 찾아온다. 오는 일요일 오후부터 ...",
        "date": "2021.11.18 13:40"
    },
    {
        "id": 1010,
        "title": "“119죠? 길에 사람이 쓰러져 있어요” CCTV 보고 인공지능이 응급 신고",
        "image": "http://00.00.00.00/image/1010",
        "date": "2021.11.17 20:36"
    }
]

ApiInterface.kt

@GET("/news/{category}")
fun getNewsList(
    @Path("category") category: String,
    @Query("page") page: Int
): Call<ArrayList<News>>

News.kt

data class News(
    @SerializedName("id")
    val id: Int,
    @SerializedName("title")
    val title: String,
    @Expose
    @SerializedName("image")
    val image: String,
    @Expose
    @SerializedName("content")
    val content: String,
    @SerializedName("date")
    val date: String
)

MainActivity.kt

fun loadList(category: String, page: Int) {
    api.getNewsList(category, page).enqueue(object : Callback<ArrayList<News>>{
        override fun onResponse(
            call: Call<ArrayList<News>>,
            response: Response<ArrayList<News>>
        ) {
            val res: ArrayList<News> = response.body() ?: return // 응답 받지 못했을 경우
            
            // 응답 값 있을 때 동작
            
        }

        override fun onFailure(call: Call<ArrayList<News>>, t: Throwable) {
            Log.d(TAG, "onFailure: ${t.stackTrace}")
        }

    })
}

POST 예시: 다중 이미지를 포함한 게시글 작성

명세서

요청 경로

METHOD URL
POST http://00.00.00.00/news

요청 내용

분류 이름 타입 필수여부 설명
Body title String Y 글 제목
  content String Y 글 내용
  images ArrayList<ImageFile> N 이미지 리스트

응답 메세지

이름 타입 필수여부 설명
isSuccess boolean Y 성공 여부
message String Y 결과 메시지
{
    "isSuccess": true,
    "message": "글 작성 성공"
}

ApiInterface.kt

@Multipart
@POST("/news")
fun postNews(
    @Part("title") title: String,
    @Part("content") title: String,
    @Part("images") title: ArrayList<MultipartBody.Part>?
): Call<ResultResponse>

ResultResponse.kt

data class ResultResponse(
    @SerializedName("isSuccess")
    val isSuccess: Boolean,
    @SerializedName("message")
    val content: String
)

MainActivity.kt

fun uploadNews(title: String, content: String, images: ArrayList<MultipartBody.Part>? = null) {
    api.postNews(title, content, images).enqueue(object : Callback<ResultResponse>{
        override fun onResponse(
            call: Call<ResultResponse>,
            response: Response<ResultResponse>
        ) {
            val res: ResultResponse = response.body() ?: return // 응답 받지 못했을 경우
            
            if (res.isSuccess)
                Log.d(TAG, "onResponse: 업로드 성공")
            else Log.d(TAG, "onResponse: 업로드 실패")
            
        }

        override fun onFailure(call: Call<ResultResponse>, t: Throwable) {
            Log.d(TAG, "onFailure: ${t.stackTrace}")
        }

    })
}

PUT 예시: 댓글 수정 요청

명세서

요청 경로

METHOD URL
PUT http://00.00.00.00/news/{newsId}/comment

요청 내용

분류 이름 타입 필수여부 설명
Query commentId int Y 댓글 번호
Body content String Y 댓글 내용

응답 메세지

이름 타입 필수여부 설명
isSuccess boolean Y 성공 여부
message String Y 결과 메시지
{
    "isSuccess": true,
    "message": "댓글 작성 성공"
}

ApiInterface.kt

@FormUrlEncoded
@PUT("/news/{newsId}/comment")
fun putComment(
    @Path("newsId") newsId: Int,
    @Query("commentId") commentId: Int,
    @Field("content") content: String
): Call<ResultResponse>

ResultResponse.kt

data class ResultResponse(
    @SerializedName("isSuccess")
    val isSuccess: Boolean,
    @SerializedName("message")
    val content: String
)

MainActivity.kt

fun editComment(newsId: Int, commentId: Int, content: String) {
    api.postNews(newsId, commentId, content).enqueue(object : Callback<ResultResponse>{
        override fun onResponse(
            call: Call<ResultResponse>,
            response: Response<ResultResponse>
        ) {
            val res: ResultResponse = response.body() ?: return // 응답 받지 못했을 경우
            
            if (res.isSuccess)
                Log.d(TAG, "onResponse: 수정 성공")
            else Log.d(TAG, "onResponse: 수정 실패")
            
        }

        override fun onFailure(call: Call<ResultResponse>, t: Throwable) {
            Log.d(TAG, "onFailure: ${t.stackTrace}")
        }

    })
}
728x90
저작자표시 비영리 변경금지 (새창열림)

'Android' 카테고리의 다른 글

[Android] RecyclerView 사용법  (0) 2022.10.22
[Android] HTTP 통신 시 Retrofit2로 헤더에 자동으로 토큰 전송하기  (0) 2021.11.24
[Android] 직접 만든 안드로이드 프로젝트 템플릿  (0) 2021.11.18
[Android] 권한 확인 및 요청  (0) 2021.11.15
[Android] 액티비티 화면전환 애니메이션  (0) 2021.11.12
'Android' 카테고리의 다른 글
  • [Android] RecyclerView 사용법
  • [Android] HTTP 통신 시 Retrofit2로 헤더에 자동으로 토큰 전송하기
  • [Android] 직접 만든 안드로이드 프로젝트 템플릿
  • [Android] 권한 확인 및 요청
Wintinue
Wintinue
201 Created!
  • Wintinue
    Win Record
    Wintinue
    • 📘 Post (68)
      • Android (32)
      • Nest.js (1)
      • NGINX (1)
      • Error (10)
      • AWS (1)
      • Git (3)
      • IT용어 (4)
      • CMD (2)
      • Language (5)
        • PHP (3)
        • Java (2)
      • Project (5)
        • 개인 프로젝트 (3)
        • 팀 프로젝트 (2)
  • 링크

    • Github
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.3
Wintinue
[Android] 레트로핏(Retrofit2) 사용법
상단으로

티스토리툴바