본문 바로가기
Android App/Kotlin

파이어베이스-리얼타임 데이터베이스 구현(실시간 채팅 앱)

by AppJinny 2022. 12. 13.

*파이어베이스-구현하기(실시간 채팅 앱, 리얼타임 데이터베이스 사용)

-리얼타임 데이터 베이스를 이용해 채팅 앱 만들기

-채팅 앱의 기본구조 : 회원가입/로그인 -> 채팅방목록/만들기 -> 입장 -> 채팅방

-데이터베이스 구조 : rooms라는 최상위 노드 안에 각 방의 id테이블이 있으며 id안 title, users, message목록(id:message)가 있음

채팅앱 데이터베이스 구조

 

 

*파이어베이스 리얼타임 데이터 베이스를 이용해 채팅 앱 만들기

-파이어베이스 프로젝트 및 리얼타임 데이터베이스 생성 : https://heeyjinny.tistory.com/121

-생성했던 프로젝트에 앱 추가할 수 있음 - 앱추가 - 다운로드 파일 추가 - SDK추가 - 완료

 

-build.gradle(Project)

--plugins{} 에 추가

id 'com.google.gms.google-services' version '4.3.13' apply false

 

-build.gradle(Module)

--plugins{} 에 추가

id 'com.google.gms.google-services'

-- android{} 에 뷰바인딩 추가

//뷰바인딩
buildFeatures{
    viewBinding true
}

--dependencies{} 에 추가

