본문 바로가기

TIL

[TIL] Kotlin Firestore, Storage [ 2 ] 이미지와 함께 게시글 데이터 가져오기

Topic =  currenUser UID가 포함된 Firestore Data를 가져와보자 

 

이전 게시글에서 데이터 저장하기에 이어서 진행
 

[TIL] Firebase Firestore, Storage [ 1 ] 이미지가 포함된 게시글 쓰기

Topic = Cloud Firestore 데이터 저장하기 Why Realtime DB를 어느정도 학습했으니, Firestore에 대해서도 학습해보자. 전에 Firebase Service 각각에 대해서 간단하게 정리해둔 글에서도 Realtime DB와 Firestore가 동일

junes-daily.tistory.com

 

 


 

 

게시글 데이터 불러오기

 

Firestore DB

 

 

Firestore 데이터 저장코드

        db.collection("Posts")
            .add(post)
            .addOnSuccessListener {
                Log.d("xxxx", "postUpload: added with : ${it}")
                requireActivity().supportFragmentManager.beginTransaction().replace(R.id.main_frame_layout,HomeFragment()).commit()
            }
  • 게시글을 DB에 저장할 때 위와 같이 Data를 저장해주었는데 Documents의 id를 별도로 지정해주지 않아서 자동으로 랜덤한 id가 생성된 것을 확인할 수 있다.
    그렇다면 랜덤으로 생성된 Documents에 특정 UID가 Field에 포함된 데이터를 확인하여서 나머지 데이터도 가져올 수 있도록 해야한다.

 

 

Firestore 데이터 불러오기 쿼리

    private fun downloadPostInfo() {
        db.collection("Posts")
            .whereEqualTo("uid", "${auth.currentUser!!.uid}")
            .get()
            .addOnSuccessListener { querySnapshot ->
                if (!querySnapshot.isEmpty) {
                    // 현재 로그인 한 UID 가 포함된 Documents 데이터
                    val documentSnapshot = querySnapshot.documents
                }
            }
            .addOnFailureListener {
                Log.d("xxxx", " Failure : $it ")
            }
    }
  • 위와 같은 쿼리를 사용하여 지정한 Collection 내의 모든 값 중에서 현재 로그인되어 있는 UID가 포함된 Documents를 가져오도록 할 것이다.
  • 쿼리에 대한 자세한 내용은 별도의 게시글에서 정리해볼 예정

 

 

 

MyPostFeedAdapter 

 

❌ 10.28 추가 - Bind에서 Storage의 이미지를 가져올 경우 스크롤 할 때마다 Storage에서 이미지를 새로 불러오기 때문에 이미지가 뒤늦게 나타나는 문제가 발생하므로 아래 게시글에서 작성한 방식으로 수정
 

[TIL] Kotlin Storage를 Adapter의 내부에서 호출 시 문제

Topic = Adatper에서 Storage의 이미지에 접근 하는 방식은, Rcv의 스크롤 시 Storage에서 매번 Storage를 통해 이미지를 가져오는 문제가 있다. Rcv에 적용하는 Model의 구조 문제 원인 위와 같이 스크롤 시 이

junes-daily.tistory.com

 

 

ViewHolder

    private val storage = Firebase.storage
    
    inner class MyPostFeedRcvViewHolder(binding: RcvItemPostBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val postTitle: TextView = binding.postTitle
        val postDesc: TextView = binding.postDesc
        val postPrice: TextView = binding.postPrice
        val postCategory: TextView = binding.postCategory
        val postImg: ImageView = binding.postImg

        fun bind(imagePath : String) {
            storage.reference.child("post").child("$imagePath").downloadUrl.addOnSuccessListener { uri ->
                Glide.with(itemView)
                    .load(uri)
                    .into(postImg)
                Log.d("xxxx", " adapter bind: $uri")
            }.addOnFailureListener {
                Log.d("xxxx", " adapter bind Failure $it")
            }


        }
    }
    
    ... 
    
}

 

  • Inner class ViewHolder의 bind() 함수 내부에서 게시글의 이미지에 해당하는 Storage의 path 값의 의미지를 Rcv Item의 이미지 뷰에 적용시켜준다.

 

