• 카테고리

    질문 & 답변
  • 세부 분야

    모바일 앱 개발

  • 해결 여부

    해결됨

북마크 탭에서 북마크 취소 시 문제

23.04.30 17:32 작성 조회수 540

0

안녕하세요. 북마크 진행하다가 북마크 탭에서 북마크를 삭제하는 부분에서 문제가 생겨 질문 올립니다.

아래 상태에서

스크린샷 2023-04-30 170637.png북마크를 취소하기위해 클릭하면

스크린샷 2023-04-30 170738.png이렇게 제가 누른 부분은 사라지지만(이 부분은 제가 클릭했을 때 bookmarkIdList에 KeyList가 없다면 사라지게 해놓은 겁니다) 또 중복으로 아이템이 생깁니다. 계속 시도해봤는데 어디서 고쳐야할지 잘 모르겠습니다.

아래는 BookmarkRVAdapter.kt의 코드입니다. 편하실 때 답변해주시면 감사하겠습니다!

package com.bokchi.mysolelife.contentsList

import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bokchi.mysolelife.R
import com.bokchi.mysolelife.utils.FBAuth
import com.bokchi.mysolelife.utils.FBRef
import com.bumptech.glide.Glide

class BookmarkRVAdapter(val context : Context,
                       val items : ArrayList<ContentModel>,
                       val keyList : ArrayList<String>,
                       val bookmarkIdList : MutableList<String>
) : RecyclerView.Adapter<BookmarkRVAdapter.Viewholder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkRVAdapter.Viewholder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.content_rv_item, parent, false)
        Log.d("RVAdapterB", "key List : " + keyList.toString())
        Log.d("RVAdapterB","Bookmark List : " + bookmarkIdList.toString())
        return Viewholder(v)
    }

    override fun onBindViewHolder(holder: BookmarkRVAdapter.Viewholder, position: Int) {

        holder.bindItems(items[position], keyList[position])
    }

    override fun getItemCount(): Int {
        return items.size
    }

    inner class Viewholder(itemView : View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(item : ContentModel, key : String) {

            itemView.setOnClickListener {
                val intent = Intent(context, ContentShowActivity::class.java)
                intent.putExtra("url", item.webUrl)
                itemView.context.startActivity(intent)
            }

            val ContentTitle = itemView.findViewById<TextView>(R.id.TextArea)
            val imageViewArea = itemView.findViewById<ImageView>(R.id.imageArea)
            val bookmarkArea = itemView.findViewById<ImageView>(R.id.BookmarkArea)

            if(bookmarkIdList.contains(key)) { // bookmarkIdList가 KeyList에 있는 정보를 가지고 있다면
                bookmarkArea.setImageResource(R.drawable.bookmark_color)

            } else {
                bookmarkArea.setImageResource(R.drawable.bookmark_white)
            }

            ContentTitle.text = item.title
            Glide.with(context)
                .load(item.imageUrl)
                .into(imageViewArea)

            bookmarkArea.setOnClickListener {   // 북마크를 누르면 실행되는 내용

                // 북마크를 클릭했을 때, 북마크가 있는 경우 -> 북마크 삭제
                if(bookmarkIdList.contains(key)) {
                    ContentTitle.visibility = View.GONE
                    imageViewArea.visibility = View.GONE
                    bookmarkArea.visibility = View.GONE

                    FBRef.bookmarkRef
                        .child(FBAuth.getUid())
                        .child(key)
                        .removeValue()
                }

            }

           if(bookmarkIdList.isEmpty()) {
               ContentTitle.visibility = View.GONE
               imageViewArea.visibility = View.GONE
               bookmarkArea.visibility = View.GONE
           }


            Log.e("BOOKMARK", bookmarkIdList.toString())

        }
    }
}

답변 2

·

답변을 작성해보세요.

0

조금 코드가 깁니다. 천천히 한번 봐주세요

image

데이터 넣기 -> firebase에 테스트 데이터 10개 정도 넣습니다.

데이터 리스트 -> firebase에 있는 데이터를 가져와서 보여줍니다. 클릭하면 북마크로 저장됩니다.

북마크 쪽으로 -> 북마크 된 데이터를 보여줍니다. 클릭할 때 마다 해제됩니다.

 