implementation platform('com.google.firebase:firebase-bom:31.1.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-database-ktx'

 

-MainActivity.kt

/**  실시간 채팅 앱 만들기(파이어베이스-리얼타임 데이터베이스)  **/

//1
//3개 Activity 필요
//Main(로그인/회원가입)
//ChatList(채팅방 목록)
//ChatRoom(채팅방 화면)
//패키지명 우클릭 - New - Activity - 채티방목록 및 화면 생성

//2
//데이터 클래스 생성
//데이터 클래스를 생성할 model패키지 추가
//패키지명 우클릭 - New - Package - model
//model패키지에 메시지, 룸, 유저 클래스 추가

//3
//안드로이드 화면 만들기
//로그인/회원가입, 채팅방목록, 채팅방, 채팅방 메시지 화면
//activity_main.xml : 로그인/회원가입 화면
//activity_chat_list : 채팅방 목록 화면
//activity_chat_room : 채팅방 화면
//item_msg_list : 채팅방화면 목록의 메시지


//4
//User 클래스
//회원가입과 로그인 화면에서
//사용자 아이디, 비밀번호, 별명 입력받는 코드

//5
//Messages 클래스
//메시지를 주고받기 위해 사용되는 클래스
//채팅방에서 채팅 목록을 보여주기 위한 코드
//아이디, 메시지, 유저이름, 전송시간

//6
//Room 클래스
//채팅방생성, 생성된 채팅방 목록에 보여주는 클래스
//방 아이디, 방 이름

//7
//로그인/회원가입 구현하기
//MainActivity.kt

//12
//채팅방 목록/방 만들기 기능 구현하기
//ChatListActivity.kt

//13
//채팅방 구현
//ChatRoomActivity.kt

class MainActivity : AppCompatActivity() {

    //뷰바인딩
    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    //7-1
    //데이터베이스 프로퍼티에 파이어베이스 생성
    val database = Firebase.database

    //7-2
    //현재 액티비티에서 사용할 users 노드 연결
    val userRef = database.getReference("users")

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

        //11
        //로그인버튼 클릭시 signIn() 호출, 회원가입버튼 클릭 시 signUp() 호출
        binding.btnSignin.setOnClickListener { signIn() }
        binding.btnSignUp.setOnClickListener { signUp() }

    }//onCreate

    //8
    //회원가입 버튼클릭 시 호출되는 함수생성
    //signUp()
    fun signUp(){
        with(binding){
            //8-1
            //입력된 값 가져오기
            val id = editId.text.toString()
            val password = editPw.text.toString()
            val name = editName.text.toString()

            //8-2
            //모두 값이 입력되어 있는지 검사***
            if (id.isNotEmpty() && password.isNotEmpty() && name.isNotEmpty()){

                //8-3
                //화면에 모두 값이 입력되어 있다면...
                //데이터베이스에 이미 아이디가 존재하는지 검사
                userRef.child(id).get().addOnSuccessListener {

                    if (it.exists()){
                        Toast.makeText(baseContext, "아이디가 이미 존재합니다.", Toast.LENGTH_SHORT).show()
                    }else{
                        //8-4
                        //데이터베이스에 아이디가 없다면
                        //저장 후 자동으로 로그인
                        //유저데이터클래스에 id,pw,name 전달
                        val user = User(id, password, name)
                        //8-5
                        //데이터베이스 user노드안 id에 유저정보 추가...
                        userRef.child(id).setValue(user)
                        
                        //8-6
                        //회원가입 완료 알림 메시지 후
                        //로그인...진행
                        Toast.makeText(baseContext, "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show()
                        signIn()
                    }

                }
            }else{
                //8-7
                //입력창이 하나라도 비어있으면 메시지 출력
                Toast.makeText(baseContext, "아이디, 비밀번호, 별명을 모두 입력해야 합니다.", Toast.LENGTH_SHORT).show()
            }

        }//with
    }//signUp

    //9
    //로그인 버튼 클릭 시 호출되는 함수생성
    //singIn()
    fun signIn(){
        with(binding){
            //9-1
            //화면에 입력된 아이디, 비밀번호 값 가져오기
            val id = editId.text.toString()
            val password = editPw.text.toString()

            //9-2
            //만약 아이디, 비밀번호 값이 모두 입력되었다면
            if (id.isNotEmpty() && password.isNotEmpty()){
                //9-3
                //입력된 아이디로 User데이터 가져오기...
                userRef.child(id).get().addOnSuccessListener {
                    //9-4
                    //id가 데이터 베이스에 있는지 확인
                    if (it.exists()){
                        //9-5
                        //비밀번호 비교
                        it.getValue(User::class.java)?.let {
                            //9-6
                            //비밀번호가 일치하다면
                            if (it.password == password){
                                //9-7
                                //유저 아이디와 별명을 전달하면서
                                //채팅방 목록 들어가기...(밑에 생성)
                                Toast.makeText(baseContext, "로그인 되었습니다.", Toast.LENGTH_SHORT).show()
                                goChatroomList(it.id, it.name)
                            }else{
                                //9-6-1
                                //비밀번호 불일치
                                Toast.makeText(baseContext, "비밀번호가 다릅니다.", Toast.LENGTH_SHORT).show()
                            }
                        }

                    }else{
                        //9-4-1
                        //아이디 없음
                        Toast.makeText(baseContext, "아이디가 없습니다.", Toast.LENGTH_SHORT).show()
                    }
                }
            }else{
                //9-2-1
                //아이디, 비밀번호 입력이 안되었다면
                Toast.makeText(baseContext, "아이디, 비밀번호를 모두 입력해야 합니다.", Toast.LENGTH_SHORT).show()
            }
        }
    }//signIn

    //10
    //로그인 성공 시 채팅방 목록으로 넘어가기
    //사용자 정보를 파라미터로 전달
    //아이디, 별명
    fun goChatroomList(userId: String, userName: String){
        //10-1
        //채팅방 목록으로 넘어가는 인텐트 생성...
        val intent = Intent(this, ChatListActivity::class.java)

        //10-2
        //방 생성 또는 입장시 사용
        //인텐트 넘겨주면서 액티비티 시작
        intent.putExtra("userId", userId)
        intent.putExtra("userName", userName)
        startActivity(intent)
    }//goChatroomList

}//MainActivity

 

-User.kt

//회원가입과 로그인 화면에서
//사용자 아이디, 비밀번호, 별명 입력받는 코드
class User {
    var id: String = ""
    var password: String = ""
    var name: String = ""

    constructor()

    constructor(id: String, password: String, name: String){
        this.id = id
        this.password = password
        this.name = name
    }
}

 

-User.kt

//회원가입과 로그인 화면에서
//사용자 아이디, 비밀번호, 별명 입력받는 코드
class User {
    var id: String = ""
    var password: String = ""
    var name: String = ""

    constructor()

    constructor(id: String, password: String, name: String){
        this.id = id
        this.password = password
        this.name = name
    }
}

 

-ChatListActivity.kt

//채팅방 목록/방 만들기 기능 구현
class ChatListActivity : AppCompatActivity() {

    //1
    //뷰바인딩
    val binding by lazy { ActivityChatListBinding.inflate(layoutInflater) }

    //2
    //데이터베이스와 연결하기
    val database = Firebase.database

    //3
    //최상위 노드 rooms 연결
    val roomsRef = database.getReference("rooms")

    //4
    //사용자 아이디와 이름을 저장하는 프로퍼티 선언
    //채팅목록뿐만아니라 다른 액티비티에서 조회가 가능하도록
    //compain object로 선언!
    companion object{
        var userId: String = ""
        var userName: String = ""
    }

    //9
    //방 목록을 저장한 roomList 프로퍼티 선언
    val roomList = mutableListOf<Room>()

    //10
    //어댑터를 저장한 프로퍼티 선언
    lateinit var adapter: ChatRoomListAdapter

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

        //4-1
        //위 선언한 프로퍼티에
        //인텐트로 전달된 값 저장
        //만약 값이 없을 때 엘비스 연산자 사용하여 기본 값 설정
        userId = intent.getStringExtra("userId") ?: "none"
        userName = intent.getStringExtra("userName") ?: "Anonymous"

        //7
        //방만들기 버튼 클릭 시
        //만들기 다이얼로그 생성
        binding.btnCreate.setOnClickListener { openCreateRoom() }

        //11
        //화면의 리사이클러뷰와 어탭터 연결
        adapter = ChatRoomListAdapter(roomList)
        binding.recyclerRooms.adapter = adapter
        binding.recyclerRooms.layoutManager = LinearLayoutManager(baseContext)

        //13
        //목록을 갱신하는 메서드 호출
        loadRooms()

    }//onCreate

    //5
    //방 만들기 버튼 클릭 시
    //AlertDialog를 사용해
    //방 정보 입력 창 띄우기
    fun openCreateRoom(){
        //5-1
        //방 이름을 입력할 EditText를 코드로 생성
        val editText = EditText(this)
        //5-2
        //다이얼로그 생성
        val dialog = AlertDialog.Builder(this)
            .setTitle("방 이름")
            .setView(editText).setNegativeButton("취소",null)
            .setPositiveButton("만들기"){ dlg, id ->
                //5-3
                //방이름 입력 여부 체크...
                if (editText.text.isNotEmpty()){
                    createRoom(editText.text.toString())
                }else{
                    Toast.makeText(baseContext, "방 이름을 입력해야 합니다.", Toast.LENGTH_SHORT).show()
                }
        }
        //5-4
        //5-3의 속성을 가지고 있는 다이얼로그 띄우기
        dialog.show()
    }//openCreateRoom

    //6
    //실제 방을 만드는 creatRoom메서드 생성
    //다이얼로그의 만들기버튼 클릭 시 호출
    fun createRoom(title: String){
        //6-1
        //방 데이터 생성 Room클래스 데이터 저장
        val room = Room(title, userName)
        //6-2
        //방 아이디 만들어서 입력
        //.push().key!!를 이용해 데이터베이스에 자동아이디 생성
        val roomId = roomsRef.push().key!!
        room.id = roomId

        //6-3
        //파이어베이스에 전송
        roomsRef.child(roomId).setValue(room)

    }//createRoom

    //12
    //목록을 갱신하는 메서드 생성
    //addValueEventListener사용...
    //필수메서드가 2개이므로 object로 받아서 생성
    fun loadRooms(){
        roomsRef.addValueEventListener(object: ValueEventListener{
            //12-1
            //데이터가 정상적으로 변경되었으면
            //파이어베이스 리얼타임 데이터베이스에서 rooms목록을 불러온 후
            //roomList에 저장하고 목록갱신함
            override fun onDataChange(snapshot: DataSnapshot) {
                //12-1
                //기존 방목록 삭제하고 방목록에 추가
                roomList.clear()
                for (item in snapshot.children){
                    item.getValue(Room::class.java)?.let {
                        roomList.add(it)
                    }
                }
                //12-2
                //어댑터 갱신
                adapter.notifyDataSetChanged()
            }

            override fun onCancelled(error: DatabaseError) {
                print(error.message)
            }

        })
    }//loadRooms

}//ChatListActivity

