본문 바로가기
Android

Kotlin, FireBase 채팅어플 만들기 -6- [채팅 리스트_fragment]

by LasBe 2021. 9. 9.
반응형

Kotlin, FireBase 채팅어플 만들기 -1- [Splash]
Kotlin, FireBase 채팅어플 만들기 -2- [Login]
Kotlin, FireBase 채팅어플 만들기 -3- [Sign Up]
Kotlin, FireBase 채팅어플 만들기 -4- [Bottom Navigation]
Kotlin, FireBase 채팅어플 만들기 -5- [친구창_fragment]
Kotlin, FireBase 채팅어플 만들기 -6- [채팅 리스트_fragment]
Kotlin, FireBase 채팅어플 만들기 -7- [프로필 변경_fragment]
Kotlin, FireBase 채팅어플 만들기 -8- [채팅창_activity]

[전체 코드 깃허브 주소]
LasBe-code/LasbeTalk (github.com)

[참고]
하울의 코딩 채널 - YouTube

 

GitHub - LasBe-code/LasbeTalk

Contribute to LasBe-code/LasbeTalk development by creating an account on GitHub.

github.com


[XML]


<fragment_chat.xml>

<?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="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_marginLeft="15dp"
            android:layout_marginBottom="5dp"
            android:fontFamily="cursive"
            android:text="채팅"
            android:textColor="@color/lasbeOrange"
            android:textSize="24sp"
            android:textStyle="bold" />

    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/chatfragment_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

 

<item_chat.xml>

<?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="wrap_content"
    android:orientation="horizontal"
    >
    <ImageView
        android:id="@+id/chat_item_imageview"
        android:layout_marginTop="8dp"
        android:layout_marginLeft="15dp"
        android:layout_marginBottom="4dp"
        android:layout_width="60dp"
        android:layout_height="60dp"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_marginTop="4dp">

        <TextView
            android:id="@+id/chat_textview_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:text="방이름"
            android:textColor="#000000"
            android:textSize="20sp"
            android:textStyle="bold" />
        <TextView
            android:id="@+id/chat_item_textview_lastmessage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="마지막 메세지"
            android:layout_marginTop="6dp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
</LinearLayout>

 

반응형

[데이터 모델]


import kotlin.collections.HashMap

class ChatModel (val users: HashMap<String, Boolean> = HashMap(),
                 val comments : HashMap<String, Comment> = HashMap()){
    class Comment(val uid: String? = null, val message: String? = null, val time: String? = null)
}

ChatModel의 users와 comments는 HashMap으로 구성해주었다.


[액티비티 or 프래그먼트]


import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
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.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.example.lasbetalk.MessageActivity
import com.example.lasbetalk.R
import com.example.lasbetalk.model.ChatModel
import com.example.lasbetalk.model.Friend
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_registration.*
import java.util.*
import java.util.Collections.reverse
import java.util.Collections.reverseOrder
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

class ChatFragment : Fragment() {
    companion object{
                fun newInstance() : ChatFragment {
            return ChatFragment()
        }
    }
    private val fireDatabase = FirebaseDatabase.getInstance().reference

    //메모리에 올라갔을 때
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    //프레그먼트를 포함하고 있는 액티비티에 붙었을 때
    override fun onAttach(context: Context) {
        super.onAttach(context)
    }

