[TIL] Android 앱 개발 최종 : 포트폴리오 프로젝트 9

지혜·2024년 3월 7일

Android_TIL

목록 보기
62/70

✏240307 목요일 TIL(Today I learned)

  • 오늘은 1차 MVP 구현 마감날이다. MVP(Minimum Viable Product)라는 것 자체가 최소 기능 구현제품 이라는 것인데, 처음 계획을 짤 때에는 최대한의 스코프를 담아서 계획을 짜기 때문에 그 중에서 진짜 MVP만을 고른다는 것이 쉽지 않았다. 이번에는 직접 기획과 디자인을 진행해서 더욱 그렇게 느껴졌다. 하지만 마감은 다가오고 어짜피 중요한 기능을 우선 순위로 잡아서 작업했기 때문에 우선 순위로 잡은 부분에서도 진짜 진짜 핵심 부분을 잡아서 한 번 더 일정을 조정한 후 오늘 까지 코드 구현 마무리를 하기로 했다.

📖Fix : 로그인하면 유저정보 초기화 되는 문제 해결

  • 마무리를 위해 정상 작동을 확인하는 과정에서, 로그아웃 후 새로 로그인을 하면 유저 정보가 아예 초기화되어 북마크 정보도 사라지고, 이름도 초기화된다는 것을 알게되었다. 그래서 LoginActivity의 callback코드에서 로그인에 성공해서 유저정보를 등록하려고 하는 코드를 if (!it.exists())을 추가하여 존재하지 않은 경우에만 새롭게 추가하도록 만들어주었다. 이를 통해 파이어베이스 쓰기 비용도 꼭 필요한 경우에만 사용된다.

📖 프로필에서 북마크 기능 가져오기

  • 어제 CampDetail에서 북마크를 추가할 수 있는 기능을 만들었으니, 오늘은 프로필 화면에서 내가 북마크한 목록을 확인 할 수 있도록 만들었다.

    //ProfileFragment.kt
    private fun setBookmarkedAdapter(userId : String) = with(binding) {
           rvBookmarked.adapter = adapter
           rvBookmarked.layoutManager = LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL,false)
           viewModel.getBookmark(userId)
           viewModel.bookmarkedList.observe(viewLifecycleOwner){
               adapter.submitList(it)
               if(it.isNotEmpty()) {
                   tvBookmarkedSize.visibility =View.VISIBLE
                   tvTabBookmarked.visibility =View.GONE
                   rvBookmarked.visibility = View.VISIBLE
                   tvBookmarkedSize.text = it.size.toString()
               } else{
                   tvTabBookmarked.visibility =View.VISIBLE
                   rvBookmarked.visibility = View.GONE
                   tvBookmarkedSize.text = it.size.toString()
               }
           }
       }
  • 목록은 리사이클러뷰 어댑터를 활용하여 화면에 뿌려줄 것이다. 이를 위해 ListAdapter를 만들어주었고, 그 안에서 북마크 목록의 아이템을 클릭하면 상세페이지로 넘어갈 수 있도록 아이템 클릭 이벤트도 만들어주었다.

    class ProfileBookmarkAdapter : ListAdapter<CampEntity, ProfileBookmarkAdapter.Holder>(diffUtil) {
       inner class Holder(val binding: ItemBookmarkedBinding) : RecyclerView.ViewHolder(binding.root) {
           fun bind(data: CampEntity) {
               with(binding) {
                   if (data.firstImageUrl.isNullOrBlank()) {
                       ivCampImg.setImageResource(R.drawable.default_camping)
                   } else {
                       Glide.with(binding.root).load(data.firstImageUrl).into(binding.ivCampImg)
                   }
                   tvCampName.text = data.facltNm
                   tvCampAddr.text = data.addr1
                   if (data.lctCl.toString() == "[]") {
                       tvCampType.text = "[일반]"
                   } else {
                       tvCampType.text = data.lctCl.toString()
                   }
                   tvCampInduty.text = data.induty.toString()
    
                   binding.root.setOnClickListener {
                       val myData = CampEntity(
                           addr1 = data.addr1,
                           contentId = data.contentId,
                           ...
                           themaEnvrnCl = data.themaEnvrnCl
                       )
    
                       val intent = Intent(binding.root.context, CampDetailActivity::class.java).apply {
                           putExtra("campData", myData)
                       }
                       binding.root.context.startActivity(intent)
    
                   }
               }
           }
       }
    
       companion object {
           private val diffUtil = object : DiffUtil.ItemCallback<CampEntity>() {
               override fun areItemsTheSame(oldItem: CampEntity, newItem: CampEntity): Boolean {
                   return oldItem == newItem
               }
    
               override fun areContentsTheSame(oldItem: CampEntity, newItem: CampEntity): Boolean {
                   return oldItem == newItem
               }
           }
       }
    
       override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
           val binding = ItemBookmarkedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
           return Holder(binding)
       }
    
       override fun onBindViewHolder(holder: Holder, position: Int) {
           val item = getItem(position)
           holder.bind(item)
       }
    }
  • 북마크 목록을 가져오는 것은 MVVM 패턴을 활용하여 만들었다. 상세 페이지로 캠핑장 전체 데이터를 intent해서 넘겨주어야 하기 때문에, 북마크로 저장하고 있는 contentID를 통해 캠핑장 정보 자체를 담을 수 있도록 데이터 가공을 거쳐서 저장을 해야한다.

    class ProfileViewModel : ViewModel() {
       private val _bookmarkedList: MutableLiveData<List<CampEntity>> = MutableLiveData()
       val bookmarkedList: LiveData<List<CampEntity>> get() = _bookmarkedList
       private val bookmarkCamp: MutableList<CampEntity> = mutableListOf()
    
       fun getBookmark(userID: String) {
           Timber.tag("겟북마크검사").d("작동중")
           val db = FirebaseFirestore.getInstance()
           val docRef = db.collection("users").document("Kakao${userID}")
           val contentIds = mutableListOf<String>()
           docRef.get().addOnSuccessListener {
               if (it.exists()) {
                   val bookmarkData = it.get("bookmarked") as? List<*>
                   Timber.tag("겟북마크목록검사").d(bookmarkData.toString())
                   if (bookmarkData != null) {
                       for (item in bookmarkData) {
                           contentIds.add(item.toString())
                       }
                       Timber.tag("북마크목록검사").d("$contentIds")
                   }
                   bookmarkCamp.clear()
                   if (contentIds.isNotEmpty()) {
                       callBookmarkCamp(contentIds)
                   }
               }
           }
       }
    
       private fun callBookmarkCamp(bookmarkedItemList: MutableList<String>) {
           val db = Firebase.firestore
           val baseQuery: Query = db.collection("camps")
           val result = baseQuery.whereIn("contentId", bookmarkedItemList)
           result.get().addOnSuccessListener {
               for (doc in it) {
                   val camp = doc.toObject(CampEntity::class.java)
                   bookmarkCamp.add(camp)
               }
               _bookmarkedList.value = bookmarkCamp
               Timber.tag("콜북마크검사").d(bookmarkCamp.size.toString())
           }
       }
    }
    • [트러블슈팅] callBookmarkCamp에서 whereIn의 경우는 null이 들어오면 강제종료되는 이슈가 있었다. 그래서 getBookmark함수에서 if (contentIds.isNotEmpty())을 통해 null이 아닐 때만 callBookmarkCamp가 작동할 수 있도록 고쳐주었다.
    • [트러블슈팅] 처음에 getBookmark()에서 bookmarkCamp.clear()를 해주지 않았더니, 매번 불러오는 북마크 리스트가 중복 누적되는 것을 발견했다.
      => callBookmarkCamp에서 사용중인 bookmarkCamp를 초기화 해주지 않아서 발생하는 문제로, 처음에는 ProfileFragment의 setBookmarkedAdapter쪽에서 clear를 해주는 함수를 사용해서 초기화해보려고했으나 코드 위치를 변경해보아도 반응이 없었고, 그 다음에는 callBookmarkCamp 쪽에서 초기화하려고 해보았으나, 역시나 반응이 좋지 않았다. 고민을 하다가, getBookmark에서 북마크 목록을 아예 넘겨주기 전에 초기화를 시키니까, 누적되는 일 없이 정상작동하였다.

