[안드로이드] socket.io를 이용한 간단한 채팅 어플 만들어보기 (2)

동현·2021년 2월 16일
0
post-thumbnail

저번의 간단한 초기 세팅에 이어 본격적으로 채팅을 구현해볼 생각이다.

1. 채팅방 입장 / 퇴장 기능 구현

socket.io는 이벤트 발생 시 행동을 정의하는 on과 이벤트를 발생시키는 emit을 통해 서버와 클라이언트가 통신하게 만들 수 있다.


io.sockets.on('connection', (socket) => {
  console.log(`Socket connected : ${socket.id}`)

  socket.on('발생할 이벤트', (data) => {
    socket.emit('클라이언트 측에 발생시킬 이벤트', '보낼 데이터')
  })
}

mSocket.on("발생할 이벤트", args -> mSocket.emit("서버 측에 발생시킬 이벤트", "보낼 데이터");

예를 들어 이런 식으로 말이다.

이를 이용해서 채팅방 입장 / 퇴장 기능을 구현해보자. 먼저 채팅방을 입장 / 퇴장할 때에는 유저의 이름과 방의 정보를 담아 서버에 보내야 한다.

public class RoomData {
    private String username;
    private String roomNumber;

    public RoomData(String username, String roomNumber) {
        this.username = username;
        this.roomNumber = roomNumber;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRoomNumber() {
        return roomNumber;
    }

    public void setRoomNumber(String roomNumber) {
        this.roomNumber = roomNumber;
    }
}

따라서 다음과 같이 서버에 보내줄 RoomData 클래스를 정의해주었다.

mSocket.on(Socket.EVENT_CONNECT, args -> {
        mSocket.emit("enter", gson.toJson(new RoomData(username, roomNumber)));
});

소켓이 연결될 때, RoomData를 서버의 enter 이벤트로 전송해준다.

socket.on('enter', (data) => {
    const roomData = JSON.parse(data)
    const username = roomData.username
    const roomNumber = roomData.roomNumber

    socket.join(`${roomNumber}`)
    console.log(`[Username : ${username}] entered [room number : ${roomNumber}]`)
})

서버에서는 이를 받아 방에 들어가는 join 메소드를 호출해주면 된다.

mSocket.emit("left", gson.toJson(new RoomData(username, roomNumber)));
mSocket.disconnect();
socket.on('left', (data) => {
    const roomData = JSON.parse(data)
    const username = roomData.username
    const roomNumber = roomData.roomNumber

    socket.leave(`${roomNumber}`)
    console.log(`[Username : ${username}] left [room number : ${roomNumber}]`)
}

퇴장할 때도 같은 맥락으로 하되, join 대신 leave 메소드를 호출해주면 된다.

2. 실시간 채팅 기능 구현

이제 이 어플에 가장 핵심적인 기능이 되는 채팅 기능의 구현이다. 채팅을 보낼 때 메세지의 정보 또한 서버로 보내야하므로 MessageData 클래스를 정의해주었다.

public class MessageData {
    private String type;
    private String from;
    private String to;
    private String content;
    private long sendTime;

    public MessageData(String type, String from, String to, String content, long sendTime) {
        this.type = type;
        this.from = from;
        this.to = to;
        this.content = content;
        this.sendTime = sendTime;
    }

    public String getType() { return type; }

    public void setType(String type) { this.type = type; }

    public String getFrom() { return from; }

    public void setFrom(String from) { this.from = from; }

    public String getTo() { return to; }

    public void setTo(String to) { this.to = to; }

    public String getContent() { return content; }

    public void setContent(String content) { this.content = content; }

    public long getSendTime() { return sendTime; }

    public void setSendTime(long sendTime) { this.sendTime = sendTime; }
}

여기서 서버에 보내기로 한 정보는 다음과 같다.

type : 메세지의 타입 (ENTER, LEFT, MESSAGE)
from : 메세지를 보낸 유저의 이름 (username)
to : 메세지를 보낼 방 (roomNumber)
content : 메세지의 내용
sendTime : 메세지를 보낸 시간

sendTime의 경우 System.currentTimeMillis()로 현재 시간을 long 타입을 보내고, 안드로이드 측에서 화면에 보낸 시각을 띄울 때, 따로 처리를 해줄 것이다.

private void sendMessage() {
    mSocket.emit("newMessage", gson.toJson(new MessageData("MESSAGE",
            username,
            roomNumber,
            binding.contentEdit.getText().toString(),
            System.currentTimeMillis())));
    binding.contentEdit.setText("");
}

안드로이드 측에서는 해당 데이터를 서버 측의 newMessage 이벤트에 전송할 것이며

socket.on('newMessage', (data) => {
    const messageData = JSON.parse(data)
    console.log(`[Room Number ${messageData.to}] ${messageData.from} : ${messageData.content}`)
    io.to(`${messageData.to}`).emit('update', JSON.stringify(messageData))
})

서버에서는 to 메소드를 통해, 해당 방에 있는 사람들에게 update 이벤트에 이 데이터를 전송한다.

mSocket.on("update", args -> {
        MessageData data = gson.fromJson(args[0].toString(), MessageData.class);
        addChat(data);
});
        
private void addChat(MessageData data) {
    runOnUiThread(() -> {
        if (data.getType().equals("ENTER") || data.getType().equals("LEFT")) {
            adapter.addItem(new ChatItem(data.getFrom(), data.getContent(), toDate(data.getSendTime()), ChatType.CENTER_CONTENT));
            binding.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
        } else {
            if (username.equals(data.getFrom())) {
                adapter.addItem(new ChatItem(data.getFrom(), data.getContent(), toDate(data.getSendTime()), ChatType.RIGHT_CONTENT));
                binding.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
            } else {
                adapter.addItem(new ChatItem(data.getFrom(), data.getContent(), toDate(data.getSendTime()), ChatType.LEFT_CONTENT));
                binding.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
            }
        }
    });
}

// System.currentTimeMillis를 몇시:몇분 am/pm 형태의 문자열로 반환
private String toDate(long currentMiliis) {
    return new SimpleDateFormat("hh:mm a").format(new Date(currentMiliis));
}

이제 이를 리사이클러뷰에 채팅 메세지를 표시해주면 된다.

3. 입장 / 퇴장 메세지 구현

socket.on('enter', (data) => {
    const roomData = JSON.parse(data)
    const username = roomData.username
    const roomNumber = roomData.roomNumber

    socket.join(`${roomNumber}`)
    console.log(`[Username : ${username}] entered [room number : ${roomNumber}]`)
    
    const enterData = {
      type : "ENTER",
      content : `${username} entered the room`  
    }
    socket.broadcast.to(`${roomNumber}`).emit('update', JSON.stringify(enterData))
})
socket.on('left', (data) => {
    const roomData = JSON.parse(data)
    const username = roomData.username
    const roomNumber = roomData.roomNumber

    socket.leave(`${roomNumber}`)
    console.log(`[Username : ${username}] left [room number : ${roomNumber}]`)

    const leftData = {
      type : "LEFT",
      content : `${username} left the room`  
    }
    socket.broadcast.to(`${roomNumber}`).emit('update', JSON.stringify(leftData))
})

마찬가지로 안드로이드 측의 update 이벤트에 입장 / 퇴장했을 때의 데이터를 전송해서 메시지를 띄울 수 있다. 또한, 본인에게는 입장 / 퇴장 메시지를 띄울 것이기 때문에 방에 있는 모든 사람에게 전송하는 to 메소드가 아닌 broadcast.to 메소드를 사용하였다.

4. 실행 결과

자세한 코드는 깃허브에 올려놓았다. 추가적으로 이미지 전송 기능도 구현해볼 생각이다.

5. 참조

npm, "socket.io", https://www.npmjs.com/package/socket.io
Joyce Hong, "Building an Android Chat App with socket.io— All source code provided!", https://medium.com/@joycehong0524/simple-android-chatting-app-using-socket-io-all-source-code-provided-7b06bc7b5aff

profile
https://github.com/DongChyeon

0개의 댓글