api에 대해 자신감이 붙고 팀원들에게 로컬에서 대이터를 저장하고 주고 받는것보다 서버에 데이터를 저장하고 받는게 편하다고 아주 오만한(?) 말을 하자마자 이 api를 만났다...ㅎ
프로필 이미지를 수정하고 그 이미지를 불러오기 위한 Api!!
서버에서 s3를 이용해 이미지를 저장하고 업로드하는데
그 이미지를 내가 patch api를 이용해서 보내주는거!!
사실 api에 대해 빠삭하게 알고있다면 충분히 어렵지 않게 할 수 있었을 것 같지만
api에 대해 잘 모르는 상태였어서 더 어려웠던 것 같다..
이 중에서 가장 중요한건!! 앨범에서 이미지 추가하는거!!
따라서 앨범에서 이미지 추가하는것만 할 줄 안다면 나머지도 구현가능!!
처음 이용해보는 색다른 형식!!! 구글링해도 잘 없어서 고생했다..
1. 이미지 파일을 file 키를 통해 multipart/form-data 형식으로 요청 본문에 첨부
(키는 image 로 주심)
2. Request Body 바디에 application/json 형식으로 이미지 타입도 같이 보내줌!
(키는 body 로 주심)
여기서 주의할점은 지금까지는 Request Body는 @Body라고 선언했다면 여기서는 multipart이기때문에 @Part로 선언!!
@Multipart
@PATCH("주소")
fun patchProfileImage(@Path(value = "profile-id") profileId: Long, @Part("body") body: RequestBody, @Part image :MultipartBody.Part?):Call<PatchProfileImage>
여기서 권한이 계속 거부됐다고 떠서 몇시간을 헤맸는데 알고보니..
이때 쓰고 있던 TargetSdk가 33이었는데!! 33부터는
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
흔히 구글링하면 나오는 이게 아닌...
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
이거였다는거..!!
그래서 우리는 걍 맘편히 32로 낮춤..ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
32로 낮추고 권한 받아 갤러리를 열면 너무 잘열림..!!!! 허무하지만
갤러리 활동의 결과를 기다리고 처리하는 데 사용함
lateinit var getResult: ActivityResultLauncher<Intent>
private fun goGallery() {
// 현재 기기에 설정된 쓰기 권한을 가져오기 위한 변수
var writePermission = ContextCompat.checkSelfPermission(
requireContext(),
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
// 현재 기기에 설정된 읽기 권한을 가져오기 위한 변수
var readPermission = ContextCompat.checkSelfPermission(
requireContext(),
android.Manifest.permission.READ_EXTERNAL_STORAGE
)
// 읽기 권한과 쓰기 권한에 대해서 설정이 되어있지 않다면
if (writePermission == PackageManager.PERMISSION_DENIED || readPermission == PackageManager.PERMISSION_DENIED) {
// 읽기, 쓰기 권한을 요청합니다.
Log.d("go!!", "su")
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.Manifest.permission.READ_EXTERNAL_STORAGE
),
1
)
}
// 위 경우가 아니라면 권한에 대해서 설정이 되어 있으므로
else {
var state = Environment.getExternalStorageState()
// 갤러리를 열어서 파일을 선택할 수 있도록
if (TextUtils.equals(state, Environment.MEDIA_MOUNTED)) {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
//갤러리를 열기 위해 사용되는 Intent를 launch() 메서드를 통해 실행
//다른 활동(갤러리)을 시작하고 해당 활동이 완료될 때까지 기다린 후 결과를 처리할 수 있게함
getResult.launch(intent)
}
}
}
파일 시스템 경로 대신 content:// 형식의 URI를 사용하여 파일에 접근
이러한 URI는 다른 앱의 데이터에도 접근할 수 있도록 보안 및 권한 관리를 용이하게 함
=>
but 실제 파일 시스템의 경로가 필요할 때가 있는데,,
그때가 지금!! 이 함수는 주어진 URI를 기반으로 실제 파일 시스템 경로를 결정
->URI에 대한 실제 파일 시스템 경로를 문자열로 반환해주는 함수임!!
만약 Xiaomi 디바이스인 경우, uri.path.toString()를 통해 간단히 URI의 경로를 반환하고, 그 외의 경우에는 MediaStore를 사용하여 URI에 대한 실제 파일 경로를 가져옴
이때 MediaStore는 Android의 미디어 컨텐츠를 관리하는 프레임워크로, 여기서는 이미지 데이터를 쿼리하여 파일의 실제 경로를 얻기 위해 활용한당
private fun getRealPathFromURI(uri: Uri): String {
val buildName = Build.MANUFACTURER
if (buildName.equals("Xiaomi")) {
return uri.path.toString()
}
var columnIndex = 0
val proj = arrayOf(MediaStore.Images.Media.DATA)
var cursor = requireActivity().contentResolver.query(uri, proj, null, null, null)
if (cursor!!.moveToFirst()) {
columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
}
return cursor.getString(columnIndex)
}
getRealPathFromURI 메서드에서 얻은 실제 경로를
{
"profile_image_type": "CHARACTER"
}
업데이트할 프로필 이미지에 대한 정보를 포함하는 JSON 객체를 생성.
이 정보에는 프로필 이미지의 유형을 나타내는 profile_image_type 필드가 포함됨
이 JSON 객체를 서버에 전송하기 위해 HTTP 요청의 본문으로 사용될 RequestBody를 생성합니다. 이 때, application/json 미디어 타입을 사용
private fun patchProfileImage(profileId: Long, type: String, filPath : String) {
// 업데이트할 프로필 이미지에 대한 정보를 JSON 객체에 담음
val json = JSONObject()
//서버에서 준 형식으로
json.put("profile_image_type", type)
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = json.toString().toRequestBody(mediaType)
//로그는 맞게 찍히는지 모두 찍어봄..ㅋㅋㅋ
Log.d("경로4",json.toString())
Log.d("경로5",mediaType.toString())
Log.d("경로6",requestBody.toString())
// <사용자가 선택한 이미지 파일을 읽어와서 Multipart 요청 생성과정>
//지정된 파일 경로를 가지고 새로운 File 객체를 생성
//서버에서 이미지를 File로 받음
val file = File(filePath)
//미디어 타입을 나타내는 MediaType을 생성하고, 이를 사용하여 파일을 RequestBody로 변환
val requestFile = "image/*".toMediaTypeOrNull()?.let { RequestBody.create(it, file) }
//Multipart 요청을 생성 createFormData() 메서드는 MultipartBody.Part 객체 생성
//requestFile!!는 앞에서 생성한 RequestBody 객체를 전달
//파일이름을 file.name이라고 안적고 임의로 적어서 계속 호출 실패함..
val body: MultipartBody.Part =
MultipartBody.Part.createFormData("image", file.name, requestFile!!)
Log.d("경로1", filePath)
Log.d("경로2", requestFile.toString())
Log.d("경로3", body.toString())
val call: Call<PatchProfileImage> =
// Retrofit을 사용하여 서버로 프로필 이미지 업데이트 요청을 전송
RetrofitClient.mainProfile.patchProfileImage(profileId, requestBody, body)
call.enqueue(object : Callback<PatchProfileImage> {
override fun onResponse(
call: Call<PatchProfileImage>,
response: Response<PatchProfileImage>
) {
if (response.isSuccessful) {
val responseBody = response.body() // 응답 본문 가져오기
if (responseBody != null) {
Log.d("서버 응답 본문", responseBody.toString()) // 응답 본문 출력
} else {
Log.d("서버 응답 본문", "응답 본문이 비어있습니다.")
}
} else {
Log.d("서버 응답 오류", "서버 응답이 실패했습니다.")
}
}
override fun onFailure(call: Call<PatchProfileImage>, t: Throwable) {
// 통신 실패 처리
Log.e("통신 실패", "요청 실패: ${t.message}", t)
}
})
}
이건 ver1을 이해했다면 아주아주 쉬움!!
여기서는 넘겨주는 이미지 파일이 없으니깐!! 그 자리에 널로 보내주면 됨~
private fun patchProfileImage2(profileId: Long, type: String) {
val json = JSONObject()
json.put("profile_image_type", type)
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = json.toString().toRequestBody(mediaType)
Log.d("경로4",json.toString())
Log.d("경로5",mediaType.toString())
Log.d("경로6",requestBody.toString())
val call: Call<PatchProfileImage> =
RetrofitClient.mainProfile.patchProfileImage(profileId, requestBody, null)
call.enqueue(object : Callback<PatchProfileImage> {
override fun onResponse(
call: Call<PatchProfileImage>,
response: Response<PatchProfileImage>
) {
if (response.isSuccessful) {
val responseBody = response.body() // 응답 본문 가져오기
if (responseBody != null) {
Log.d("서버 응답 본문", responseBody.toString()) // 응답 본문 출력
} else {
Log.d("서버 응답 본문", "응답 본문이 비어있습니다.")
Log.d("서버", response.message())
}
} else {
val errorBody = response.errorBody()?.string() ?: "No error body"
//Log.d("서버 응답 오류", "서버 응답이 실패했습니다.")
Log.d("서버 응답 오류", "서버 응답이 실패했습니다. 오류 메시지: $errorBody")
try {
val errorMessage = JSONObject(errorBody).getString("message")
Log.d("오류 메시지", errorMessage)
Toast.makeText(requireContext(),errorMessage,Toast.LENGTH_SHORT).show()
} catch (e: JSONException) {
Log.e("JSON 파싱 오류", "오류 메시지를 추출하는 데 실패했습니다: ${e.message}")
}
}
}
override fun onFailure(call: Call<PatchProfileImage>, t: Throwable) {
// 통신 실패 처리
Log.e("통신 실패", "요청 실패: ${t.message}", t)
}
})
}
![]() | ![]() |
---|
✨이런 이미지까지 들어가는 마이프로필이 완성된당..!!
이거 해결하고 자러가는.../////////////////