[트러블슈팅] 중간 발표 때 프로필화면에서 프로필 이미지가 resolveUri failed on bad bitmap uri 오류로 로드가 안되고 있었다. 나는 이게 보안 문제 등으로 인한 변환 오류인줄만 알고 스토리지에 연결해서 암호화..된 URI가 들어오면 괜찮을 줄 알았는데, 스토리지 URI도 전혀 먹히지 않았다.
=> 해결책을 구글링해보면 일단 비트맵 변환에 대한 이야기가 주로 나와서, 비트맵 변환 방식을 직접 적용해야하나 고민하다가 일단 Glide 사용 중이니까 일단 글라이드로 로드되도록 코드를 바꾸니까 원래 카카오이미지주소도 잘 로드가 되는 것 같다.
어쨌든, resolveUri failed on bad bitmap uri 문제가 아니더라도, 로컬 이미지를 가지고 올떄에는 안드로이드 자체의 보안에 의해서 SecurityException 이 발생하므로, 프로필 수정을 위해서는 파이어스토어의 Storage가 꼭 필요하다.
//스토리지에 이미지 업로드하는 방법
fun profileImgUpload(imageURI: Uri, userID: String) {
val storage = Firebase.storage
val storageRef = storage.getReference("profileImage")
if (imageURI.toString().startsWith("http")) {
GlobalScope.launch(Dispatchers.IO) {
//인터넷URL의 경우 putStream사용.
storageRef.child(userID).putStream(URL(imageURI.toString()).openStream())
}
} else {
//로컬 파일 업로드 시 putFile 사용
storageRef.child(userID).putFile(imageURI)
}
}
LoginActivity에서 처음 접속을 할 때에도 사용하고, 프로필이미지를 수정할 때 ProfileFragment에서도 사용하기 때문에, 공용으로 사용할 수 있도록 FirebaseUtils.kt에 작성해 주었다.스토리지에 이미지를 업로드하면, 바로 스토리지의 이미지를 가져오는 것이 아니라 스토리지에 저장된 URI를 파이어스토어에도 저장해줘야한다. 팀장님과 상의 결과 어쨋든 파이어스토어를 백엔드용으로 겸해서 사용하고 있는 것인데, 데이터를 분산해서 가져오는 것은 아키텍쳐적으로 좋지 못한 모양새라는 점에 동의해서 저장한 스토리지의 URI를 가져와서 저장하는 방법을 사용하기로 했다.
Toast.makeText(this, "로그인에 성공하였습니다.", Toast.LENGTH_SHORT).show()
val db = Firebase.firestore
UserApiClient.instance.me { user, _ ->
//스토리지에 이미지를 미리 업로드
profileImgUpload(Uri.parse(user?.kakaoAccount?.profile?.profileImageUrl), "Kakao${user?.id}")
val userModel = hashMapOf(
"nickName" to "${user?.kakaoAccount?.profile?.nickname}",
"profileImage" to null,
"userEmail" to "${user?.kakaoAccount?.email}",
"bookmarked" to null,
"writing" to null
)
val documentRef = db.collection("users").document("Kakao${user?.id}")
documentRef.get().addOnSuccessListener {
if (!it.exists()) {
documentRef.set(userModel)
//스토리지에 업로드한 이미지 URI를 가져와서 파이어스토어에도 저장해주기
Firebase.storage.getReference("profileImage").child("Kakao${user?.id}").downloadUrl.addOnCompleteListener {
if (it.isSuccessful) {
val profileImgURI = hashMapOf<String, Any>(
"profileImage" to it.result.toString()
)
documentRef.update(profileImgURI)
}
}
}
}
finish()
}
기존에 다이렉트로 프로필URL을 저장했던 hashmap 안의 "profileImage"를 null처리해주고, 먼저 유저에 대한 문서를 생성해 준 후, 들어오는 이미지를 스토리지에 먼저 저장하고, 저장한 이미지의 downloadURL을 가져와서 "profileImage"에 넣어준 후 생성한 유저문서에 업데이트 해주는 방식.
[트러블슈팅] 스토리지에 있는 downloadURL을 바로 profileImgURI의 hashmap 안에서 저장해주려고 했다가 java.lang.IllegalStateException: Task is not yet complete 비동기처리 오류를 만나서 GPT의 힘을 좀 빌렸다. 작동되는 코드를 보고 있으니 내가 했던 시도가 좀 어이없긴한데, 비동기 처리에 대해서 좀 더 유의해야겠다.
스토리지를 연결했으니, 로컬에서 고른 이미지를 안전하게 저장하고 가져올 수 있게 되었다. 이를 바탕으로 프로필 수정을 눌렀을 때 프로필 이미지를 변경할 수 있도록 만들었다.
private fun handleClickEdit(confirm: Boolean) {
with(binding) {
if (confirm) { //수정 확인 버튼
UserApiClient.instance.me { user, _ ->
val documentRef = db.collection("users").document("Kakao${user?.id}")
//이름수정
val updateNickname = hashMapOf<String, Any>("nickName" to "${tvProfileName.text}")
//이미지수정
if (profileImgUri != null) {
profileImgUpload(profileImgUri!!, "Kakao${user?.id}")
Firebase.storage.getReference("profileImage").child("Kakao${user?.id}").downloadUrl.addOnCompleteListener {
if (it.isSuccessful) {
val profileImgURI = hashMapOf<String, Any>("profileImage" to it.result.toString())
documentRef.update(profileImgURI)
}
}
//profileImgUri = null
}
documentRef.get().addOnSuccessListener {
documentRef.update(updateNickname)
}
}
//수정된 이름이 바로 적용되도록 작성
tvProfileName.text = tvProfileName.text
} else { //수정 취소버튼 눌렀을 때. 이미지와 사진이 비었다가 깨끗하게 다시 채워지도록 작성.
profileImgUri = null
tvProfileName.text = ""
ivProfileImg.setImageURI(profileImgUri)
initLogin()
}
...
}
}
//profileImgUri = null의 위치를 tvProfileName.text = tvProfileName.text 위로 잡았다가, if (profileImgUri != null)에서 아예 profileImgUri를 null로 잡아서 이미지 수정이 안되는 이슈가 있었다. profileImgUri은 imageLauncher를 통해 고르기 때문에, 전역 변수로 관리하고 있었는데, 사진을 고르고 새로 ProfileFragment가 onResume되는 때에도 profileImgUri 값을 유지하고 있었으면서, 귀신같이 확인 버튼 눌렀을 때만 null로 인식을 해서 업데이트가 안되었다.새삼 프로필 화면을 만들다 보니, 당연하게 여기는 모든 것들이 일일히 코드를 작성해서 제어해줘야한다는 것을 새삼 느꼈다. 취소버튼을 눌렀을 떄, 확인 버튼을 눌렀을 때, 이름만 수정했을 때, 아무것도 수정안했지만 확인버튼 눌렀을 때 등등 경우의 수가 참 많다. 최대한 확인하고 있지만, 생각치도 못한 일이 발생할까봐 긴장하고 있다.ㅎㅎ
사실 오늘은 이름 수정 시 자동 키보드 활성화가 어느 기점으로 부터 실행이 안되어서, 어짜피 deprecated된 코드들을 활용하고 있다보니 고쳐볼려고 도전을 했다가 실패했다. 더 붙잡고 있었으면 되긴 되었을텐데 마감을 위한 우선순위 고려로 2시간 이상 잡고 있는건 아니라고 생각했기 때문에 보내주었다. 다만, 원인 확인까지는 되었는데
-> 내가 이름 수정을 AlartDialog를 사용했는데, 여기서 키보드 자동 활성화는 플러그 문제로 쉽게 활성화 되지 않는다고 했다. 또, showSoftInput의 경우는 view를 받고있는데, 이 뷰가 다이얼로그에서 좀 까다롭게 작동하는 것 같다.
-> 플래그를 GPT도움을 받아서 정리하니까 키보드가 나오긴 나왔는데, 다이얼로그 뒤에 프로필프래그먼트에서 키보드가 활성화 되는 경우도 있었다. ㅎ showSoftInput안에 view문제인것같아서 잠깐 고쳐봤는데 고치면 아예 안나오고... 그래서 그냥 일단 5분기록보드에 적어놓고, 보류해두었다.
약 2주간의 일정중에서 벌써 3일차인데, 처음 정리한거에 비해 일찍 끝나는 부분들이 있었지만, 그만큼 뭔가 신경써야할 부분들이 늘어나서 마감이 다시 초조해지기 시작했다....ㅎㅎ
어쩄든, 내일은 스크럼시간에 브리핑한대로 작성한글 탭을 완성시킬 것이다. 겸사겸사 북마크 탭에서 상세페이지를 불러오는 것에 대해 공통적인 업데이트 작업이 필요해서 팀원분이 제시해주신 코드를 보고 고쳐주어야한다.
그 외에도 리사이클러뷰 아이템 스와이프로 삭제하기, 유저디테일페이지완성하기, 아까 언급했던 키보드 활성화 문제 고치기, 세세한 디자인 피드백 등등 의 문제가 있지만 빡빡하더라도 기간내에는 충분히 해결할 수 있는 문제라고 생각한다. 마지막까지 파이팅!!!!!