API 서버에 이미지 + 내용 데이터 전송하기
서버에서 원하는 데이터 형식은 아래와 같다.
Posting (게시물)을 서버 쪽으로 POST 할 때 2가지의 값을 multipart/form-data 형식으로 받는다.
이미지 파일을 변환하고 나머지 데이터를 param 형식으로 전송하는 방법을 시도했다.
MultiPart 형식으로 데이터를 전송하기 위해 ViewModelScope를 사용했다.
ViewModelScope
viewModelScope는 viewModel 내부에서 사용되며,
lifecycle을 인식하는 코루틴 스코프를 만들 수 있다.
이 방식은 viewModelScope 블럭에서 실행되는 작업들을
별도의 처리 없이 ViewModel이 clear 되는 순간 자동으로 취소하게 할 수 있다.
데이터를 전송하기 위해 multipart로 데이터를 변환하는 작업은 취소하게 되면 작업 취소 과정을 생략할 수 있다.
새로 데이터를 추가하는 함수를 ViewModel 내부에 선언하고, 해당 함수에 viewModelScope를 적용한다.
try 구문 내에서 데이터 변환과 전송을 시도한다.
class MultiPartViewModel: ViewModel(){
fun uploadCommunityPoster(productId:Int,multiCommunityData: MultiCommunityData,filePath : Uri?,
activity: Activity,paramFunc:(ReviewInCommunity?,String?)->Unit){
viewModelScope.launch {
try {
/,,,,,
}catch (e:Exception){
paramFunc(null,"error")
}
}
}
1. 이미지 데이터 변환
@Multipart
@POST("boards/community/posts/")
fun addCommunityPost(
// param
@Part multipartFile: MultipartBody.Part? //image
): Call<Review>
이미지 데이터를 전송하기 위해서는 MultipartBody.Part 형식으로 변환해야 한다.
포스팅 함수를 실행할 때 filepath라는 이미지 Uri 데이터를 가져왔다.
이 데이터를 MultipartBody.Part 형식으로 변환하면 된다.
//이미지 처리
val multipartFile : MultipartBody.Part? = if (filePath!=null) {
val path = getImageFilePath(activity, filePath)
val file = File(path)
val imageRequestBody = file.asRequestBody()
//이미지 데이터 생성
MultipartBody.Part.createFormData(
"multipartFile",
file.name, imageRequestBody
)
}else{ null } //이미지가 없다면 null 처리
path 함수를 통해서 이미지 소스를 String으로 전환한 후 file 형식으로 변환한다.
그 후에 imageReqeustBody 변수에 파일 형식을 RequestBody 형식으로 변환해서 넣어준다.
마지막으로 MultipartBody.Part에 createFormData를 통해 파일 이름과 imageReqeustBody를 넣어준다.
만약에 전달 된 filePath가 없을 경우 전송할 이미지 파일이 없으므로 null 값을 넣는다.
이제 param 데이터를 생성하면 된다.
2. param (나머지 데이터 : type, subject, text, link 등..) 변환
이제 서버로 param 데이터를 전송하려면 나머지 데이터를 하나로 묶어야 한다.
이 과정에서 3가지 방법을 시도했다.
[방법 1] : Hash Map을 통한 RequestBody 매칭
가정 먼저 시도한 방법인데 실패했다.
HashMap에 RequestBody로 변환한 데이터를 넣고 해당 값을 전송하는 방식이었다.
//데이터 변환
val typeRequestBody : RequestBody = multiCommunityData.type.toString()
.toPlainRequestBody()
val subjectRequestBody : RequestBody = multiCommunityData.subject
.toPlainRequestBody()
val textRequestBody : RequestBody = multiCommunityData.text
.toPlainRequestBody()
val linkUrlRequestBody : RequestBody = multiCommunityData.linkUrl
.toPlainRequestBody()
val jointProductRequestBody : RequestBody = multiCommunityData.jointProduct
.toPlainRequestBody()
오류가 발생했는데, 데이터의 형식이 맞지 않는다는 것이었다.
[방법 2] : Hash Map으로 단순 key- value 형식으로 보내기
두 번째로 실패한 방법인데, json 형식에 맞게 키 - 밸류 형식으로 데이터를 전송했다.
방법 2
key -value 형식으로 묶음
val params = hashMapOf<String,String>()
params["type"] = multiCommunityData.type.toString()
params["subject"] = multiCommunityData.subject
params["text"] = multiCommunityData.text
params["linkUrl"] = multiCommunityData.linkUrl
params["jointProduct"] = multiCommunityData.jointProduct
데이터의 형식이 맞지 않는 오류가 발생해서 다시 서버쪽의 데이터 전송 방식을 살펴봤다.
서버 쪽에서는 param 데이터를 받을 때 {}에 감싸진 json 형식이 아닌 body : {} 형식을 원했다.
내가 보낸 데이터는 {d1 : "" , d2 : "" ....} 이러한 json 형식인데,
서버에서는 "body" : {d1 : "" , d2 : "" ....} 형식으로 데이터를 받고 있었다.
[방법 3] : body로 감싼 데이터 전송
body 데이터를 가지는 새로운 model을 선언하고, 해당 body 값으로 데이터를 넘겨주는 방식을 사용했다.
class addCommunityForPOST(
val body : MultiCommunityData
) : Serializable
POST용 클래스를 다시 선언했다.
전달할 값을 그대로 addCommunityForPOST에 넣고 이를 param 데이터로 보내봤다.
val body = BodyReviewForPOST(multiCommunityData)
retrofit을 연결하고, 해당 데이터를 전송했더니 결과는 성공이었다.
서버와 API 통신을 할 경우 당연한 얘기지만,
서버에서 받는 방식으로 데이터를 변환해서 보내야 한다.
멀티파트를 사용하는 곳에서 에러가 발생한 줄 알았지만 멀티파트 전송 부분은 정상적으로 작동했다.,