Rcv Adapter - onBindViewHolder()

    override fun onBindViewHolder(holder: MyPostFeedRcvViewHolder, position: Int) {
        val post = currentList[position]

        val positionItem = currentList[position]

        holder.apply {
            postCategory.text = "카테고리 : ${positionItem.category}"
            postTitle.text = positionItem.title
            postDesc.text = positionItem.desc
            postPrice.text = positionItem.price
        }
        holder.bind(positionItem.imgs.First())
        Log.d("xxxx", "onBindViewHolder: ${positionItem.imgs.last()}")
    }
  • 각 Position에 해당하는 Item에 일치하는 데이터를 적용시켜준다.
  • Firestore 데이터에 넣어준 이미지 경로의 값이 3개라는 가정이면 이미지를 선택할 때 2,1,0 순서로 들어가기 때문에 게시글에 이미지를 선택할 때 가장 먼저 선택한 이미지가 대표이미지로 보이도록 마지막에 해당하는 img.last() 로 Index 지정해준다. 
    • Android studio에서 기본적으로 제공하는 IntentACTION_PICK 과 Photo Picker는 클릭한 순서대로 List에 저장되는 기능을 제공하지 않는다. 이미지를 클릭할 때 클릭한 순서대로 List에 담는 방법 학습하게 되면 추가 예정.

 

 

 

MyFeedPostFragment.kt
class MyPostFeedFragment : Fragment() {

    private var _binding: FragmentMyPostFeedBinding? = null
    private val binding get() = _binding!!

    private lateinit var auth: FirebaseAuth
    private lateinit var db: FirebaseFirestore
    
    // 내 게시글 RecyclerView Adpapter
    private lateinit var myPostFeedAdapter: MyPostFeedAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        auth = Firebase.auth
        db = Firebase.firestore

    }
  • 내 게시글 목록 Fragment - 현재 로그인 되어져 있는 사용자의 UID 값과 Firestore DB의 데이터가 필요하므로 Firebase 객체 선언 및 초기화 해준다

 

 

Firestore 데이터 가져오기

    private fun downloadPostInfo() {
        db.collection("Posts")
            .whereEqualTo("uid", "${auth.currentUser!!.uid}")
            .get()
            .addOnSuccessListener { querySnapshot ->
                if (!querySnapshot.isEmpty) {
                    val documentSnapshot = querySnapshot.documents
                    Log.d("xxxx", " testA[0].data : ${documentSnapshot[0].data} ")

                    val rcvList: MutableList<Post> = mutableListOf()

                    for (document in documentSnapshot) {
                        rcvList.add(
                            Post(
                                document.data?.get("uid") as String,
                                document.data?.get("title") as String,
                                document.data?.get("price") as String,
                                document.data?.get("category") as String,
                                document.data?.get("address") as String,
                                document.data?.get("deadline") as String,
                                document.data?.get("desc") as String,
                                document.data?.get("imgs") as List<String>,
                                document.data?.get("uploadDate") as String,
                            )
                        )
                    }
                    Log.d("xxxx", " result : $rcvList")

                    myPostFeedAdapter.apply {
                        submitList(rcvList)
                        notifyDataSetChanged()
                    }
                }
            }
            .addOnFailureListener {
                Log.d("xxxx", " Failure : $it ")
            }
    }
  • whereEqualTo 쿼리를 통해 currentUser의 UID가 포함된 Documents 를 가져온다.
  • Document에서 필요한 Filed 값들을 Post Data Model의 형식으로 RecyclerView List에 추가해준다.
  • UID 에 해당하는 값을 찾고 순서대로 정렬하는 복합쿼리에 대해서 추가 학습 후 정렬기능 추가예정.

 

 

Rcv Adapter 연결해주기

private fun setupRcv() {
        myPostFeedAdapter = MyPostFeedAdapter(object : PostClick {
            override fun postClick(post: Post) {
                Log.d("xxxx", "postClick: ")
            }
        })

        binding.postFeedRcv.apply {
            setHasFixedSize(true)
            layoutManager =
                LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            adapter = myPostFeedAdapter
            addItemDecoration(DividerItemDecoration(context, LinearLayout.VERTICAL))
        }

        downloadPostInfo()
    }
  • 정의해둔 setupRcv() 함수를 onCreateView() 내부에서 실행할 수 있도록 해주면 된다.

 

 