    //뷰가 생성되었을 때
    //프레그먼트와 레이아웃을 연결시켜주는 부분
    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_chat, container, false)
        val recyclerView = view.findViewById<RecyclerView>(R.id.chatfragment_recyclerview)
        recyclerView.layoutManager = LinearLayoutManager(requireContext())
        recyclerView.adapter = RecyclerViewAdapter()

        return view
    }

    inner class RecyclerViewAdapter : RecyclerView.Adapter<RecyclerViewAdapter.CustomViewHolder>() {

        private val chatModel = ArrayList<ChatModel>()
        private var uid : String? = null
        private val destinationUsers : ArrayList<String> = arrayListOf()

        init {
            uid = Firebase.auth.currentUser?.uid.toString()
            println(uid)

            fireDatabase.child("chatrooms").orderByChild("users/$uid").equalTo(true).addListenerForSingleValueEvent(object : ValueEventListener{
                override fun onCancelled(error: DatabaseError) {
                }
                override fun onDataChange(snapshot: DataSnapshot) {
                    chatModel.clear()
                    for(data in snapshot.children){
                        chatModel.add(data.getValue<ChatModel>()!!)
                        println(data)
                    }
                    notifyDataSetChanged()
                }

            })

        }
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {


            return CustomViewHolder(LayoutInflater.from(context).inflate(R.layout.item_chat, parent, false))
        }

        inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val imageView: ImageView = itemView.findViewById(R.id.chat_item_imageview)
            val textView_title : TextView = itemView.findViewById(R.id.chat_textview_title)
            val textView_lastMessage : TextView = itemView.findViewById(R.id.chat_item_textview_lastmessage)
        }

        override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
            var destinationUid: String? = null
            //채팅방에 있는 유저 모두 체크
            for (user in chatModel[position].users.keys) {
                if (!user.equals(uid)) {
                    destinationUid = user
                    destinationUsers.add(destinationUid)
                }
            }
            fireDatabase.child("users").child("$destinationUid").addListenerForSingleValueEvent(object : ValueEventListener {
                override fun onCancelled(error: DatabaseError) {
                }

                override fun onDataChange(snapshot: DataSnapshot) {
                    val friend = snapshot.getValue<Friend>()
                    Glide.with(holder.itemView.context).load(friend?.profileImageUrl)
                            .apply(RequestOptions().circleCrop())
                            .into(holder.imageView)
                    holder.textView_title.text = friend?.name
                }
            })
            //메세지 내림차순 정렬 후 마지막 메세지의 키값을 가져
            val commentMap = TreeMap<String, ChatModel.Comment>(reverseOrder())
            commentMap.putAll(chatModel[position].comments)
            val lastMessageKey = commentMap.keys.toTypedArray()[0]
            holder.textView_lastMessage.text = chatModel[position].comments[lastMessageKey]?.message

            //채팅창 선책 시 이동
            holder.itemView.setOnClickListener {
                val intent = Intent(context, MessageActivity::class.java)
                intent.putExtra("destinationUid", destinationUsers[position])
                context?.startActivity(intent)
            }
        }

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


리사이클러뷰 어댑터 초기화 구문에 orderByChild("users/$uid").equalTo(true)를 사용하여
자신이 포함된 채팅방의 uid를 모두 가져오게 된다.

init { uid = Firebase.auth.currentUser?.uid.toString() println(uid) fireDatabase.child("chatrooms").orderByChild("users/$uid").equalTo(true).addListenerForSingleValueEvent(object : ValueEventListener{ override fun onCancelled(error: DatabaseError) { } override fun onDataChange(snapshot: DataSnapshot) { chatModel.clear() for(data in snapshot.children){ chatModel.add(data.getValue<ChatModel>()!!) println(data) } notifyDataSetChanged() } }) }

chatrooms/"chatroomUid"/users




가장 중요한 부분인 BindViewHolder에서는
채팅 리스트에서 선택시 채팅창으로 이동하기 위해 위에서 받아놨던 chatModel로
상대방의 uid를 destinationUsers에 저장해준다.

for (user in chatModel[position].users.keys) { 
 	if (!user.equals(uid)) { 
    	destinationUid = user destinationUsers.add(destinationUid) 
    } 
}



다음 채팅 리스트에 띄울 상대방의 이름, 프로필 사진을 위해 정보를 받아와 담아주면서 연결시켜준다.

fireDatabase.child("users")
	.child("$destinationUid")
	.addListenerForSingleValueEvent(object : ValueEventListener { 
    	override fun onCancelled(error: DatabaseError) { } 
        override fun onDataChange(snapshot: DataSnapshot) { 
        	val friend = snapshot.getValue<Friend>() 
            Glide.with(holder.itemView.context).load(friend?.profileImageUrl) 
            	.apply(RequestOptions().circleCrop()) 
                .into(holder.imageView) 
            holder.textView_title.text = friend?.name 
        } 
})



가장 마지막 메세지를 띄우는 것은 TreeMap을 역순으로 선언한 뒤
chatModel의 comments를 모두 넣어주고
toTypedArray()로 배열로 변환한 뒤 첫번째 값을 lastMessageKey에 넣어준다.

역순으로 선언된 첫번째 값이니 가장 마지막에 보낸 메세지가 된다.

val commentMap = TreeMap<String, ChatModel.Comment>(reverseOrder()) 
commentMap.putAll(chatModel[position].comments) 
val lastMessageKey = commentMap.keys.toTypedArray()[0] 
holder.textView_lastMessage.text = chatModel[position].comments[lastMessageKey]?.message



마지막으로 채팅 리스트에서 선택시 아까 받아놨던 상대방의 uid를
putExtra로 넘겨주면서 채팅창으로 intent한다.

holder.itemView.setOnClickListener { 
	val intent = Intent(context, MessageActivity::class.java) 
    	intent.putExtra("destinationUid", destinationUsers[position]) 
    	context?.startActivity(intent) 
}


프래그먼트에서 있었던 문제점들은 친구창을 만들면서 대부분 해결했기 때문에 비교적 수월하게 진행할 수 있었다.


[작동]



[아쉬운 점]


제작에만 몰두해 코틀린 기초가 부족하다.

반응형

댓글


오픈 채팅