//8
//채팅방을 새로 만들면
//데이터베이스에 rooms 노드가 추가됨
//그 rooms의 목록을 리사이클러뷰에 보여주기
//리사이클러뷰 어댑터를 만들고 뷰와 연결하기
//새로 생성하지 않고 액티비티 밑에 클래스 생성...
class ChatRoomListAdapter(val roomList: MutableList<Room>):
    RecyclerView.Adapter<ChatRoomListAdapter.Holder>() {//ChatRoomListAdapter

    //8-3
    //레이아웃을 생성하고 Holder에 담은 후 리턴...
    //텍스트뷰가 1개인 간단한 목록이어서 아이템 레이아웃을 따로 생성하지 않음
    //안드로이드 기본제공하는 아이템 simple_list_item_1 사용...
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val view = LayoutInflater.from(parent.context)
            .inflate(android.R.layout.simple_list_item_1,parent,false)

        return Holder(view)
    }

    //8-4
    //현재 포지션의 데이터를 꺼낸 후 홀더에 전달
    override fun onBindViewHolder(holder: Holder, position: Int) {
        val room = roomList.get(position)
        holder.setRoom(room)
    }

    //8-2
    //아이템 목록 리턴
    override fun getItemCount(): Int {
        return roomList.size
    }

    //8-1
    //아이템 레이아웃을 따로 만들지 않았기 때문에 뷰바인딩 처리는 하지 않음...
    //기본 아이템 뷰 사용예정이므로 바인딩말고
    //View를 import함
    inner class Holder(itemView: View): RecyclerView.ViewHolder(itemView){

        //13
        //목록 아이템 클릭 시 채팅방으로 이동
        lateinit var mRoom: Room
        init {
            itemView.setOnClickListener {
                val intent = Intent(itemView.context, ChatRoomActivity::class.java)
                intent.putExtra("roomId", mRoom.id)
                intent.putExtra("roomTitle", mRoom.title)
                itemView.context.startActivity(intent)
            }
        }

        //8-5
        //setRoom()메서드 생성
        //바인딩을 사용하지 않기 때문에 findViewById사용하여 위젯접근
        //기본제공 아이템레이아웃의 텍스트뷰의 아이디는 text1 임...
        fun setRoom(room: Room){
            //14
            //클릭 리스너를 init에서 구현하기때문에
            //setRoom메서드에서 채팅방을 저장하여 init에서 사용...
            this.mRoom = room

            //8-5
            itemView.findViewById<TextView>(android.R.id.text1).setText(room.title)
        }
    }

}//ChatRoomListAdapte

 

