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}")
}
})
}
'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 |