룰루랄라~ 난 이제 GET 요청 POST 요청 다 잘 보내는 API 장인이다!
라고 생각이 들 때, 한번쯤 사진을 전송하는 멀티파트 서버 통신에도 도전해 보시길 바란다.
보통 이미지를 보내는 경우는 무언가 작성할 때, 즉 생성할 때와 관련이 있으므로 POST 요청을 보내야 한다.
POST 요청을 할 때는 body에 값을 담아 보냈었는데,
이미지 / 문서 등 사이즈가 큰 데이터를 서버로 전송할 때는 다른 통신 방식이 필요하다.
멀티파트는 다양한 타입의 데이터를 전송할 때 사용하는 데이터 타입이다.
인스타그램에서 글을 업로드할 때를 생각해 보면,
글
+ 사진
+ 동영상
+ 함께한 유저
를 한 번에 묶어서 서버로 보내야 한다.
이렇게 여러 종류의 데이터를 구별해서 Body에 넣어주기 위한 방법이 Multipart 타입이다.
Type-safe HTTP client for Android and Java인 Retrofit의 문서를 읽으며 어떻게 구현할지 방법을 생각해보자!
원래 POST 요청을 보내기 위해서는 @BODY
를 달아서 한 방에 넣어줬지만, 멀티파트 통신에 쓰이는 모든 요소들은 이미지, 숫자, 문자 모두 @PART
를 달아서 넣어줘야 하며,
이 때 자료형은 모두 Retrofit에서 제공해주는 RequestBody
클래스를 상속받은 자료형이여야 한다.
@MultiPart
@POST("user/photo")
fun updateUser(
@Part photo: RequestBody,
@Part description: RequestBody
) : Call<User>
좀 서운하게 자바 코드만 써줬길래 ,, 코틀린 코드로 번역해봤다.
인터페이스 속 함수는 이렇게 정의하고, 실제로 API를 쏘는 곳에서는 보낼 데이터를 모두 RequestBody
로 변환해주어야 한다.
이렇게 데이터를 보내야 하는 상황을 가정해보자.
여기서 image
는 form-data/file
,
content
와 year/month/day
는 String
,
index
는 Int
형이다.
하지만 뚝심 있게 모두 RequestBody
로 넣어줬다.
나의 경우 image
를 Bitmap 형태로 줘야 했기에 MultipartBody.Part
라는 자료형을 정의하여 사용했다.
image
를 제외한 자료들은 모두 data라는 해쉬맵에 넣어 줬다.
@Multipart
@POST("activity/new")
fun addActivity(
@Part image: MultipartBody.Part?,
@PartMap data: HashMap<String, RequestBody>
): Call<ResponseWrapperEmptyData>
API를 호출할 때는 아래와 같이 호출한다.
HashMap에 담을 때는 RequestBody로 바꿔주고 key값은 API 명세서에 적힌 대로 적어주면 된다.
이미지 비트맵은 BitmapRequestBody로 형변환해준 이후 MultipartBody로 바꾸어 준다. MultipartBody.Part.createFormData()에서 첫 인자로 API 명세서에 적힌 값을 주면 성공적으로 보낼 수 있다.
fun sendAddRequest() {
viewModelScope.launch {
val contentRequestBody : RequestBody = content.toPlainRequestBody()
val yearRequestBody: RequestBody = year.toPlainRequestBody()
val monthRequestBody: RequestBody = month.toPlainRequestBody()
val dayRequestBody: RequestBody = date.toPlainRequestBody()
val indexRequestBody: RequestBody = index.toString().toPlainRequestBody()
val textHashMap = hashMapOf<String, RequestBody>()
textHashMap["content"] = contentRequestBody
textHashMap["year"] = yearRequestBody
textHashMap["month"] = monthRequestBody
textHashMap["day"] = dayRequestBody
textHashMap["index"] = indexRequestBody
val bitmapRequestBody = bitmap?.let { BitmapRequestBody(it) }
val bitmapMultipartBody: MultipartBody.Part? =
if (bitmapRequestBody == null) null
else MultipartBody.Part.createFormData("image", "seojin", bitmapRequestBody)
val response = api.addActivity(bitmapMultipartBody, textHashMap).awaitResponse()
...
}
}
inner class BitmapRequestBody(private val bitmap: Bitmap) : RequestBody() {
override fun contentType(): MediaType = "image/jpeg".toMediaType()
override fun writeTo(sink: BufferedSink) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 99, sink.outputStream())
}
}
private fun String?.toPlainRequestBody() = requireNotNull(this).toRequestBody("text/plain".toMediaTypeOrNull())
마지막 줄은 String
을 Plain Text RequestBody
로 바꿔주는 확장함수인데, 저작권은 l2hyunwoo에게 있다.
구글 검색결과 최상단에 계시네요 ... 축하드립니다