MainActivity -> 테스트 데이터를 넣고, 다른 액티비티로 이동합니다.

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.simpleName

    private lateinit var auth: FirebaseAuth

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        // 인증
        auth = Firebase.auth
        auth.signInAnonymously()
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInAnonymously:success")


                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInAnonymously:failure", task.exception)
                    Toast.makeText(
                        baseContext,
                        "Authentication failed.",
                        Toast.LENGTH_SHORT,
                    ).show()

                }
            }


        val dataInsert = findViewById<Button>(R.id.dataInsert)
        dataInsert.setOnClickListener {
            firebaseDataInsert()
        }

        val dataList = findViewById<Button>(R.id.dataList)
        dataList.setOnClickListener {
            val intent = Intent(this, DataActivity::class.java)
            startActivity(intent)
        }

        val bookMarkList = findViewById<Button>(R.id.bookmark)
        bookMarkList.setOnClickListener {
            val intent = Intent(this, BookmarkActivity::class.java)
            startActivity(intent)
        }

    }


    fun firebaseDataInsert(){

        val database = Firebase.database
        val myRef = database.getReference("content")

        for(i in 0..10) {
            val testData = TestDataClass(
                i.toString(),
                "title number : $i"
            )

            myRef.push().setValue(testData)
        }



    }

}

 

DataActivity -> 데이터 리스트를 보여줍니다.

클릭하면 북마크로 저장합니다.

class DataActivity : AppCompatActivity() {

    private val TAG = DataActivity::class.java.simpleName
    private val database = Firebase.database
    private val myRef = database.getReference("content")
    private val auth = Firebase.auth
    val dataList = mutableListOf<TestDataClass>()

    lateinit var lv : ListView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_data)

        lv = findViewById(R.id.lv)

        lv.setOnItemClickListener { parent, view, position, id ->

            val bookMarkRef = database.getReference("bookMark")
            bookMarkRef.child(auth.uid.toString()).child(id.toString()).setValue(dataList[position])

        }

        getList()



    }

    fun getList(){

        val valueEventListener = object : ValueEventListener {

            override fun onDataChange(dataSnapshot: DataSnapshot) {

                for (userSnapshot in dataSnapshot.children) {
                    val data = userSnapshot.getValue(TestDataClass::class.java)
                    if (data != null) {
                        dataList.add(data)
                    }

                }
                val adapter = DataListAdapter(baseContext, dataList)
                lv.adapter = adapter


            }

            override fun onCancelled(databaseError: DatabaseError) {
                Log.e(TAG, "Failed to read value.", databaseError.toException())
            }
        }

        myRef.addListenerForSingleValueEvent(valueEventListener)


    }
}
class DataListAdapter(val context: Context, val List: MutableList<TestDataClass>) : BaseAdapter(){

    // View를 꾸며주는 부분
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {

        // converView가 null이 아닐 경우 View를 재활용
        // 이 부분이 없다면, View를 리스트의 갯수만큼 호출해야 함
        var convertView = convertView
        if (convertView == null) {
            // list_view_item 을 가져온다
            convertView = LayoutInflater.from(parent?.context).inflate(R.layout.content_item, parent, false)
        }

        // List에 있는 데이터들을 하나씩 list_view_item의 textView의 아이디를 찾아서 넣어줌
        val title = convertView?.findViewById<TextView>(R.id.idArea)
        val list_item = List[position]
        title!!.text = list_item.title

        return convertView

    }

    // 각각의 리스트 하나씩 가져오는 부분
    override fun getItem(position: Int): Any {
        return List[position]
    }

    // 리스트의 ID를 가져오는 부분
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    // 리스트의 전체 크기
    override fun getCount(): Int {
        return List.size
    }

}

 

BookmarkActivity -> 북마크한 데이터를 받아옵니다. 아이템 클릭 시 북마크가 해제됩니다.

class BookmarkActivity : AppCompatActivity() {

    private val TAG = BookmarkActivity::class.java.simpleName
    private val database = Firebase.database
    private val auth = Firebase.auth
    private val bookmarkRef = database.getReference("bookMark").child(auth.uid.toString())
    val dataList = mutableListOf<TestDataClass>()

