채팅 - 채팅방 생성하기

변현섭·2023년 9월 6일
0

드디어 기본 틀을 완성하였습니다. 이제 본격적으로 채팅 기능을 구현해보도록 하겠습니다. 채팅을 구현하는 방법에는 여러가지 방법이 있지만, 어떤 방법을 사용해서 구현하든 구현 난이도가 매우 높은 편에 속합니다. 저는 Firebase의 Realtime Database를 사용하는 방법이 그나마 가장 간단하다고 생각하기 때문에, 이 방법을 사용해 실시간 채팅을 구현해보도록 하겠습니다.

이번 포스팅에서는 채팅방을 생성하고, 생성한 채팅방에 입장하는 기능을 추가해보도록 하겠습니다.

1. Entity Mapping

1) ChatRoom

① default 패키지 하위로, chat_room이라는 이름의 패키지를 추가한다.

② chat_room 패키지 하위로, ChatRoom, ChatRoomController, ChatRoomService, ChatRoomRepository를 추가하고 dto 패키지를 추가한다.

④ ChatRoom에 아래의 내용을 입력한다.

@Entity
@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatRoom extends BaseTimeEntity {
    @Id
    private String chatRoomId;

    @Column(nullable = false)
    private String roomName; // 채팅방 이름

    @Column(nullable = false)
    private Integer userCount; // 채팅방 인원 수

    @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<UserChatRoom> userChatRooms = new ArrayList<>();

    public void updateUserCount(int userCount){
        this.userCount = userCount;
    }
}

