본문 바로가기

Android Project

[Android/Kotlin] 커뮤니티앱(5) - 북마크 만들기

북마크를 만들기전에 먼저 저번시간에 짜줬던 코드를 조금 다듬어볼것이다

리사이클러뷰 아이템을 클릭했을때 ContentShowActivity(웹사이트)로 이동하는 코드를 조금 더 간단하게 수정할것이다

 

ContentListActivity와 ContentRVAdapter에 있는 리사이클러뷰 아이템 클릭시 실행되는 코드를 삭제해준다

아래 작성해놓은 코드들을 삭제시켜준다 

//        // 리사이클러뷰 아이템 클릭시
//        rvAdapter.itemClick = object  : ContentRVAdapter.ItemClick {
//            override fun onClick(view: View, position: Int) {
//
//                Toast.makeText(baseContext, items[position].title, Toast.LENGTH_SHORT).show()
//
//                val intent = Intent(this@ContentListActivity, ContentShowActivity::class.java)
//                intent.putExtra("url",items[position].webUrl)
//                startActivity(intent)
//            }
//        }

ContentListActivity.kt

//    interface ItemClick{
//        fun onClick(view : View, position: Int)
//    }
//    var itemClick : ItemClick? = null
//        // 리사이클러뷰에 있는 아이템을 클릭하면 이동하도록 하는 이벤트 처리
//        if(itemClick != null){
//            holder.itemView.setOnClickListener {v ->
//                itemClick?.onClick(v, position)
//
//            }
//        }

ContentRVAdapter.kt

 

 

리사이클러뷰 아이템 클릭에 해당하는 코드를 삭제했으니, 앱을 실행시켰을때 아이템을 클릭해도 작동이 안될것이다

이제 아이템 클릭 코드를 좀더 간단하게 구현할것이다 

ContentRVAdapter에 bindItems함수로 가서 아이템뷰 클릭이벤트를 구현한다 

putExtra를 사용해서 url데이터도 함께 넘겨줘야지 웹사이트로 연결된다

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

    fun bindItems(item : ContentModel){

        // 리사이클러뷰 아이템 클릭시
        itemView.setOnClickListener {
            val intent = Intent(context, ContentShowActivity::class.java)
            //데이터도 함께 넘겨줌
            intent.putExtra("url", item.webUrl)
            itemView.context.startActivity(intent)
        }
        
         ...

    }
}

ContentRVAdapter.kt

 

이렇게하면 아까와 똑같이 아이템을 클릭했을때 웹사이트로 이동하는것을 확인할수있다

 

 

 

이제 북마크를 클릭하는 코드를 작성해볼것이다 

ContentRVAdapter로 가서 findViewById를 사용해서 북마크아이콘을 연결해주고, 북마크 클릭이벤트를 만들어준다. 

// content_rv_item에서 텍스트, 이미지,북마크를 찾아옴(연결)
val contentTitle = itemView.findViewById<TextView>(R.id.textArea)
val imageViewArea = itemView.findViewById<ImageView>(R.id.imageArea)
val bookmarkArea = itemView.findViewById<ImageView>(R.id.bookmarkArea)


// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Toast.makeText(context, "북마크 클릭", Toast.LENGTH_SHORT).show()
}

ContentRVAdapter.kt

 

 

이제 파이어베이스에다가 북마크데이터를 추가해줄건데 먼저 리얼타임데이터베이스에 들어갈 구조부터 살펴보겠다

지금은 이러한 구조로 되어있는것을 볼수있는데 

 

이런식의 구조로 만들어볼것이다

bookmark_list안에 각각 회원에대한 uid값이 있고 그 안에 그 사람이 북마크한 contentId가 있게 할것이다

contentId는 임의의 값에 해당한다고 할수있다. 이 임의의값을 키값이라고 하는데 이 키값을 로그를 사용해서 찍어주고, 키값을 받아서 저장해주도록 하겠다. 그리고 이 리스트안에 키값을 저장해주도록한다

그러면 이 리스트안에는 키값이 들어갈것이다 

package com.example.mysololife.contentsList

class ContentListActivity : AppCompatActivity() {

    lateinit var myRef : DatabaseReference

    override fun onCreate(savedInstanceState: Bundle?) {

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

        val items = ArrayList<ContentModel>()
        val itemKeyList = ArrayList<String>()     //키값을 받아서 저장
        val rvAdapter = ContentRVAdapter(baseContext, items)


         ...



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

                for (dataModel in dataSnapshot.children){
                    Log.d("ContentListActivity",dataModel.toString())
                    Log.d("ContentListActivity", dataModel.key.toString())
                    val item = dataModel.getValue(ContentModel::class.java)
                    items.add(item!!)
                    itemKeyList.add(dataModel.key.toString())

                }
                rvAdapter.notifyDataSetChanged()
                Log.d("ContentListActivity",items.toString())

            }

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