    lateinit var lv : ListView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_bookmark)

        lv = findViewById(R.id.lv)

        lv.setOnItemClickListener { parent, view, position, id ->

            bookmarkRef.child(dataList[position].id).removeValue()

            dataList.clear()
            getList()

        }

        getList()
    }

    fun getList(){

        val valueEventListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {

                for (userSnapshot in dataSnapshot.children) {
                    val data = userSnapshot.getValue(TestDataClass::class.java)

                    Log.d(TAG, data.toString())
                    if (data != null) {
                        dataList.add(data)
                    }

                }
                val adapter = DataListAdapter(baseContext, dataList)
                lv.adapter = adapter

            }

            override fun onCancelled(databaseError: DatabaseError) {
                Log.e(TAG, "Failed to read value.", databaseError.toException())
            }
        }

        bookmarkRef.addListenerForSingleValueEvent(valueEventListener)


    }
}

 

TestDataClass -> 데이터셋입니다.

data class TestDataClass (

    val id : String,
    val title : String

) {
    constructor() : this("", "")
}

 

아래는 xml입니다.

acitivity_bookmark

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BookmarkActivity">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_data

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DataActivity">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/dataInsert"
        android:text="데이터 넣기"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/dataList"
        android:text="데이터 리스트 보기"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/bookmark"
        android:text="북마크 쪽으로"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>



</LinearLayout>

content_item

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp">

    <TextView
        android:id="@+id/idArea"
        android:text="id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"/>

    <TextView
        android:id="@+id/titleArea"
        android:text="title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"/>

</LinearLayout>

 

혹시 궁금하신 점 있으시거나 잘 동작하지 않으면 알려주셔요 :)

 

 

이채원님의 프로필

이채원

질문자

2023.05.22

조금 늦게 봤네요 친절히 알려주셔서 감사합니다! 다시 한번 해보겠습니다.

0

안녕하세요 채원님

제가 정확하게 질문을 이해하지는 못했는데

섹션5 - 북마크 만들기 (북마크 동적으로 삭제 버그 수정) 에서

아래와 같이 bookmarkIdList.clear() 부분을 깜빡하셔서 오류가 나는 분들이 종종 있습니다.

이 부분을 빼먹으신 것이 아니라면

전체 코드를 깃허브에 올리시고 링크를 공유해주시면 제가 실행해보겠습니다 :)

image

이채원님의 프로필

이채원

질문자

2023.05.02

답변 감사합니다. 그런데 해당 부분은 빼먹지 않았습니다. firebase내에서는 추가/삭제가 잘 되지만 북마크 탭 내에서 북마크 취소 시 레이아웃 자체가 사라지지 않고 오히려 중복으로 아이템이 더 생기는 문제가 발생합니다. visibility 속성을 이용하여 없애보려 이것저것 시도했지만 실패했습니다.. 아래는 깃허브 링크입니다!

3uomlkh/MySoleLife (github.com)

안녕하세요 채원님 답변이 늦어 죄송합니다.

코드를 실행해보니

현재 북마크를 클릭해도

image

북마크탭에 아무것도 나오지 않고 있는데 혹시 뭔가 코드를 수정하셨을까요?

image

이 부분 한번 확인해주시고 말씀해주시겠어요?

그리고 firebase realtime database 화면도 스크린샷으로 보여주시면 좋을 것 같습니다.

이채원님의 프로필

이채원

질문자

2023.05.04

저도 지금 확인해보니 갑자기 북마크탭 부분이 보이지 않습니다. 여러번 시도해보았는데 아예 안보이거나 0.1초정도 보였다가 사라집니다. 그런데 firebase realtime database에서는 북마크를 추가하거나 삭제하는 것이 잘 됩니다. 레이아웃에서만 안보이는 것 같은데 특별히 수정한 부분도 없고 그 이유를 모르겠습니다..일단 아래는 database 화면입니다.

image

추가로 BookmarkRVAdapter내에서 로그창에 title과 imageUrl을 출력해보니 제대로 출력이 됩니다.

image

그리고 혹시 몰라 헷갈릴 것 같은 코드는 다 빼고 다시 수정하여 깃허브에 올려놓았습니다! 편하실때 한 번 확인해주시면 감사하겠습니다.

 

