본문 바로가기

Kotlin_study

[Kotlin] Retrofit2 Multipart 이미지와 데이터 보내기

이번 방학 동안 앱런칭 프로젝트를 진행했다. 강의를 듣고 처음 진행하는 프로젝트이기도 했고, 생각했던 기능들을 사용하기 위해서 검색도 많이 하고, 기존 공부했던 자료를 많이 보면서 진행했다. 그 중 가장 시간을 많이 잡아먹었던게 이미지와 데이터를 한번에 보낼 때 interface를 어떻게 짜느냐였는데, 이에 대해 어떻게 했는지 이야기 해보려 한다.

보내야 하는 자료형(postman)

우선 imageList에는 이미지가 여러 장 들어갈 수도, 아예 안 들어갈 수도 있다.

그리고 postData에는 userId, category, title, contents, hashtagList가 들어간다. 이 때 content type이 application/json이어서 postData는 json으로 변환하여 보내주었다.

class StoryService {
 
    fun writeStory(token : String?, pathList : List<MultipartBody.Part?>, id : String, title: String,
                   category: String, contents: String, hashtagList : List<String>?){
        val retrofit = Retrofit.Builder().baseUrl("서버 주소").addConverterFactory(GsonConverterFactory.create()).build()
        val storyService = retrofit.create(StoryInterface::class.java)
        val jsonArray = arrayListOf<String>()
        if((hashtagList?.isNotEmpty())!!){
            for(hashtag in hashtagList){
                jsonArray.add("\"$hashtag\"")
            }
        }
        val body = RequestBody.create(MultipartBody.FORM,"")
        val emptyPart = MultipartBody.Part.createFormData("imageList","",body)
        val emptyList = arrayListOf<MultipartBody.Part>()
        emptyList.add(emptyPart)

        val jsonObject = JSONObject("{\"userId\":\"${id}\",\"category\":\"${category}\",\"title\":\"${title}\",\"contents\":\"${contents}\",\"hashTags\":${jsonArray}}").toString()
        val jsonBody = RequestBody.create(parse("application/json"),jsonObject)

        storyService.writeStory("Bearer $token", if(pathList.isEmpty()){
            emptyList
        } else{
              pathList}, jsonBody).enqueue(object : Callback<StoryResponse>{
            override fun onResponse(call: Call<StoryResponse>, response: Response<StoryResponse>) { 
            }
            override fun onFailure(call: Call<StoryResponse>, t: Throwable) {
            }
            })
    }

API를 호출하면 인자로 유저의 accessToken, 이미지 경로를 담고 있는 pathList, id, 등 전달 할 것들을 받아온다. 그 다음 hashtag가 없을 때는 emptyList를 전달해야 하므로 있을 때만 json 형식으로 변환하여 jsonArray에 넣어주었다.

 

그 다음은 Multipart.Part 형으로 빈 리스트를 생성해주었는데, 이는 만약 전달받은 pathList가 비어있다면, 즉 이미지를 선택하지 않았을 때에는 빈 리스트를 전달해주어야 하기 때문에 생성하였다. 여기서 emptyPart에 들어가는 인자들은 서버에서 받는 키값, 파일 이름, 파일 객체이다. 그래서 여기 키값을 제대로 넣었는지도 확인해야 한다.

 

그 아래 jsonObject를 통해 서버에 보낼 형태로 Json 객체를 만들어주고, jsonBody를 통해 RequestBody 형태로 변환해주었다. 

interface StoryInterface {

    @Multipart
    @POST("경로")
    fun writeStory(
        @Header("Authorization") Authorization: String,
        @Part imageList : List<MultipartBody.Part?>,
        @Part("postData") postData : RequestBody
    ) : Call<StoryResponse>
}

다음은 interface이다. 우선 Mutipart 형식으로 데이터를 전달하기 위해서는 @Multipart 어노테이션을 꼭 추가해줘야 한다. 그리고 우리는 업로드를 할거니까 POST 메서드를 사용해준다. 헤더같은 경우에는 필자 같은 경우에는 유저의 토큰을 함께 전달해야 했어서 추가했다. 그 외의 데이터 앞에는 @Part 어노테이션을 추가해주고 이미지 같은 경우에는 MultipartBody.Part의 리스트 형태로, 나머지 데이터는 키값과 함께 RequestBody 형태로 전달해주었다.

 

이번 프로젝트를 진행하면서 400에러를 가장 많이 봤던 부분이었다. 참고했던 블로그나 자료들에서는 데이터를 하나, 하나 키, value를 따로 전달해서 HashMap이나 Map을 썼는데, 우리의 경우에는 한번에 묶어서 전달해야 해서 어떤 형식으로 데이터를 가공한 다음 전달해야 할지 고민을 많이 했다. 서버 연동은 끝났지만 JSON으로 파싱하는 법에 대해 좀 더 알아보고 더 좋은 코드를 짜야겠다.

 

참고

[Android] Retrofit2 Multipart사용하기 (Java) (velog.io)

Retrofit 으로 파일 업로드 하기 | Jungwoon Blog

Android Studio - Retrofit @Multipart 이미지 & @PartMap으로 데이터 전송하기 (tistory.com)

rest - How to send the Multipart file and json data to spring boot - Stack Overflow