MultiPart

Yuree Choi·2022년 6월 3일
1
post-thumbnail

Multipart

1. MultiPart란?

말 그대로 Multi(여러) Part(부분)으로 데이터를 보내주는 것!
합동세미나에서 했던 당근 글쓰기 뷰 서버 파트에서는
제목, 가격, 내용에 추가적으로 '이미지'까지 보내야함.

근데 내가 원래 했던 서버통신의 content-typeapplication/json인데
json으로는 이미지나 파일을 보내주기 어렵다!
-> 그래서 MultiPart/form-data 사용!!

그래서 멀티파트를 어케 쓰냐면요.

2. MultiPart 사용 : Service

//WriteService.kt
interface WriteService {
    @Multipart
    @POST("/item")
    fun postItem(
        @Part image: List<MultipartBody.Part>,
        @PartMap data: HashMap<String, RequestBody>
    ): Call<ResponseWrite>
}
  • @MultiPart 어노테이션을 함수 위에 붙이자! = 이 함수는 멀티파트로 통신한다
  • 보낼 데이터를 @Part image 처럼 @Part 어노테이션 붙여서 정의
  • 서버 피셜 image 한 장씩 보낼 필요 없이 List로 보내도 된다고 했다!
  • PartMap은 Map이라는 자료구조를 이용한건데, [key, value] 쌍으로 저장!
  • title, price, content를 따로따로 저장해주면 귀찮으니까 HashMap으로 묶어줌
  • 리턴타입은 Call<ResponseWrite>로 해주었습니다~!

3. MultiPart

(근데여기서하나..! 나는 뷰모델을 써서 내용을 다 WriteViewModel 안에 썼다는거!)

//image는 잠시 뒤로 미뤄두고 우선 string들 코드설명부터 적어볼게요
fun multipart(
        title: String,
        price: String,
        content: String,
        image: List<Bitmap>
    ) {
        val titleRequestBody =
            title.toRequestBody("text/plain".toMediaTypeOrNull())
        val priceRequestBody =
            price.toRequestBody("text/plain".toMediaTypeOrNull())
        val contentRequestBody =
            content.toRequestBody("text/plain".toMediaTypeOrNull())

        val requestBodyHashMap = HashMap<String, RequestBody>()
        requestBodyHashMap["title"] = titleRequestBody
        requestBodyHashMap["price"] = priceRequestBody
        requestBodyHashMap["contents"] = contentRequestBody

price는 원래 int라고 명세서에 써있지만.. string으로 보내도 서버 측에서는 문제가 없다고 합니다 헤헹

  • toRequestBody()RequestBody로 바꿔주는 역할

  • "text/plain".toMediaTypeOrNull())에서 toMediaTypeOrNull()은 그 type으로 바꿔주는 역할이다. 여기서는 Stringtext/plain으로 바꿔주는 거겠징

  • title.toRequestBody("text/plain".toMediaTypeOrNull())text/plain타입의 requestbody가 된 것이고

  • requestBodyHashMap["title"] = titleRequestBody
    title이라는 key에 titleResquestBody라는 value 할당!
    다른 price, contents도 마찬가지


이제 이미지를 볼까요

 companion object {
        class BitmapRequestBody(private val bitmap: Bitmap) : RequestBody() {
            override fun contentType(): MediaType? {
                return "image/png".toMediaTypeOrNull()
            }

            override fun writeTo(sink: BufferedSink) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 99, sink.outputStream()) //99프로 압축
            }
        }
    }
  • 이 함수는 BitMapRequestBody로 바꿔주는 클래스
  • RequestBody 상속받아 오버라이딩 함
  • override fun contentType() 이건 어떤 타입으로 바꿀거냐임.
    여기선 png로 설정해줌!
  • override fun writeTo 이건 이미지를 압축하는 함순데 png로 압축을 할거임. 99% 압축한다는 의미