-Room.kt

//Room 클래스
//채팅방생성, 생성된 채팅방 목록에 보여주는 클래스
//방 아이디, 방 이름
class Room {
    var id: String = ""
    var title: String = ""
    //users프로퍼티에는 하나의 이름만 입력되는데
    //프로젝트가 끝난 후 여러개의 이름이 입력될 수 있도록 설계
    var users: String = ""

    constructor()

    constructor(title: String, creatorName: String){
        this.title = title
        users = creatorName
    }
}

 

-ChatRoomActivity.kt

//채팅방 구현
//데이터베이스의 rooms의 아이디노드 아래 방 정보와 함께 저장되는
//메시지 목록 사용
//rooms.child("방아이디").child("메시지들") 단계로 참조됨
class ChatRoomActivity : AppCompatActivity() {

    //1
    //뷰바인딩
    val binding by lazy { ActivityChatRoomBinding.inflate(layoutInflater) }

    //2
    //파이어베이스 데이터베이스 연결
    val database = Firebase.database

    //3
    //메시지노드를 참조하는 프로퍼티 생성
    //메시지노드 참조 시 방 아이디가 필요하기 때문에
    //lateinit 으로 먼저 선언
    lateinit var msgRef: DatabaseReference

    //4
    //방 아이디, 방 제목 프로퍼티 미리 선언
    var roomId: String = ""
    var roomTitle: String = ""