⑤ ChatRoomController에 아래의 내용을 입력한다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatRoomController {
    private final ChatRoomService chatRoomService;
    private final ChatRoomRepository chatRoomRepository;
    private final JwtService jwtService;
    private final UtilService utilService;

    // 채팅방 생성
    @PostMapping("/room")
    public BaseResponse<String> CreateChatRoom(@RequestParam String roomName) {
        try {
            Long userId = jwtService.getUserIdx();
            return new BaseResponse<>(chatRoomService.createChatRoom(userId, roomName));
        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }

    // 내가 속한 채팅방 리스트 반환
    @GetMapping("/room")
    public BaseResponse<List<GetChatRoomRes>> getChatRoomList() {
        try {
            Long userId = jwtService.getUserIdx();
            return new BaseResponse<>(chatRoomService.getChatRoomListById(userId));
        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }

    // 채팅에 참여한 유저 리스트 반환, 채팅방 안에서 호출
    @GetMapping("/room/{roomId}")
    public BaseResponse<List<GetUserRes>> getUserList(@PathVariable String roomId) {
        try {
            return new BaseResponse<>(chatRoomService.getUserListById(roomId));

        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }
    
    // 친구 초대
    @PostMapping("/room/add")
    public BaseResponse<String> AddUser(@RequestBody AddUserReq addUserReq) {
        try {
            return new BaseResponse<>(chatRoomService.addUser(addUserReq));
        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }
    
    // 채팅방에 참여한 인원 수 반환
    @GetMapping("/userCount/{roomId}")
    public BaseResponse<String> getUserCount(@PathVariable String roomId) {
        try {
            return new BaseResponse<>(chatRoomService.getUserCount(roomId));

        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }

    // 채팅방 나가기
    @DeleteMapping("/room/{roomId}")
    public BaseResponse<String> exitChatRoom(@PathVariable String roomId){
        try {
            Long userId = jwtService.getUserIdx();
            return new BaseResponse<>(chatRoomService.exitChatRoom(userId, roomId));
        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }
}

⑥ ChatRoomService에 아래의 내용을 입력한다.

@Service
@RequiredArgsConstructor
public class ChatRoomService {
    private final ChatRoomRepository chatRoomRepository;
    private final UserChatRoomRepository userChatRoomRepository;
    private final TokenRepository tokenRepository;
    private final UtilService utilService;

    @Transactional
    public String createChatRoom(Long userId, String roomName) throws BaseException {
        ChatRoom chatRoom = ChatRoom.builder()
                .chatRoomId(UUID.randomUUID().toString())
                .roomName(roomName)
                .userCount(1) // 채팅방 참여 인원수
                .build();
        User user = utilService.findByUserIdWithValidation(userId);
        UserChatRoom userChatRoom = new UserChatRoom();
        userChatRoom.setChatRoom(chatRoom);
        userChatRoom.setUser(user);
        chatRoomRepository.save(chatRoom);
        userChatRoomRepository.save(userChatRoom);
        return chatRoom.getChatRoomId();
    }

    // 채팅방 인원+1
    @Transactional
    public void plusUserCount(String roomId) {
        ChatRoom chatRoom = chatRoomRepository.findChatRoomById(roomId).orElse(null);
        chatRoom.updateUserCount(chatRoom.getUserCount() + 1);
        chatRoomRepository.save(chatRoom);
    }

    // 채팅방 인원-1
    @Transactional
    public void minusUserCount(String roomId) {
        ChatRoom chatRoom = chatRoomRepository.findChatRoomById(roomId).orElse(null);
        chatRoom.updateUserCount(chatRoom.getUserCount() - 1);
        chatRoomRepository.save(chatRoom);
    }
    public String getNickNameList(String chatRoomId) throws BaseException {
        utilService.findChatRoomByChatRoomIdWithValidation(chatRoomId);
        List<UserChatRoom> userChatRooms = userChatRoomRepository.findUserChatRoomByRoomId(chatRoomId);
        List<String> nickNameList = userChatRooms.stream()
                .map(userChatRoom -> {
                    String nickName = userChatRoom.getUser().getNickName();
                    return nickName;
                })
                .collect(Collectors.toList());
        String result = String.join(", ", nickNameList);
        return result;
    }

    public List<GetUserRes> getUserListById(String chatRoomId) throws BaseException {
        utilService.findChatRoomByChatRoomIdWithValidation(chatRoomId);
        List<UserChatRoom> userChatRooms = userChatRoomRepository.findUserChatRoomByRoomId(chatRoomId);
        List<GetUserRes> getUserRes = userChatRooms.stream()
                .map(userChatRoom -> {
                    String uid = userChatRoom.getUser().getUid();
                    String nickName = userChatRoom.getUser().getNickName();
                    String profileUrl = Optional.ofNullable(userChatRoom.getUser().getProfile())
                            .map(profile -> profile.getProfileUrl())
                            .orElse(null);

                   return new GetUserRes(uid, profileUrl, nickName);
                })
                .collect(Collectors.toList());
        return getUserRes;
    }

    public String getUserCount(String chatRoomId) throws BaseException {
        ChatRoom chatRoom = utilService.findChatRoomByChatRoomIdWithValidation(chatRoomId);
        return chatRoom.getUserCount().toString();
    }

    public List<GetChatRoomRes> getChatRoomListById(Long userId) {
        List<UserChatRoom> userChatRooms = userChatRoomRepository.findUserListByUserId(userId);
        List<GetChatRoomRes> getChatRoomRes = userChatRooms.stream()
                .map(userChatRoom -> {
                    return new GetChatRoomRes(userChatRoom.getChatRoom().getChatRoomId(),
                            userChatRoom.getChatRoom().getRoomName(),
                            getNickNameList(userChatRoom.getChatRoom().getChatRoomId()));
                })
                .collect(Collectors.toList());
        return getChatRoomRes;
    }

    // 채팅방 나가기
    @Transactional
    public String exitChatRoom(Long userId, String roomId) throws BaseException {
        utilService.findChatRoomByChatRoomIdWithValidation(roomId);
        userChatRoomRepository.deleteUserChatRoomByUserIdWithRoomId(userId, roomId);
        minusUserCount(roomId);
        ChatRoom chatRoom = utilService.findChatRoomByChatRoomIdWithValidation(roomId);
        if (chatRoom.getUserCount() == 0) { // 채팅방에 아무도 안 남게 되면 Repository에서 삭제
            userChatRoomRepository.deleteUserChatRoomsByRoomId(roomId);
            chatRoomRepository.deleteChatRoomById(roomId);
        }
        // 만약 사진 업로드 기능을 추가한다면 S3에 올라간 파일도 삭제해주어야 함
        User user = utilService.findByUserIdWithValidation(userId);
        String result = user.getNickName() + "님이 " + roomId + "번 채팅방을 나갔습니다.";
        return result;
    }

    @Transactional
    public String addUser(AddUserReq addUserReq) throws BaseException {
        try {
            ChatRoom chatRoom = utilService.findChatRoomByChatRoomIdWithValidation(addUserReq.getRoomId());
            User user = utilService.findByUserUidWithValidation(addUserReq.getUid());
            UserChatRoom userChatRoom = userChatRoomRepository.findUserChatRoomByUserIdWithRoomId(user.getId(), chatRoom.getChatRoomId()).orElse(null);
            if (userChatRoom != null) { // 이미 채팅방에 추가된 유저인 경우
                throw new BaseException(BaseResponseStatus.ALREADY_EXIST_MEMBER);
            }
            userChatRoom = UserChatRoom.builder()
                    .user(user)
                    .chatRoom(chatRoom)
                    .build();

            userChatRoomRepository.save(userChatRoom);
            plusUserCount(addUserReq.getRoomId());
            return user.getNickName();
        } catch (BaseException exception) {
            throw new BaseException(exception.getStatus());
        }
    }
}

⑦ ChatRoomRepository에 아래의 내용을 입력한다.

public interface ChatRoomRepository extends JpaRepository<ChatRoom, String> {
    @Query("select c from ChatRoom c " +
            "INNER JOIN UserChatRoom uc ON c.chatRoomId = uc.chatRoom.chatRoomId " +
            "where uc.user.id = :userId and c.roomName Like %:text%")
    List<ChatRoom> findChatRoomByRoomName(@Param("userId") Long userId, @Param("text") String text);

    @Query("select c from ChatRoom c where c.chatRoomId = :chatRoomId")
    Optional<ChatRoom> findChatRoomById(@Param("chatRoomId") String chatRoomId);

    @Modifying
    @Query("delete from ChatRoom c where c.chatRoomId = :chatRoomId")
    void deleteChatRoomById(@Param("chatRoomId") String chatRoomId);
}

⑧ dto 패키지 안에 GetChatRoomRes를 추가한다.

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class GetChatRoomRes {
    private String chatRoomId;
    private String roomName;
    private String userList; // 유저의 이름을 쉼표로 구분
}

⑨ dto 패키지 안에 AddUserReq도 추가해준다.

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class AddUserReq {
    private String uid;
    private String roomId;
}

2) UserChatRoom

채팅방과 유저의 관계는 다대다 관계이므로, 두 Entity 사이에 연결테이블로 UserChatRoom이 필요하다.

① 이번에는 user_chat_room이라는 패키지를 만들고, 그 안에 UserChatRoom, UserChatRoomRepository를 추가한다.

② UserChatRoom에 아래의 내용을 입력한다.

@Entity
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UserChatRoom { // 유저와 채팅방의 다대다 관계 매핑을 위한 연결 클래스
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userChatRoomId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "chat_room_id")
    private ChatRoom chatRoom;

    public void setChatRoom(ChatRoom chatRoom){
        this.chatRoom = chatRoom;
    }

    public void setUser(User user){
        this.user = user;
    }
}

③ UserChatRoomRepository에 아래의 내용을 입력한다.

public interface UserChatRoomRepository extends JpaRepository<UserChatRoom, Long> {
    @Query("select uc from UserChatRoom uc where uc.user.id = :userId and uc.chatRoom.chatRoomId = :chatRoomId")
    Optional<UserChatRoom> findUserChatRoomByUserIdWithRoomId(@Param("userId") Long userId, @Param("chatRoomId") String chatRoomId);

    @Query("select uc from UserChatRoom uc where uc.chatRoom.chatRoomId = :chatRoomId")
    List<UserChatRoom> findUserChatRoomByRoomId(@Param("chatRoomId") String chatRoomId);

    @Query("select uc from UserChatRoom uc where uc.user.id = :userId")
    List<UserChatRoom> findUserListByUserId(@Param("userId") Long userId);

    @Modifying
    @Query("delete from UserChatRoom uc where uc.chatRoom.chatRoomId = :chatRoomId")
    void deleteUserChatRoomsByRoomId(@Param("chatRoomId") String chatRoomId);

    @Modifying
    @Query("delete from UserChatRoom uc where uc.user.id = :userId and uc.chatRoom.chatRoomId = :chatRoomId")
    void deleteUserChatRoomByUserIdWithRoomId(@Param("userId") Long userId, @Param("chatRoomId") String chatRoomId);
}

2. 채팅방 목록 구성하기

1) 채팅방 생성하기

① drawable 디렉토리 하위에 채팅방 생성을 나타낼 아이콘을 plus라는 이름으로 추가한다.

② chat_room_dialog.xml 파일을 layout 디렉토리 하위에 추가한다.

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginHorizontal="10dp"
        android:text="생성하실 채팅방의\n이름을 입력해주세요."
        android:textSize="30sp"
        android:textColor="#000000"
        android:gravity="center"
        android:background="@android:color/transparent"/>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="40dp">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/roomName"
            android:hint="채팅방 이름"
            android:padding="5dp"
            android:background="@drawable/main_border"
            android:layout_marginBottom="30dp"
            android:textColorHint="#808080"
            android:textSize="25sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </com.google.android.material.textfield.TextInputLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="100dp">

        <Button
            android:id="@+id/create"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="100dp"
            android:layout_marginTop="16dp"
            android:background="@drawable/main_border"
            android:text="OK"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.55"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</LinearLayout>

③ api 패키지 하위로, ChatApi 인터페이스를 생성한다.

interface ChatApi {
    @POST("/chat/room")
    suspend fun createChatRoom(
        @Header("Authorization") accessToken : String,
        @Query("roomName") roomName : String
    ): BaseResponse<String>
}

④ RetrofitInstance에도 ChatApi를 추가한다.

class RetrofitInstance {
    companion object {
        private val retrofit by lazy {
            Retrofit.Builder()
                .baseUrl(ApiRepository.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }
        val userApi = retrofit.create(UserApi::class.java)
        val myPageApi = retrofit.create(MyPageApi::class.java)
        val chatApi = retrofit.create(ChatApi::class.java)
    }
} 

⑤ FirebaseRef를 아래와 같이 수정한다.

class FirebaseRef {
    companion object {
        val database = Firebase.database
        val userInfo = database.getReference("userInfo")
        val chatRoom = database.getReference("chatRoom")
    }
}

2) 리스트 뷰 구성하기

① chat 패키지를 생성하고, 이 패키지 하위로 ListView의 Generics로 사용할 ChatRoom data class를 추가한다.

data class ChatRoom(
    val chatRoomId : String? = null,
    val roomName : String? = null,
    val userList : String? = null,
)

② layout 디렉토리 하위로, chat_listview_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="match_parent"
    android:background="@drawable/list_border"
    android:orientation="vertical">

    <TextView
        android:id="@+id/lvChatRoomName"
        android:text="ChatRoom Name"
        android:textStyle="bold"
        android:textColor="#000000"
        android:textSize="30sp"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/lvUserListArea"
        android:text="User List"
        android:textSize="20sp"
        android:layout_marginLeft="40dp"
        android:layout_marginBottom="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

③ fragment_chat_list.xml 파일에 ListView 태그를 추가한다.

<?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:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ChatListFragment">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:layout_marginHorizontal="10dp"
        android:layout_marginTop="20dp"
        android:paddingHorizontal="10dp"
        android:background="@drawable/list_border"
        android:text="나의 채팅방"
        android:textColor="#000000"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ListView
        android:id="@+id/LVChatRoom"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginHorizontal="10dp"
        app:layout_constraintBottom_toTopOf="@+id/linearLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <ImageView
        android:id="@+id/plus"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_margin="10dp"
        android:src="@drawable/plus"
        app:layout_constraintBottom_toTopOf="@+id/linearLayout"
        app:layout_constraintEnd_toEndOf="parent" />

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ImageView
            android:id="@+id/freind"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="1dp"
            android:layout_weight="1"
            android:gravity="center"
            android:src="@drawable/friend" />

        <ImageView
            android:id="@+id/chat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="1dp"
            android:layout_weight="1"
            android:background="@drawable/main_border"
            android:gravity="center"
            android:src="@drawable/chat" />

        <ImageView
            android:id="@+id/mypage"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="1dp"
            android:layout_weight="1"
            android:gravity="center"
            android:src="@drawable/mypage" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

④ 이제 ListView를 위한 Adapter 클래스를 만들어야 한다. chat 디렉토리 하위로 ChatRoomAdapter라는 이름의 kotlin class를 추가하자.

class ChatRoomAdapter(private val context: Context, private val dataList : List<ChatRoom>) : BaseAdapter() {
    override fun getCount(): Int {
        return dataList.size
    }

    override fun getItem(position: Int): Any {
        return dataList[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        var convertView = convertView

        if(convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.chat_listview_item, parent, false)
        }
        val listViewChatRoomName = convertView?.findViewById<TextView>(R.id.lvChatRoomName)
        val listViewUserList = convertView?.findViewById<TextView>(R.id.lvUserListArea)

        listViewChatRoomName!!.text = dataList[position].roomName
        listViewUserList!!.text = dataList[position].userList

        return convertView!!
    }
}

⑤ chat 패키지 하위로, ChatRoomActivity를 생성한다.

⑥ ChatListFragment를 아래와 같이 수정한다.

class ChatListFragment : Fragment() {

    lateinit var listViewAdapter : ChatRoomAdapter
    lateinit var nickName : String
    val chatRoomList = mutableListOf<ChatRoom>()

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

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_chat_list, container, false)

        CoroutineScope(Dispatchers.IO).launch {
            val response = getUserInfo(FirebaseAuthUtils.getUid())
            if (response.isSuccess) {
                nickName = response.result?.nickName.toString()
            } else {
                Log.d("ChatListFragment", "유저의 정보를 불러오지 못함")
            }
        }

        val listView = view.findViewById<ListView>(R.id.LVChatRoom)
        listViewAdapter = ChatRoomAdapter(requireActivity(), chatRoomList)
        listView.adapter = listViewAdapter

        getChatRoomList()

        listView.setOnItemClickListener { parent, view, position, id ->
            val chatRoomId = chatRoomList!![position].chatRoomId
            val chatRoomName = chatRoomList!![position].roomName
            val userList = chatRoomList!![position].userList
            val intent = Intent(requireActivity(), ChatRoomActivity::class.java)
  
            intent.putExtra("chatRoomId", chatRoomId)
            intent.putExtra("chatRoomName", chatRoomName)
            intent.putExtra("userList", userList)
            intent.putExtra("managerUid", managerUid)
            startActivity(intent)
        }

        val plusBtn = view.findViewById<ImageView>(R.id.plus)
        plusBtn.setOnClickListener {
            showDialog()
        }

        val freind = view.findViewById<ImageView>(R.id.freind)
        freind.setOnClickListener {
            it.findNavController().navigate(R.id.action_chatListFragment_to_userListFragment)
        }

        val mypage = view.findViewById<ImageView>(R.id.mypage)
        mypage.setOnClickListener {
            it.findNavController().navigate(R.id.action_chatListFragment_to_myPageFragment)
        }
        return view
    }

    private fun showDialog() {
        val dialogView = LayoutInflater.from(requireActivity()).inflate(R.layout.chat_room_dialog, null)
        val builder = AlertDialog.Builder(requireActivity())
            .setView(dialogView)
            .setTitle("채팅방 생성하기")
        val alertDialog = builder.show()

        val createBtn = alertDialog.findViewById<Button>(R.id.create)
        createBtn.setOnClickListener {
            val roomName = alertDialog.findViewById<TextInputEditText>(R.id.roomName)
            val roomNameStr = roomName.text.toString()
            if(roomNameStr.isEmpty()) {
                Toast.makeText(requireActivity(), "채팅방 이름을 입력해주세요", Toast.LENGTH_SHORT).show()
            }
            else {
                getAccessToken { accessToken ->
                    if (accessToken.isNotEmpty()) {
                        CoroutineScope(Dispatchers.IO).launch {
                            val response = createChatRoom(accessToken, roomNameStr)
                            if (response.isSuccess) {
                                Log.d("ChatListFragment", response.toString())
                                val uidList = mutableListOf<String>()
                                uidList.add(FirebaseAuthUtils.getUid())
                                val roomId = response.result
                                val chatRoom = ChatRoom(roomId!!, roomNameStr, nickName)
                                Log.d("ChatRoom", chatRoom.toString())
                                FirebaseRef.chatRoom.child(FirebaseAuthUtils.getUid()).child(roomId!!).setValue(chatRoom)
                                withContext(Dispatchers.Main) {
                                    Toast.makeText(requireActivity(), "채팅방이 생성되었습니다", Toast.LENGTH_SHORT).show()
                                }
                            }
                            else {
                                Log.d("ChatListFragment", "채팅방 생성 실패")
                                val message = response.message
                                Log.d("ChatListFragment", message)
                                withContext(Dispatchers.Main) {
                                    Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
                                }
                            }
                        }
                    } else {
                        Log.e("ChatListFragment", "Invalid Token")
                    }
                }
                alertDialog.dismiss()
            }
        }
    }

    private suspend fun createChatRoom(accessToken : String, roomName : String): BaseResponse<String> {
        return RetrofitInstance.chatApi.createChatRoom(accessToken, roomName)
    }

    private fun getAccessToken(callback: (String) -> Unit) {
        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val data = dataSnapshot.getValue(com.chrome.chattingapp.authentication.UserInfo::class.java)
                val accessToken = data?.accessToken ?: ""
                callback(accessToken)
            }

            override fun onCancelled(databaseError: DatabaseError) {
                Log.w("ChatListFragment", "onCancelled", databaseError.toException())
            }
        }

        FirebaseRef.userInfo.child(FirebaseAuthUtils.getUid()).addListenerForSingleValueEvent(postListener)
    }

    private fun getChatRoomList() {
        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                chatRoomList.clear()
                for (datamModel in dataSnapshot.children) {
                    val chatRoom = datamModel.getValue(ChatRoom::class.java)
                    chatRoomList.add(chatRoom!!)
                }
                listViewAdapter.notifyDataSetChanged()
            }

            override fun onCancelled(databseError: DatabaseError) {
                Log.w("MyMessage", "onCancelled", databseError.toException())
            }
        }
        FirebaseRef.chatRoom.child(FirebaseAuthUtils.getUid()).addValueEventListener(postListener)
    }

    private suspend fun getUserInfo(uid: String): BaseResponse<GetUserRes> {
        return RetrofitInstance.myPageApi.getUserInfo(uid)
    }
}