        ...


    }
}

ContentListActivity.kt

 

이제 이 키값을 어뎁터로 넘겨줄것이다

ContentListActivity에 있는 어뎁터에 itemKeyList값을 넘겨주고, ContentRVAdapter에서 itemKeyList값을 받아와준다 

그리고 북마크를 클릭했을때 토스트메시지로 key값이 출력되도록 작성해준다

package com.example.mysololife.contentsList

class ContentListActivity : AppCompatActivity() {

    lateinit var myRef : DatabaseReference

    override fun onCreate(savedInstanceState: Bundle?) {

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

        val items = ArrayList<ContentModel>()
        val itemKeyList = ArrayList<String>()     //키값을 받아서 저장
        val rvAdapter = ContentRVAdapter(baseContext, items ,itemKeyList) // itemKeyList의 값도 넘겨주기
        
        ...
            }
}

ContentListActivity.kt

 

package com.example.mysololife.contentsList

// itemKeyList값 받아오기
class ContentRVAdapter(val context : Context, val items : ArrayList<ContentModel>, val keyList : ArrayList<String>) : RecyclerView.Adapter<ContentRVAdapter.ViewHolder>(){



    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContentRVAdapter.ViewHolder {
        // 아이템 레이아웃 연결
        val v = LayoutInflater.from(parent.context).inflate(R.layout.content_rv_item, parent,false)
        return ViewHolder(v)
    }

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

        fun bindItems(item : ContentModel){

           ...

            // content_rv_item에서 텍스트, 이미지,북마크를 찾아옴
            val contentTitle = itemView.findViewById<TextView>(R.id.textArea)
            val imageViewArea = itemView.findViewById<ImageView>(R.id.imageArea)
            val bookmarkArea = itemView.findViewById<ImageView>(R.id.bookmarkArea)


            // bookmarkArea 클릭시
            bookmarkArea.setOnClickListener {
                Toast.makeText(context, keyList.toString(), Toast.LENGTH_SHORT).show()
            }
   
           ...

        }
    }


}

ContentRVAdapter.kt

 

 

 

이렇게 북마크를 누르면 해당 키리스트값이 토스트메시지로 출력되는것을 확인할수있다

 

 

키리스트들이 쭉 나오는데 이 키리스트들을 하나하나 빼와서 아까 설명했던 데이터구조처럼 만들어보도록 하겠다

이제 각각의 아이템별로 가져와야되는데 ContentRVAdapter에서 VIewHolder 클래스에 있는 bindItems함수에 key값을 String으로 지정해준다음, onBindViewHolder함수에 있는 bindItems에서 keyList[postion]을 추가해준다

이렇게하고 북마크를 클릭해주면 이제 키가 하나씩 나오는걸 확인할수있을것이다

...

override fun onBindViewHolder(holder: ContentRVAdapter.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)

        }


        // content_rv_item에서 텍스트, 이미지,북마크를 찾아옴(연결)
        val contentTitle = itemView.findViewById<TextView>(R.id.textArea)
        val imageViewArea = itemView.findViewById<ImageView>(R.id.imageArea)
        val bookmarkArea = itemView.findViewById<ImageView>(R.id.bookmarkArea)


        // bookmarkArea 클릭시
        bookmarkArea.setOnClickListener {
            Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
        }


       ...


    }

ContentRVAdapter.kt

 

 

 

이제 uid를 만들어줄건데 uid를 만들려면 FirebaseAuth코드를 사용해서 인증을 해줘야되는데 이 코드가 계속 반복된다

그래서 이 반복되는 코드들을 좀더 편리하게 쓰기위해서 아얘 하나의 파일을 만들어서 거기에 중복되는 코드를 넣어줄것이다

FBAuth.kt라는 코틀린 파일을 만들고 getUid라는 함수를 만들어준다

package com.example.mysololife.utils

class FBAuth {

    companion object{

        private lateinit var auth : FirebaseAuth

        fun getUid() : String{

            auth = FirebaseAuth.getInstance()

            //현재 사용자의 uid값 리턴
            return auth.currentUser?.uid.toString()
        }

    }
}

FBAuth.kt

 

ContentRVAdapter로 돌아와서 FBAuth에 있는 getUid함수값을 로그를 사용해서보면, 현재 나의 uid값(내가 로그한값의 uid와 동일)이 잘 찍히는것을 확인할수있다 

// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Log.d("ContentRVAdapter", FBAuth.getUid())
    Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
}

