본문 바로가기
Android/Kotlin

[Android/Kotlin] 구글 Firebase - 3. Firestore를 이용한 간단한 채팅 앱

by 깐테 2021. 7. 29.

2021.07.19 - [Android/Kotlin] - [Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기

 

[Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기

2021.07.16 - [Android] - [Android] 구글 Firebase(파이어베이스) 연동하기 [Android] 구글 Firebase(파이어베이스) 연동하기 아마 요즘 가장 많이 사용하는 데이터베이스가 아닌가 할 생각이 들 정도로 많이들..

kante-kante.tistory.com

Firebase 인증과 Firestore를 이용하여 간단한 채팅 기능을 구현해보도록 하겠다.

 

인증 기능은 상단에 첨부한 이전 포스트를 참고.

 


1. 레이아웃 만들기

 

1. item 만들기

 

Layout - New - Layout Resource File

 

item_message라고 만들어준다.

 

 

 

다음과 비슷하게 UI를 만들어 준다.

 

 

2. MainActivity

 

MainActivity는 다음과 같이 만들어 준다.

 

컴포넌트 트리에 있는 이름들은 id이므로 해당 id와 같이 바꿔주면 된다.

 

 

2. 클래스 파일

1. User.kt

class User {
    constructor(){

    }

    constructor(email:String,name:String){
        this.email=email
        this.name=name

    }

    var email:String?=null
    var profileUrl:String?=null

    var name:String=""
    var profiletext:String=""
    var profile_text2:String=""

}

 

생성자를 만들고 해당 구조는 Firebase 콘솔에 만들어진다.

 

 

2. Message.kt

class Message{

    constructor(){

    }

    constructor(message:String,email:String,imageUrl:String,name:String){
        this.message=message
        this.email=email // 이메일을 받는 생성자
        this.imageUrl=imageUrl
        this.name=name
        date=Date()
    }

    
    var email:String=""
    var name:String=""
    var message:String=""
    var date:Date=Date()
    var id:String=""
    var imageUrl:String=""

}

 

메시지에 표시될 정보는 보낸 메세지와 이름, 시간 정보가 표시된다.

 

imageUrl이라고 만들어 둔 것은 추후에 메시지 옆에 유저 프로필 정보를 표시할 것이기 때문에 만들어 두었는데

이는 추후 다시 포스팅할 예정.

 

 

3. MessageAdapter.kt

2021.07.23 - [Android/Kotlin] - [Android/Kotlin] 구글 Firebase - 2. Storage를 이용하여 사진, 파일 업로드하기

 

[Android/Kotlin] 구글 Firebase - 2. Storage를 이용하여 사진, 파일 업로드하기

2021.07.19 - [Android] - [Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기 [Android/Kotlin] 구글 Firebase - 1. 인증을 이용하여 로그인하기 2021.07.16 - [Android] - [Android] 구글 Firebas..

kante-kante.tistory.com

어댑터는 이전 포스트에서도 만들어 주었는데, 데이터와 앱 사이에서 다리 역할을 해준다고 생각하면 된다.

 

class MessageAdapter(var context:Context, var messageList:ArrayList<Message>): RecyclerView.Adapter<MessageAdapter.ViewHolder> (){

    var itemClickListener:ItemClickListener?=null

    inner class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
        var nameTv:TextView=itemView.findViewById(R.id.name_tv)
        var messageTv:TextView=itemView.findViewById(R.id.message_tv)
        var timeTv:TextView=itemView.findViewById(R.id.time_tv)


        fun bind(message:Message){

            messageTv.text=message.message
            timeTv.text=String.format("%02d:%02d",message.date.hours,message.date.minutes)

            var firestore=FirebaseFirestore.getInstance()
            firestore.collection("User").document(message.email).get()
                .addOnSuccessListener {
                    var user=it.toObject(User::class.java)
                    nameTv.text=user?.name
                }

            itemView.setOnLongClickListener{
                if(itemClickListener!=null){
                    itemClickListener?.onLongClick(message)
                }
                true
            }
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        var view=LayoutInflater.from(context).inflate(R.layout.item_message,parent,false)
        return ViewHolder(view)
    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(messageList[position])

    }

    interface ItemClickListener{
        fun onLongClick(message:Message)
    }
}

 

 

4. MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var firestore:FirebaseFirestore
    lateinit var auth: FirebaseAuth

    lateinit var messageEt:EditText
    lateinit var submitBtn: Button
    lateinit var messageList:ArrayList<Message>
    lateinit var messageRv:RecyclerView
    lateinit var messageAdapter: MessageAdapter
    lateinit var instance:MainActivity
    lateinit var logoutBtn:Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        instance=this

        //채팅 메세지데이터를 관리하는 리스트 초기화
        messageList=ArrayList()

        //UI에 사용될 위젯 초기화
        messageEt=findViewById(R.id.message_et)
        submitBtn=findViewById(R.id.submit_btn)
        messageRv=findViewById(R.id.list_rv)
        logoutBtn=findViewById(R.id.logout_test)

        //Adapter초기화
        messageAdapter= MessageAdapter(this,messageList)
        messageRv.adapter=messageAdapter
        messageRv.layoutManager=LinearLayoutManager(this)

        //Firestore 초기화
        firestore= FirebaseFirestore.getInstance()
        auth= FirebaseAuth.getInstance()

        //Message Collection의 변경사항 리스너 등록
        firestore?.collection("message")
            .orderBy("date",Query.Direction.ASCENDING)
            .addSnapshotListener { querySnapshot, firebaseFirestoreException ->
            if (querySnapshot != null) {

                for(dc in querySnapshot.documentChanges){
                    //Firebase에 추가된 메세지를 messageList에 추가
                    if(dc.type==DocumentChange.Type.ADDED){
                        var firebaseMessage=dc.document.toObject(Message::class.java)
                        firebaseMessage.id=dc.document.id
                        messageList.add(firebaseMessage)
                        messageAdapter.notifyDataSetChanged()
                        messageRv.scrollToPosition(messageAdapter.itemCount-1)
                    }
                    //Firebase에 삭제된 메세지를 messageList에서도 삭제
                    if(dc.type==DocumentChange.Type.REMOVED){
                        var findedMessage=messageList.filter{message-> message.id==dc.document.id}
                        messageList.remove(findedMessage[0])
                        messageAdapter.notifyDataSetChanged()
                    }
                    //Firebase에 수정된 메세지를 messageList에서도 수정
                    if(dc.type==DocumentChange.Type.MODIFIED){
                        var firebaseMessage=dc.document.toObject(Message::class.java)
                        var findedMessage=messageList.filter{message-> message.id==dc.document.id}
                        var messageIndex=messageList.indexOf(findedMessage[0])
                        messageList.get(messageIndex).message=firebaseMessage.message
                        messageAdapter.notifyDataSetChanged()
                    }

                }
            }
        }

        //전송 버튼을 눌렀을때
        submitBtn.setOnClickListener{
            onClickSubmitBtn()
        }

        logoutBtn.setOnClickListener {
            auth.signOut()
            var intent= Intent(this,LoginActivity::class.java) //로그인 페이지 이동
            startActivity(intent)
            this.finish()
        }

        //메세지를 길게 클릭했을때
        messageAdapter.itemClickListener=object : MessageAdapter.ItemClickListener {
            override fun onLongClick(message: Message) {

                AlertDialog.Builder(instance)
                    .setItems(R.array.menu) { dialog, which ->
                        if (which == 0) {
                            firestore?.collection("message").document(message.id).delete()
                        } else if (which == 1) {
                            val input = EditText(instance)
                            AlertDialog.Builder(instance)
                                .setView(input)
                                .setPositiveButton("확인") { dialog, which ->
                                    firestore?.collection("message").document(message.id)
                                        .update("message", input.text.toString())
                                    dialog.dismiss()
                                }
                                .show()
                        }
                    }
                    .setNegativeButton("취소") { dialog, which ->
                        dialog.dismiss()
                    }
                    .show()


            }
        }

    }

    fun onClickSubmitBtn(){
        var msg=messageEt.text.toString()
        var name=name_tv.text.toString()
        
        if("".equals(msg)){
            return
        }
        
        // 사용자가 입력한 메세지로 Message 인스턴스 생성
        var message=Message(msg,auth.currentUser?.email!!,"",name)
        messageEt.setText("")   //메세지 입력창 초기화

        firestore?.collection("message").document().set(message)
            .addOnCompleteListener { task->
                if(!task.isSuccessful){
                    Toast.makeText(this,"네트워크가 원활하지 않습니다",Toast.LENGTH_SHORT).show()
                }
                //else edit

            }

    }

}

 

코드가 조금 길긴 한데 찬찬히 살펴보면

 

1. 각 버튼들과 리스트뷰, 텍스트 요소들을 초기화 해준다.

2. Firestore에 메시지를 저장하고 그 메시지를 리스트 뷰로 가져온다.

3. 전송 버튼을 클릭하면 메시지가 리스트 뷰에 보여지게 된다.

 

https://firebase.google.com/docs/firestore/query-data/listen?hl=ko 

 

Cloud Firestore로 실시간 업데이트 가져오기  |  Firebase

onSnapshot() 메서드로 문서를 수신 대기할 수 있습니다. 사용자가 제공하는 콜백이 최초로 호출될 때 단일 문서의 현재 콘텐츠로 문서 스냅샷이 즉시 생성됩니다. 그런 다음 콘텐츠가 변경될 때마

firebase.google.com

 

onSnapshot() 메서드로 문서를 수신 대기할 수 있습니다. 사용자가 제공하는 콜백이 최초로 호출될 때 단일 문서의 현재 콘텐츠로 문서 스냅샷이 즉시 생성됩니다. 그런 다음 콘텐츠가 변경될 때마다 콜백이 호출되어 문서 스냅샷을 업데이트합니다

 

해당 문서는 구글 공식 문서이며 위에 설명한 2번에 해당하는 코드인 addSnapShotListener에 대해 설명되어 있다.

 

 

itemClickListener 부분은 메시지를 클릭했을 때, 메시지를 삭제 또는 수정할 수 있도록 처리한 코드이다.

 

 

 

 

결과 화면

 

 

전송 버튼을 클릭하면 메시지를 보내고

메시지를 보낸 다음 입력창을 초기화하도록 설정했다.

 

 

다음 포스트에서는 조금 더 활용해서 유저 프로필을 메시지 옆에 표시해보도록 하려 한다.

반응형