for (i in 0 until image.size) {
            val imageRequestBody = BitmapRequestBody(image[i])
...
  • for문 보면 i가 이미지 사이즈 미만까지 도는거다
  • val imageRequestBody = BitmapRequestBody(image[i]) 는 배열의 i번째 인덱스를 위에서 만들었던 BitmapRequestBody 함수에 넣어주는 거! 얘가 이제 requestbody가 됨.
  • 근데 우리는 WriteService에서@Part image: List<MultipartBody.Part> 라고 해놨으니 멀티파트로 바꿔줘야겠지요?
    아래에서 해봅시다
//위 코드에 이어서 작성
val imageMultipartBody: MultipartBody.Part =
                MultipartBody.Part.createFormData(
                    "image",
                    "and" + System.currentTimeMillis()
                        .toString() + i.toString(), // 승현 - index 를 활용해 이미지 이름을 구분합니다.
                    imageRequestBody
                )
            imageListMultipartBody.add(imageMultipartBody)
        }
  • MultipartBody.Part 타입이고
  • createFormData 함수에는 넣어야 할 값이 세개다
  1. 처음은 이름! 키 값과 같은 역할을 함. 그러니 이미지로 써줌
  2. 다음은 파일 이름. 겹칠까봐 System.currentTimeMillis() 추가
  3. 마지막은 어떤걸 멀티파트로 만들어줄건데? 를 넣어야 함. imageRequestBody 얘를 멀티파트로 만들어준다는 말!

imageListMultipartBody.add(imageMultipartBody)
= 리스트에 방금 만든 imageMultipartBody를 추가해줌

 val call: Call<ResponseWrite> =
            ServiceCreator.writeService.postItem(imageListMultipartBody, requestBodyHashMap)

        call.enqueue(object : Callback<ResponseWrite> {
            override fun onResponse(
                call: Call<ResponseWrite>,
                response: Response<ResponseWrite>
            ) {
                if (response.isSuccessful) {
                    Log.d("NetworkTest", "success")
                    _isMultiPartSuccess.call()
                } else {
                    Log.d("NetworkTest", "connected but failed")
                }
            }

            override fun onFailure(call: Call<ResponseWrite>, t: Throwable) {
                Log.e("NetworkTest", "error:$t")
            }
        })
  • ServiceCreator.writeService.postItem()에 파라미터로 imageListMultipartBody, requestBodyHashMap를 넣어주고
  • 그 이후는 서버 하던대로 해주었습니다ㅋㅋ

4. MultiPart 호출

멀티파트 여태까지 뷰모델에 썼으니까 호출해줘야겠죠?

WriteFragment.kt

 private fun clickEvent() {
        binding.tvFinish.setOnClickListener {
            if (writeViewModel.writeTitle.value?.length == 0 || writeViewModel.writePrice.value?.length == 0 || writeViewModel.writeContent.value?.length == 0 || writeViewModel.selectedImageList.value?.size == 0) {
                Toast.makeText(requireContext(), "채워지지 않은 부분이 있습니다", Toast.LENGTH_SHORT).show()
            } else {
                writeViewModel.multipart(
                    requireNotNull(writeViewModel.writeTitle.value),
                    requireNotNull(writeViewModel.writePrice.value),
                    requireNotNull(writeViewModel.writeContent.value),
                    requireNotNull(writeViewModel.selectedImageList.value).map { requireNotNull(it.first.image) }
                )
            }
        }
        binding.btnBack.setOnClickListener {
            requireActivity().finish()
        }
    }
  • if에는 내용 채워지지 않았을 때 넘어가지 말라고 토스트메시지 뜨게 했음
  • 내용 채워져있을 때(else) 뷰모델 멀티파트 함수 호출~!
  • Livedata는 처음에 아무 값도 할당하지 않으면 null임. 그래서 requireNotNull을 사용해주는데, value가 null이 아닐 때 갖다 쓴다는 의미! null이 아니라는 걸 보장해주는것..

본 내용은 한승현씨 강의를 바탕으로 작성했음을 알려드립니다!~ ㅋㅋ
한교수님 짱 ㅋㅋ

profile
마음만은 잔디밭

0개의 댓글