ContentRVAdapter.kt

 

현재 사용자 uid값과 contentId값을 가져왔으니깐 필요한 정보들은 다 가져왔다

이제 이 값들을 데이터베이스에 집어넣어주기만하면 된다 

데이터베이스에 집어넣어주는 작업도 반복되는 코드가 있어서 FBRef.kt이라는 파일을 만들어서 좀 더 편리하게 해볼것이다

package com.example.mysololife.utils

class FBRef {

    companion object{
        private val database = Firebase.database

        val bookmarkRef = database.getReference("bookmark_list")

    }
}

FBRef.kt

 

이렇게 FBRef파일을 만들고 어뎁터로 넘어와서,

bookmark_list를 가져와서 uid안에 키값을 넣어준다

// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Log.d("ContentRVAdapter", FBAuth.getUid())
    Toast.makeText(context, key, Toast.LENGTH_SHORT).show()

    // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는다
    FBRef.bookmarkRef.child(FBAuth.getUid()).child(key).setValue("Good")
}

ContentRVAdapter.kt

 

이제 파이어베이스를 보면 bookmark_list안에 나의현재 uid값이 있고, 그 안에 contentId라는 키값이 있는것을 확인할수있다

 

 

여기까지 북마크 데이터를 파이어베이스에 넣는 작업까지 완료했다

 

이제 북마크 데이터를 가져오는(받아오는) 작업을 해볼것이다

// 북마크 데이터 가져오는 함수
private fun getBookmarkData(){

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

            for (dataModel in dataSnapshot.children){
                Log.d("getBookmarkData", dataModel.key.toString())
                Log.d("getBookmarkData", dataModel.toString())
            }


        }

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

}

ContentListActivity.kt

이렇게해서 로그를 찍어보니깐 데이터가 변경하기쉽게 안나와서 코드를 조금 변경해줄것이다 

저렇게 말고 BookmarkModel이라는 데이터 모델을 만들어서 해줄것이다 

package com.example.mysololife.contentsList

data class BookmarkModel (
    val bookmarkIsTrue : Boolean? = null
)

BookmarkModel.kt

 

어뎁터로와서 북마크 데이터를 넣어줬던부분을 수정해준다

// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Log.d("ContentRVAdapter", FBAuth.getUid())
    Toast.makeText(context, key, Toast.LENGTH_SHORT).show()


    // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는다
    FBRef.bookmarkRef
        .child(FBAuth.getUid())
        .child(key)
        .setValue(BookmarkModel(true))
}

ContentRVAdapter.kt

setValue부분에 방금 만들었던 BookmarkModel의 true값을넣어준다

파이어베이스를 확인해보면 값이 잘 들어가있는걸 볼수있다

 

 

 

 

이제 값을 받아올건데 우리가 알고싶은값은 uid값 아래에 있는 북마크에 관련된 값(-NphaB2HiIs0ibnUQ6c)이기 때문에, child를 사용해서 코드를 작성해준다 [ -NphaB2HiIs0ibnUQ6c의 값은 contents의 id값과도 같다 ]

//데이터받아옴
FBRef.bookmarkRef.child(FBAuth.getUid()).addValueEventListener(postListener)

ContentListActivity.kt

 

이렇게 수정해주고 로그를 찍어서 확인해보면 값들이 잘 분리되어들어간것을 볼수있다

로그 결과

 

이제 contents의 id값 을 담을 리스트를 생성하고, 가져와서 넣어준다

package com.example.mysololife.contentsList

class ContentListActivity : AppCompatActivity() {

    lateinit var myRef : DatabaseReference

    // id값을 담을 리스트 생성
    val bookmarkIdList = mutableListOf<String>()


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


    // 북마크 데이터 가져오는 함수
    private fun getBookmarkData(){

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

                for (dataModel in dataSnapshot.children){
                    
                   // 리스트 가져옴
                    bookmarkIdList.add(dataModel.key.toString())
                }
                Log.d("ContentListActivity", bookmarkIdList.toString())

            }
            
           ...
           
    }


}

ContentListActivity.kt

 

로그값을 확인해보면 잘찍히는것을 확인할수있다

이제 이 데이터들을 어뎁터로 넘겨줄것이다. 아이템들을 어뎁터에서 나타내주고있기 때문에 어뎁터로 넘겨줘야한다

 

bookmarkList에 해당하는 리스트를 만들어주고, 그 bookmarkList를 사용해서

만약 kekList가 bookmarkList에 있는 정보를 포함하면 아이콘을 까맣게 칠해주고, 포함하지 않으면 하얀색으로 놔두는 코드를 짜준다 