완성 화면

 

 

 

MyFeedPostFragment 전체코드

class MyPostFeedFragment : Fragment() {

    private var _binding: FragmentMyPostFeedBinding? = null
    private val binding get() = _binding!!

    private lateinit var auth: FirebaseAuth
    private lateinit var db: FirebaseFirestore

    private lateinit var myPostFeedAdapter: MyPostFeedAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        auth = Firebase.auth
        db = Firebase.firestore

    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMyPostFeedBinding.inflate(inflater, container, false)

        setupRcv()



        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        binding.backButtonWritepage.setOnClickListener {
            requireActivity().supportFragmentManager.beginTransaction().replace(R.id.main_frame_layout,MyPageFragment()).commit()
        }

    }

    private fun downloadPostInfo() {
        db.collection("Posts")
            .whereEqualTo("uid", "${auth.currentUser!!.uid}")
            .get()
            .addOnSuccessListener { querySnapshot ->
                if (!querySnapshot.isEmpty) {
                    val documentSnapshot = querySnapshot.documents
                    Log.d("xxxx", " testA[0].data : ${documentSnapshot[0].data} ")

                    val rcvList: MutableList<Post> = mutableListOf()

                    for (document in documentSnapshot) {
                        rcvList.add(
                            Post(
                                document.data?.get("uid") as String,
                                document.data?.get("title") as String,
                                document.data?.get("price") as String,
                                document.data?.get("category") as String,
                                document.data?.get("address") as String,
                                document.data?.get("deadline") as String,
                                document.data?.get("desc") as String,
                                document.data?.get("imgs") as List<String>,
                                document.data?.get("uploadDate") as String,
                            )
                        )
                    }
                    Log.d("xxxx", " result : $rcvList")

                    myPostFeedAdapter.apply {
                        submitList(rcvList)
                        notifyDataSetChanged()
                    }
                }
            }
            .addOnFailureListener {
                Log.d("xxxx", " Failure : $it ")
            }
    }

    private fun setupRcv() {
        myPostFeedAdapter = MyPostFeedAdapter(binding.root,object : PostClick {
            override fun postClick(post: Post) {
                Log.d("xxxx", "postClick: ")
            }
        })

        binding.postFeedRcv.apply {
            setHasFixedSize(true)
            layoutManager =
                LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            adapter = myPostFeedAdapter
            addItemDecoration(DividerItemDecoration(context, LinearLayout.VERTICAL))
        }

        downloadPostInfo()
    }
}

 

 

 


 

[ A. 오늘 복습한 내용 / B. 다음에 학습할 내용 ]

A. Firesbase DB 저장 및 불러오기 복습

 

B. ★ Firestore 쿼리를 통한 여러가지 데이터 정렬 및 다운로딩 방법에 대해서 배워보자 

 

B. Firebase 속도가 느린건지 내가 뭘 잘못한건지 데이터 불러오는 속도가 느린데 이 문제를 개선할 방법 

 


 

[오류,에러 등등]

1. 이미지 선택 시 선택한 순서대로 List에 들어가지 않는 문제 

  • Listener에서 받아오는 순간부터 순서가 정해져서 들어오기 때문에 별도로 이미지 피커 Library나, Activity로 만드는 방법이 필요하다?
  • 해결 시 추가 예정

 

 


 

[느낀 점]

1. Firebase 전체적인 이해도 + 5 [ 7 / 100 ]

 

2. Firebase DB는 쿼리를 잘 사용하는 것이 중요해 보인다. 

 

3. 학습 속도가 느린 것 같아서 걱정이다.

 

 


[Reference]

 

// Firestore

https://firebase.google.com/docs/firestore/quickstart?hl=ko#kotlin+ktx

https://firebase.google.com/docs/firestore/query-data/queries?hl=ko#kotlin+ktx_1