아래의 코드를 기입하시고 이전 코드와 비교해보세요!

BookmarkFragment.kt

class BookmarkFragment : Fragment() {

    private lateinit var binding : FragmentBookmarkBinding

    private val TAG = BookmarkFragment::class.java.simpleName

    val bookmarkIdList = mutableListOf<String>()
    val items = ArrayList<ContentModel>()
    val itemKeyList = ArrayList<String>()

    lateinit var rvAdapter : BookmarkRVAdapter

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

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_bookmark, container, false)

        // 2. 사용자가 북마크한 정보를 다 가져옴!
        getBookmarkData()


        rvAdapter = BookmarkRVAdapter(requireContext(), items, itemKeyList, bookmarkIdList)

        val rv : RecyclerView = binding.bookmarkRV
        rv.adapter = rvAdapter

        rv.layoutManager = GridLayoutManager(requireContext(), 2)

        binding.homeTap.setOnClickListener {
            it.findNavController().navigate(R.id.action_bookmarkFragment_to_homeFragment)
        }

        binding.tipTap.setOnClickListener {
            it.findNavController().navigate(R.id.action_bookmarkFragment_to_tipFragment)
        }

        binding.talkTap.setOnClickListener {
            it.findNavController().navigate(R.id.action_bookmarkFragment_to_talkFragment)
        }

        binding.storeTap.setOnClickListener {
            it.findNavController().navigate(R.id.action_bookmarkFragment_to_storeFragment)
        }

        return binding.root
    }

    private fun getCategoryData(){

        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {

                for (dataModel in dataSnapshot.children) {

                    Log.d(TAG, dataModel.toString())
                    val item = dataModel.getValue(ContentModel::class.java)

                    // 3. 전체 컨텐츠 중에서, 사용자가 북마크한 정보만 보여줌!
                    if (bookmarkIdList.contains(dataModel.key.toString())){
                        items.add(item!!)
                        itemKeyList.add(dataModel.key.toString())
                    }


                }
                rvAdapter.notifyDataSetChanged()

            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Log.w("ContentListActivity", "loadPost:onCancelled", databaseError.toException())
            }
        }
        FBRef.category1.addValueEventListener(postListener)
        FBRef.category2.addValueEventListener(postListener)

    }

    private fun getBookmarkData(){

        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {

                for (dataModel in dataSnapshot.children) {

                    Log.e(TAG, dataModel.toString())
                    bookmarkIdList.add(dataModel.key.toString())

                }

                // 1. 전체 카테고리에 있는 컨텐츠 데이터들을 다 가져옴!
                getCategoryData()
            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Log.w("ContentListActivity", "loadPost:onCancelled", databaseError.toException())
            }
        }
        FBRef.bookmarkRef.child(FBAuth.getUid()).addValueEventListener(postListener)

    }



}

BookmarkRVAdapater.kt

class BookmarkRVAdapter(val context : Context,
                        val items : ArrayList<ContentModel>,
                        val keyList : ArrayList<String>,
                        val bookmarkIdList : MutableList<String>)

    : RecyclerView.Adapter<BookmarkRVAdapter.Viewholder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkRVAdapter.Viewholder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.content_rv_item, parent, false)

        Log.d("BookmarkRVAdapter", keyList.toString())
        Log.d("BookmarkRVAdapter", bookmarkIdList.toString())
        return Viewholder(v)
    }

    override fun onBindViewHolder(holder: BookmarkRVAdapter.Viewholder, position: Int) {
        holder.bindItems(items[position], keyList[position])
    }

    override fun getItemCount(): Int {
        return items.size
    }

    inner class Viewholder(itemView : View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(item : ContentModel, key : String) {

            itemView.setOnClickListener {
                Toast.makeText(context, item.title, Toast.LENGTH_LONG).show()
                val intent = Intent(context, ContentShowActivity::class.java)
                intent.putExtra("url", item.webUrl)
                itemView.context.startActivity(intent)
            }

            val contentTitle = itemView.findViewById<TextView>(R.id.TextArea)
            val imageViewArea = itemView.findViewById<ImageView>(R.id.imageArea)
            val bookmarkArea = itemView.findViewById<ImageView>(R.id.BookmarkArea)

            if(bookmarkIdList.contains(key)) {
                bookmarkArea.setImageResource(R.drawable.bookmark_color)
            } else {
                bookmarkArea.setImageResource(R.drawable.bookmark_white)
            }

            contentTitle.text = item.title

            Glide.with(context)
                .load(item.imageUrl)
                .into(imageViewArea)

        }

    }


}

 

