728x90
Mutipart 무엇인가?
- Multipart는 HTTP에서 여러 종류의 데이터를 동시에 전송하기 위해 사용되는 방식입니다.
- 'Content-Type' 헤더에 'multipart/form-data'값을 가지며 여러 개의 part로 구성됩니다.
- 주로 파일 업로드나 폼 데이터 전송 등에 사용됩니다.
Multipart 활용을 위한 Api 호출 함수 구성
@Multipart
@PATCH("/api/v1/users/{id}")
suspend fun editUserImage(
@HeaderMap headers: HashMap<String, String>,
@Path("id") id: Int,
@Part file: MultipartBody.Part
): Response<Unit>
@Multipart 어노테이션과 보낼 파일 데이터에 대해서는 @Part를 사용하여 구성해줍니다.
Multipart 활용 가능하도록 uri 변환
특정 로직으로 파일 데이터의 uri를 가져왔다면 이것을 Part로 변환하는 작업이 필요합니다.
먼저 특정 uri를 File 데이터로 변환하는 Converter를 구성해 줍니다.
object FileConverter {
@SuppressLint("Recycle")
fun uriToFile(context: Context, uri: Uri): File? {
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
inputStream?.let {
val file = createTempImageFile(context)
copyInputStreamToFile(it, file)
return file
}
return null
}
private fun createTempImageFile(context: Context): File {
val timeStamp = System.currentTimeMillis()
val imageFileName = "JPEG_${timeStamp}_"
val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
imageFileName,
".jpg",
storageDir
)
}
private fun copyInputStreamToFile(inputStream: InputStream, file: File) {
try {
FileOutputStream(file).use { outputStream ->
val buffer = ByteArray(4 * 1024) // buffer size
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
inputStream.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
외부 저장소에서 가져온 이미지의 uri를 기반으로 새로운 파일을 생성하여 반환하는 로직입니다.
@HiltViewModel
class UserUpdateViewModel @Inject constructor(
@NetworkModule.Main private val apiService: ApiService
) : ViewModel() {
fun updateUserProfileImg(header: String, userId: Int, imageFile: File): Flow<ApiResult<UserUpdateResult>> = flow {
try {
val imageRequestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData("image", imageFile.name, imageRequestBody)
val field : HashMap<String, String> = HashMap()
field["Authorization"] = header
val response = apiService.editUserImage(field, id = userId, file = imagePart)
emit(ApiResult.Success(UserUpdateResult(response.isSuccessful, response.code())))
} catch (e: java.lang.Exception) {
emit(ApiResult.Error(e.localizedMessage ?: "An error occurred", 0))
}
}
}
ViewModel에서 변환된 File로 Part 생성 및 통신 처리를 진행합니다.
asRequestBody로 파일 형식에 맞는 Media RequestBody를 만들어줍니다. 이미지 파일이므로 "image/*"를 사용했습니다.
영상 파일이라면 "video/*"를 활용하겠지요?
생성된 파일의 이름과 RequestBody를 조합하여 Part.FormData를 생성하고, 이를 Api 통신에 활용하면 되겠습니다.
val imagePart = MultipartBody.Part.createFormData("image", imageFile.name, imageRequestBody)
val response = apiService.editUserImage(field, id = userId, file = imagePart)
emit(ApiResult.Success(UserUpdateResult(response.isSuccessful, response.code())))
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.let { uri ->
imageUri = uri
val imageFile = FileConverter.uriToFile(requireContext(), uri) ?: return@let
viewLifecycleOwner.lifecycleScope.launch {
....
userEditViewModel.updateUserProfileImg(token, userId = userId, imageFile).collect { result ->
when (result) {
is ApiResult.Success -> {
if (result.data.isSuccess) {
Toast.makeText(
context,
"저장되었습니다. ${result.data.responseCode}",
Toast.LENGTH_SHORT
).show()
binding.profileImg.setImageURI(imageUri)
}
}
else -> return@collect
}
}
}
}
}
}
728x90