    //5
    //메시지목록 프로퍼티 선언
    val msgList = mutableListOf<Messages>()

    //6
    //어댑터를 저장할 프로퍼티 선언 후 어댑터 생성
    lateinit var adapter: MsgListAdapter

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

        //13
        //인텐트로 전달된 방 정보와 사용자 정보꺼내기
        roomId = intent.getStringExtra("roomId") ?: "none"
        roomTitle = intent.getStringExtra("roomTitle") ?: "없음"

        //14
        //메시지 노드 레퍼런스 연결
        //최상의노드 rooms안에 id에 들어가 messages생성 및 연결
        msgRef = database.getReference("rooms").child(roomId).child("messages")

        //15
        //어댑터 생성
        adapter = MsgListAdapter(msgList)

        with(binding){
            //16
            //리사이클러뷰와 어댑터 연결
            recyclerMsg.adapter = adapter
            recyclerMsg.layoutManager = LinearLayoutManager(baseContext)

            //20
            //방제목 입력
            textRoomName.text = roomTitle
            //21
            //뒤로가기버튼 방종료하기
            //전송버튼 메시지 전송
            btnBack.setOnClickListener { finish() }
            btnSend.setOnClickListener { sendMsg() }
        }

        //18
        //메시지 목록을 읽어와 갱신
        //loadMsg()호출
        loadMsg()

    }//onCreate

    //17
    //메시지 목록을 읽어오는 ValueEventListener 를 호출하는
    //loadMsg()메서드 생성
    fun loadMsg(){
        msgRef.addValueEventListener(object: ValueEventListener{
            override fun onDataChange(snapshot: DataSnapshot) {
                //17-1
                //메시지목록 삭제
                msgList.clear()
                for (item in snapshot.children){
                    //17-2
                    //메시지 목록에 추가
                    item.getValue(Messages::class.java)?.let {
                        msgList.add(it)
                    }
                }
                //17-3
                //어댑터 갱신
                adapter.notifyDataSetChanged()
            }

            override fun onCancelled(error: DatabaseError) {
                print(error.message)
            }

        })
    }//loadMsg

    //19
    //메시지를 파이어베이스에 전송하는
    //메서드 sendMsg 작성
    fun sendMsg(){
        with(binding){
            //19-1
            //입력된 메시지가 있을때만 처리...
            if (editMsg.text.isNotEmpty()){
                val message = Messages(editMsg.text.toString(), ChatListActivity.userName)
                val msgId = msgRef.push().key!!
                message.id = msgId
                msgRef.child(msgId).setValue(message)
                //19-2
                //메시지 입력 후 입력필드 삭제
                editMsg.setText("")
            }
        }
    }//sendMsg

}//ChatRoomActivity

//7
//어댑터클래스 생성
class MsgListAdapter(val msgList:MutableList<Messages>): RecyclerView.Adapter<MsgListAdapter.Holder>(){

    //10
    //뷰생성
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemMsgListBinding.inflate(LayoutInflater.from(parent.context),parent,false)

        return Holder(binding)
    }

    //11
    //현재 위치 바인딩 처리
    override fun onBindViewHolder(holder: Holder, position: Int) {
        val msg = msgList.get(position)
        holder.setMsg(msg)
    }

    //9
    //목록의 개수
    override fun getItemCount(): Int {
        return msgList.size
    }

    //8
    //홀더생성
    inner class Holder(val binding: ItemMsgListBinding): RecyclerView.ViewHolder(binding.root){
        //12
        //바인딩처리
        fun setMsg(msg:Messages){
            with(binding){
                textName.setText(msg.userName)
                textMsg.setText(msg.msg)

                //24시간 HH...
                var sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
                textDate.setText("${sdf.format(msg.time)}")
            }
        }

    }

}//MsgListAdapter

 