3) 채팅방 입장

① 메시지 전송에 사용할 아이콘을 send라는 이름으로, 참여자 목록에 사용할 아이콘을 participants라는 이름으로 drawable 디렉토리 하위에 추가한다.

② acticity_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:background="@drawable/main_border"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".chat.ChatRoomActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ScrollView>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="110dp"
        android:background="@drawable/main_border"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/userCount"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginTop="55dp"
            android:layout_marginLeft="10dp"
            android:text="1"
            android:gravity="right"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="@+id/roomName"
            app:layout_constraintHorizontal_bias="0.774"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:layout_width="35dp"
            android:layout_height="30dp"
            android:layout_marginTop="55dp"
            android:gravity="right"
            android:text="명 :"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="@+id/roomName"
            app:layout_constraintHorizontal_bias="0.774"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/nickNameList"
            android:layout_width="220dp"
            android:layout_height="30dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="55dp"
            android:text="User List"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="@+id/roomName"
            app:layout_constraintHorizontal_bias="0.774"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/invite"
            android:layout_width="70dp"
            android:layout_height="40dp"
            android:layout_marginTop="15dp"
            android:background="@color/skyBlue"
            android:text="초대"
            android:textSize="20sp" />

    </LinearLayout>

    <ImageView
        android:id="@+id/participants"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_marginTop="120dp"
        android:layout_marginRight="15dp"
        android:src="@drawable/participants"
        android:textSize="25sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/chatRoomName"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="ChatRoomName"
        android:textColor="#000000"
        android:textSize="30sp"
        android:layout_marginTop="10dp"
        android:layout_marginHorizontal="20dp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        tools:layout_editor_absoluteX="-19dp">

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/message"
                android:layout_width="330dp"
                android:layout_height="50dp"
                android:layout_marginLeft="13dp"
                android:layout_marginBottom="10dp"
                android:background="@drawable/main_border"
                android:hint="텍스트를 입력하세요"
                android:padding="5dp"
                android:textColorHint="#808080"
                android:textSize="25sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

        </com.google.android.material.textfield.TextInputLayout>



        <ImageView
            android:id="@+id/send"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            android:src="@drawable/send" />
    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