package com.example.mysololife.contentsList

// bookmarkIdList 리스트 추가
class ContentRVAdapter(val context : Context, val items : ArrayList<ContentModel>, val keyList : ArrayList<String>, val bookmarkIdList : MutableList<String> )
    : RecyclerView.Adapter<ContentRVAdapter.ViewHolder>(){

              ...


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

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

              ...


            // 북마크 클릭했을때 색칠되게
            // key리스트들이 bookmarkIdList에 포함되어있는지
            if(bookmarkIdList.contains(key)){
                bookmarkArea.setImageResource(R.drawable.bookmark_color)
            }else{
                bookmarkArea.setImageResource(R.drawable.bookmark_white)
            }

           ...

        }
    }


}

ContentRVAdapter.kt

 

이렇게 해주면 북마크를 클릭했을때 북마크가 색칠이 되는걸 확인할수있다

 

 

이제 다시 북마크를 클릭했을때 삭제되게 만들어보겠다

if문을 사용해서 북마크가 클릭되었을때랑 클릭되지않았을때로 나눠서 코드를 짜줄것이다

 

북마크를 클릭했을때 만약 kekList가 bookmarkList에 있는 정보를 포함한거면 이미 클릭이 되어있는거니깐 파이어베이스에 등록된 북마크 데이터를 삭제해주고, 그게 아니라면 북마크 데이터를 추가해준다

아래와같이 if문을 사용해서 코드를 수정해준다

// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Log.d("ContentRVAdapter", FBAuth.getUid())
    Toast.makeText(context, key, Toast.LENGTH_SHORT).show()


    if(bookmarkIdList.contains(key)){
        // 북마크가 있을때 (북마크 클릭O)
        // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는데, 그값을 삭제
        FBRef.bookmarkRef
            .child(FBAuth.getUid())
            .child(key)
            .removeValue()

    }else{
        // 북마크가 없을때 (북마크 클릭X)
        // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는다
        FBRef.bookmarkRef
            .child(FBAuth.getUid())
            .child(key)
            .setValue(BookmarkModel(true))
    }

}

ContentRVAdapter.kt

 

여기까지하면 북마크데이터가 삭제되고 파이어베이스에도 적용이되는것을 볼수있는데, 바로 적용되는것이 아니라 앱을 나갔다가 다시 켜야지 북마크삭제가 앱에 적용이되는것을 볼수있다.

 

 

누르면 바로 삭제가 적용되도록 수정해볼것이다

그래서 bookmarkIdList에 있는 애들을 하나 삭제를 해줘야하는데 

bookmarkIdList에 있는 클릭된 북마크의 키를 삭제해준다 (이 코드는 삭제해도됨)

// bookmarkArea 클릭시
bookmarkArea.setOnClickListener {
    Log.d("ContentRVAdapter", FBAuth.getUid())
    Toast.makeText(context, key, Toast.LENGTH_SHORT).show()


    if(bookmarkIdList.contains(key)){
        // 북마크가 있을때 (북마크 클릭O)
        
        // 북마크 키 삭제 (이코드는 삭제해도됨)
        bookmarkIdList.remove(key)

        // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는데, 그값을 삭제
        FBRef.bookmarkRef
            .child(FBAuth.getUid())
            .child(key)
            .removeValue()

    }else{
        // 북마크가 없을때 (북마크 클릭X)

        // bookmark_list를 가져와서 FBAuth.getUid안에 contentId값(키값)을 집어넣는다
        FBRef.bookmarkRef
            .child(FBAuth.getUid())
            .child(key)
            .setValue(BookmarkModel(true))
    }

}

ContentRVAdapter.kt

 

그런다음 ContentListActivity로 가서 getBookmarkData()함수에 bookmarkId.clear() 라는 코드를 추가해줘야지 버그 없이 북마크가 잘 실행되는것을 볼수있다 

그래야지 기존에 리스트에 있는애들을 clear해주고 bookmarkIdList에 있는애들을 받아와서 어뎁터를 동기화시키는 형태로 진행이되게된다. 저 코드를 추가해주지 않으면 북마크를 연속으로 삭제했을때 지워지지않는 버그가 발생한다

// 북마크 데이터 가져오는 함수
private fun getBookmarkData(){

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

            bookmarkIdList.clear()

            for (dataModel in dataSnapshot.children){

                bookmarkIdList.add(dataModel.key.toString())
            }
            Log.d("ContentListActivity", bookmarkIdList.toString())
            rvAdapter.notifyDataSetChanged()

        }

ContentListActivity.kt

 

 

 

 

여기까지하면 꿀팁탭 부분에 있는 북마크부분까지 완성이된것을 볼수있다