아래와 같이 나오는 것을 확인했습니다.

image

image

https://m.blog.naver.com/pmw9440/221780643456

이런 사이트를 이용해보셔도 좋습니다.

이채원님의 프로필

이채원

질문자

2023.05.06

감사합니다. 비교해보니 코드는 같은데 왜 안나왔는지 모르겠네요..다시 북마크가 제대로 실행되기는 하는데 북마크 탭 내에서 '참치맛나니 초간단 레시피'의 북마크를 해제했을때,

image

다음과 같이 북마크를 해제한 아이템은 북마크 색깔만 바뀐채 그대로 있고, 북마크를 해제하지 않은 아이템은 또 새로 추가됩니다.( firebase database 내에서는 정상적으로 삭제됩니다.)

image

다른 탭에 갔다가 되돌아오면 아래와 같이 정상적으로 돌아오긴하지만, 북마크를 누르면 바로 아래와 같은 결과가 나오도록 만들고 싶습니다. 어떤 방법으로 해야하는지 궁금합니다. 혹은 어떤 관련된 검색 키워드가 있을까요? 감사합니다.
image

현재 github 업데이트가 2일 전인데 최신 코드를 올려주시면 살펴볼게요~

이채원님의 프로필

이채원

질문자

2023.05.06

네 올렸습니다!

넵 코드 공유 감사합니다.

그런데 문의주신 부분의 기능을 강의 어디에서 다루고 있을까요?? (시간이 조금 지나서 찾기가 어렵네요)

 

코드를 살펴보니 onDataChange에서 실시간으로 데이터가 observe 되면서 변경되다 보니 신경써줘야 할 것들이 있어서 좀 많이 코드를 변경해줘야 할 것 같습니다.

 

사실 firebase를 이용해서 임시로 북마크를 만들어놨는데, 실제로는 별도의 북마크 db를 만들어오고 불러와서 사용합니다.

(강의에서는 난이도 조절을 위해 전체 데이터를 불러와서 북마크 id만 보여줍니다.)

 

이 부분은 그냥 넘어가시는게 어떨까요??

만약 이 부분을 변경하면 코드가 많이 변경되어, 기존 강의와 다른 의도로 실습을 진행하실 것 같습니다.

좀 이상적인 방법을 말씀드리면

firebase에 북마크를 저장할 때

userId 기반으로

  • bookmark

라는 child 를 만들어서 이 안에 전체 id / url / title 등을 저장하는 것이 바람직합니다 :)

 

이채원님의 프로필

이채원

질문자

2023.05.08

별도의 북마크 db를 만들어서 가져오는 방식은 이 강의에서 한 것과는 완전히 다른걸까요? 사실 북마크 하는 방법을 찾다가 이 강의를 듣게 된거라 궁금한 점이 많았습니다. 현재 따로 만들어보고 있는 작업에서 이런 식으로 저장되고 있긴 합니다만...

image

북마크 탭에서 해제했을때 아이템이 중복으로 생기는 문제점은 똑같네요. 따로 더 찾아봐야 할 것같습니다. 혹시 선생님의 다른 강의들 중 북마크에 대해 더 자세히 나와있는 강의가 있다면 알려주실 수 있으실까요? 답변 감사합니다.

방향은 맞는 것 같습니다만

따로 북마크만 강의를 제작한 것이 없어서.. 제가 빠르면 이번주말, 늦어지면 다음주말에 시간날 때 북마크부분 구현하는 예제를 만들어서 유튜브에 올려놓겠습니다..!

유튜브에 올리고 링크 공유드리겠습니다

이채원님의 프로필

이채원

질문자

2023.05.11

네! 번거로우실텐데 정말 감사합니다. 저도 더 공부해보겠습니다.