안녕하세요..!! 안드로이드 개발을 시작한지 약 2개월 정도된 완전 초보자입니다.
현재 메인 엑티비티에 뷰페이저를 사용하여 총 4개의 프레그먼트를 이동할 수 있도록해놓았습니다. 편하게 프레그먼트 1~4라고 칭하겠습니다.
이중 1,2,3는 같은 A라는 어답터를 사용하고 4는 B라는 다른 어답터를 사용합니다.
각 프레그먼트마다 onViewCreated()에 파이어스토어에서 데이터를 가져오는 코드가 작성되어있습니다..
1, 2, 3, 4번 프레그먼트 어플이 실행과 동시에 onViewCreated()가 정상적으로 작동하고 안에 있는 데이터를 가져오는 코드 또한 제대로 작동합니다.(로그를 통해 확인해보았습니다.)
하지만 1, 2, 3 번 프레그먼트는 리사이클러뷰에 데이터들이 적용이 잘되는데 4번 프레그먼트는 데이터를 불러오기까지는 하는데 화면에 나타나지 않습니다.
그래서 onResume()안에 넣고 실행하면 데이터를 불러와서 리사이클러뷰에 연결까지 잘되어 화면이 나오지만 onResume에서 작동하는 거라 처음 4번 프래그먼트로 이동하는 과정에서는 비어있고 이동하고나서야 데이터가 나타나게 됩니다.
메인엑티비티에서
binding.viewPager.offscreenPageLimit = fragmentList.size
설정도 해놓았고 0~5까지의 숫자를 모두 대입해보았는데도 변화가 없습니다... 문제가 무엇인걸까요. 왜 1, 2, 3 프레그먼트는 정상적으로 작동되는데 4번만 데이터 연결에 문제가 있는지 모르겠습니다.
아래에 4번 프레그먼트의 코드와 4번 프레그먼트의 리사이클러뷰에 연결된 어답터 코드를 올려두겠습니다.,.!! 긴 글 읽어주셔서 감사합니다.
이 코드가 4번 프레그먼트의 코드입니다.
class QuoteFragment : Fragment() {
val binding by lazy { FragmentQuoteBinding.inflate(layoutInflater) } val storeDb = Firebase.firestore val adapter = QuoteRecyclerAdapter() var quoteList = mutableListOf<Quote>() override fun onResume() { super.onResume() // 모든 명언 불러오기 // getAllQuote() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 이 부분이 데이터를 불러오는 부분인데 데이터만 불러오고 리사이클러뷰에 연결이 안됨 getAllQuote() // 아래 토스트 부분은 정상출력이됨 Toast.makeText(context, "onViewCreated 있는 토스트", Toast.LENGTH_SHORT).show() // 내가 쓴 글만 볼 것인지 전체글을 볼 것인지 binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{ override fun onTabSelected(tab: TabLayout.Tab?) { when(tab?.position){ 0 -> getAllQuote() 1 -> getRandomQuote() } } override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} }) adapter.setItemClickListener(object : QuoteRecyclerAdapter.OnItemClickListener { override fun onClick(view: View, position: Int) { Toast.makeText(context,"click", Toast.LENGTH_SHORT).show() } }) // 버튼 누르면 글쓰기 페이지로 이동 binding.btnWrite.setOnClickListener { val intent = Intent(context, WriteActivity::class.java) intent.putExtra("postPath", "quote") startActivity(intent) } adapter.quoteList = quoteList binding.recyclerView.adapter = adapter binding.recyclerView.layoutManager = LinearLayoutManager(context) } // 모든 명언 가져오기 fun getAllQuote(){ storeDb.collection("quote") .addSnapshotListener{ documents, e -> adapter.quoteList.clear() documents?.forEach{ document -> val englishVer = document.get("englishVer").toString() val koreanVer = document.get("koreanVer").toString() val date = document.get("date").toString() val id = document.get("id").toString() quoteList.add(Quote(englishVer,koreanVer, date, id)) } ?: quoteList.add(Quote("자료가", "없습니다", "없", "어")) } quoteList.sortByDescending { it.date } adapter.notifyDataSetChanged() } // 랜덤 명언 가져오기 // 맛탱이감 fun getRandomQuote(){ storeDb.collection("quote") .addSnapshotListener{documents, e -> adapter.quoteList.clear() documents?.forEach{document -> val englishVer = document.get("englishVer").toString() val koreanVer = document.get("koreanVer").toString() val date = document.get("date").toString() val id = document.get("id").toString() quoteList.add(Quote(englishVer,koreanVer, date, id)) } ?: quoteList.add(Quote("자료가", "없습니다", "없", "어")) } quoteList.sortByDescending { it.date } adapter.notifyDataSetChanged() } }
이 코드가 4번 프래그먼트에 연결되어있는 어답터의 코드입니다.
class QuoteRecyclerAdapter : RecyclerView.Adapter<QuoteRecyclerAdapter.Holder>() { var quoteList = mutableListOf<Quote>() // Adapter interface OnItemClickListener { fun onClick(view: View, position: Int) } fun setItemClickListener(onItemClickListener: OnItemClickListener) { this.itemCLickListener = onItemClickListener } private lateinit var itemCLickListener: OnItemClickListener // Adapter 부터 이 위까지 클릭판정을 하기 위함 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { val binding = QuoteRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false) return Holder(binding) } override fun onBindViewHolder(holder: Holder, position: Int) { val quote = quoteList.get(position) holder.setQuote(quote) // 클릭 판정하기 위하여 holder.itemView.setOnClickListener { itemCLickListener.onClick(it, position) } } override fun getItemCount(): Int { return quoteList.size } class Holder(val binding : QuoteRecyclerBinding) : RecyclerView.ViewHolder(binding.root){ fun setQuote(quote : Quote){ with(binding) { val engVer = quote.englishVer val korVer = quote.koreanVer val date = quote.date quoteEng.setText("${engVer}") quoteKor.setText("${korVer}") quoteDate.setText("${date}") } } } }
Firestore 에서 데이터를 제대로 가지고 오고 있다면, 코드 상으로는 동작을 해야할 것 같은데요. 혹시 리사이클러뷰에 들어가는 quote_recycler.xml의 layout_height="match_parent"로 설정하지 않으셨는지 체크해 보세요.
그리고 addSnapshotListener 내부에서 데이터를 체크하실 때
if (e != null) {
// 에러처리
retutrn
}
val quotes = docuents?.map { document -> document.toObject(Quote::class.java) } ?: emptyList()
udateQuotes(quotes);
처럼 처리하시면 좀 더 깔끔할 듯 합니다.
한가지 더요, Frgment에서 ViewBinding을 사용하실 때는 binding변수를 onDestroyView에서 null로 만들어주셔야 메모리 누수가 없습니다.
이건 Fragment에서 다른 화면으로 갔다 돌아올 때 처럼, 프레그먼트는 살아있지만 뷰는 소멸되면서 binding 변수가 이전 레퍼런스를 가지게 되면서 생기는 문제인데,
https://developer.android.com/topic/libraries/view-binding
위의 개발자 페이지에 보시면 어떻게 처리해야하는지 나와 있습니다. 그리고 좀 더 깔끔한 방법은
https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c
와 같이 Property delegation을 쓰는 것인데, 도움이 되신다면 사용하시면 될 것 같습니다.
질문게시판이 별도로 있는데, 거기에 오려보시죠. 어댑터 레이아웃 XML도 올려보시구요.