-Messages.kt

//메시지를 주고받기 위해 사용되는 클래스
//채팅방에서 채팅 목록을 보여주기 위한 코드
//아이디, 메시지, 유저이름, 전송시간
class Messages {
    var id: String = ""
    var msg: String = ""
    var userName: String = ""
    var time: Long = 0

    constructor()

    constructor(msg: String, userName: String){
        this.msg = msg
        this.userName = userName
        this.time = System.currentTimeMillis()
    }

}

 

-activity_main.xml 

<?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=".MainActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/key">

            <!--  로그인&회원가입 화면  -->

            <TextView
                android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="48dp"
                android:text="Firebase Chat"
                android:textSize="36sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <EditText
                android:id="@+id/editId"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="48dp"
                android:ems="10"
                android:hint="아이디"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/text" />

            <EditText
                android:id="@+id/editPw"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:ems="10"
                android:hint="비밀번호"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/editId" />

            <Button
                android:id="@+id/btnSignin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:backgroundTint="@color/teal_200"
                android:text="로그인"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/editPw" />

            <TextView
                android:id="@+id/text2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="48dp"
                android:text="회원가입일 경우 아이디와 비밀번호 \n 그리고 별명을 입력한 후 \n 회원가입 버튼을 터치합니다."
                android:textAlignment="center"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/btnSignin" />

            <EditText
                android:id="@+id/editName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:ems="10"
                android:hint="별명"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/text2" />

            <Button
                android:id="@+id/btnSignUp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:backgroundTint="@color/teal_700"
                android:text="회원가입"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/editName" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>

 

-activity_chat_list.xml

<?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=".ChatListActivity"
    android:padding="16dp">

<!--  채팅방 목록 -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="채팅방 목록"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="@+id/btnCreate"
        app:layout_constraintEnd_toStartOf="@+id/btnCreate"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/btnCreate" />


    <Button
        android:id="@+id/btnCreate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:backgroundTint="@color/teal_700"
        android:text="방 만들기"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerRooms"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnCreate" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

-activity_chat-room.xml

<?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=".ChatRoomActivity"
    android:padding="16dp">

<!--  채팅방 -->
    <ImageButton
        android:id="@+id/btnBack"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:backgroundTint="@color/teal_700"
        android:scaleType="center"
        android:src="@drawable/ic_baseline_arrow_back_24"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textRoomName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:singleLine="true"
        android:text="방제목"
        android:textAlignment="center"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="@+id/btnBack"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btnBack"
        app:layout_constraintTop_toTopOf="@+id/btnBack" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerMsg"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toTopOf="@+id/editMsg"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnBack" />

    <EditText
        android:id="@+id/editMsg"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="8dp"
        android:hint="메시지 입력"
        app:layout_constraintBottom_toBottomOf="@+id/btnSend"
        app:layout_constraintEnd_toStartOf="@+id/btnSend"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/btnSend" />

    <ImageButton
        android:id="@+id/btnSend"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:backgroundTint="@color/teal_200"
        android:src="@drawable/ic_baseline_send_24"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

<!--  메시지 목록에서 사용할 아이템 레이아웃 생성
        layout우클릭 - New - 새레이아웃리소스파일 - item_msg_list.xml-->

</androidx.constraintlayout.widget.ConstraintLayout>

 

-item_msg_list

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

<!--  채팅방 메시지 목록 아이템  -->
    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="10"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/textName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:text="이름"
            app:layout_constraintEnd_toStartOf="@+id/textMsg"
            app:layout_constraintStart_toStartOf="parent" />

        <TextView
            android:id="@+id/textMsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_weight="7"
            android:text="메시지"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/textName"
            app:layout_constraintTop_toTopOf="parent" />

    </LinearLayout>

    <TextView
        android:id="@+id/textDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2022/00/00 12:00:00"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

-결과

 

 

 

 

 

 

 

 


이 포스팅에 작성한 내용은 고돈호, ⌜이것이 안드로이드다⌟, 한빛미디어(주), 2022 에서 발췌하였습니다.