③ ChatRoomActivity에 아래의 내용을 입력한다.

class ChatRoomActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chat_room)

        val roomName = findViewById<TextView>(R.id.chatRoomName)
        val nickNameList = findViewById<TextView>(R.id.nickNameList)
        val userCount = findViewById<TextView>(R.id.userCount)
        val sendBtn = findViewById<ImageView>(R.id.send)

        // Intent로부터 데이터를 가져옴
        roomName.text = intent.getStringExtra("chatRoomName")
        nickNameList.text = intent.getStringExtra("userList")
        val chatRoomId = intent.getStringExtra("chatRoomId")
    }
}

이제 코드를 실행시켜보자. 채팅방을 추가하면 채팅방 목록에 추가되고, 채팅방을 클릭하면 해당 채팅방에 입장할 수 있다.

제목이 너무 길 경우 제목이 잘리는 현상이 발생할 수 있으므로, 채팅방 제목의 글자 수 제한을 두는 것이 좋을 것 같다. 글자수 제한은 15 글자로 설정하기로 하자. chat_room_dialog.xml 파일을 아래와 같이 수정한다.

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginHorizontal="10dp"
        android:text="생성하실 채팅방의\n이름을 입력해주세요."
        android:textSize="30sp"
        android:textColor="#000000"
        android:gravity="center"
        android:background="@android:color/transparent"/>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="40dp"
        app:counterMaxLength="15"
        app:counterEnabled="true">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/roomName"
            android:hint="채팅방 이름"
            android:padding="5dp"
            android:background="@drawable/main_border"
            android:layout_marginBottom="10dp"
            android:textColorHint="#808080"
            android:textSize="25sp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </com.google.android.material.textfield.TextInputLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="100dp">

        <Button
            android:id="@+id/create"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="100dp"
            android:layout_marginTop="20dp"
            android:background="@drawable/main_border"
            android:text="OK"
            android:textSize="20sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.55"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</LinearLayout>

채팅방 이름이 15 글자를 넘겼을 때, 토스트 메시지를 띄워주는 등의 예외처리는 직접 해보기 바란다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글