📖 프로필 유저정보 수정 정보 데이터베이스에 저장하기

  • 프로필 수정 기능을 화면 상에서만 가능하게하고, 데이터베이스와 연동을 아직 하지 않았었다. 유저정보가 데이터베이스에 생겼기 때문에 더 늦기 전에 프로필 수정 정보도 데이터베이스에 연결해주기로 했다.
    private fun handleClickEdit(confirm: Boolean) {
           with(binding) {
               if (confirm) {
                   //Todo. 사진에 대한 변경값을 다시 스토리지를 통헤 데이터베이스에 넘겨서 저장
                   UserApiClient.instance.me { user, error ->
                       val documentRef = db.collection("users").document("Kakao${user?.id}")
                       val updateNickname = hashMapOf<String, Any>("nickName" to "${tvProfileName.text}")
                       //val updateProfileImg = hashMapOf<String,Any>("profileImage" to img_URI.toString())
                       documentRef.get().addOnSuccessListener {
                           documentRef.update(updateNickname)
                           //documentRef.update(updateProfileImg)
                       }
                   }
                   tvProfileName.text = tvProfileName.text
                   //ivProfileImg.setImageURI(img_URI)
               } else {
                   tvProfileName.text = ""
                   initLogin()
               }
               ...
               if(rvBookmarked.visibility == View.VISIBLE){
                   tvTabBookmarked.visibility = View.GONE
               }
           }
       }
    • 프로필에서는 닉네임과 프로필이미지가 각각 수정이 가능하므로 필드명과 변경값을 hashMap으로 저장해주어 update를 사용해 DB에 저장되도록 만들었다.
    • 여기서 문제는, 사진의 경우 이미지 저장을 할 때 로컬 주소로 데이터베이스에 저장되면 보안 문제로 접근이 안되는 오류가 발생한다는 것이다. 이런 문제는 파이어베이스의 스토리지를 사용하는 것으로 해결해야하는데, 당장 오늘이 마감이므로 지금에 와서 스토리지를 연결하기엔 시간이 부족하다는 판단으로 팀원들과 상의 하여 최종 마감때 처리하는 것으로 일단 미루기로 했다. ...
    • 이름 수정 취소를 누르면 initLogin을 통해서 원래 데이터베이스의 이름이 떠오른다.
    • 프로필 수정 후에 다시 원래 프로필 화면으로 돌리는 과정에서 북마크 목록이 있을 때와 없을 떄의 화면이 다르므로 if문을 통해 조절해 주었다.
profile
파이팅!

0개의 댓글