[Flutter] 소켓 연결 + 구독 + 채팅 보내기/Socket connect + subscribe + send message
결과화면
- 웹소켓을 통해 내용 입력하면 실시간으로 아래 채팅 리스트가 변하는 기능을 만들어볼 것이다.
- user_1이 다른 유저, 이름 가린 게 나다!
0. 의존성 다운
https://pub.dev/packages/stomp_dart_client
dependencies:
stomp_dart_client: ^0.4.4
1. 소켓 연결
- StompClient를 사용하여 소켓을 연결한다.
- 소켓의 base url를 넣어주고, 연결될 때 구독을 하는 함수는 onConnect에 넣어두었다.
- 마지막으로 활성화를 해주면 연결 성공!!
// 소켓 연결
void _connectWebSocket() async {
const storage = FlutterSecureStorage();
const storageKey = 'kakaoAccessToken';
String token = await storage.read(key: storageKey) ?? "";
// stomp 클라이언트 생성
_stompClient = StompClient(
config: StompConfig(
url: AppUrl.webSocketUrl, // 소켓 base url (ex: ws://0.00.000.00:8080/abc)
onConnect: (frame) {
_onConnect(frame, token);
},
stompConnectHeaders: {'x-access-token': token},
webSocketConnectHeaders: {'x-access-token': token},
onDebugMessage: (p0) => print("mmm socket: $p0"),
));
// 클라이언트 활성화
_stompClient!.activate();
}
2. 구독하기
- 구독 url을 넣고, 구독 성공하여 콜백 받을 때마다 할 코드를 적는다.
// 구독
void _onConnect(StompFrame frame, String token) {
// ...
// 연결 되면 구독
_stompClient?.subscribe(
destination: AppUrl.socketSubscribeStageUrl, // 구독 url (ex: /topic/stage)
callback: (StompFrame frame) {
// 구독 성공하면 콜백 올 때마다 할 일
if (frame.body != null) {
// stage 상태 변경
var socketResponse = BaseSocketResponse.fromJson(
jsonDecode(frame.body.toString()), null);
_setStageType(socketResponse, frame);
}
});
}
- 나는 콜백으로 받는 상태들이 많았다. 그래서 아래와 같이 enum 클래스를 사용해 상태를 관리했다.
- 이중에 TALK_MESSAGE 상태를 받으면 채팅 리스트 목록에 추가하는 코드를 소개하려고 한다.
enum StageType {
CATCH_START,
CATCH_END,
PLAY_START,
MVP_START,
USER_COUNT,
STAGE_ROUTINE_STOP,
TALK_MESSAGE,
TALK_REACTION,
// ...등등등
}
3. 메시지 전송 함수
- stompClient의 send함수를 사용해 서버에서 요청한 값인 token을 헤더에, content를 body에 넣어 메시지를 전송한다.
// 메시지 전송하기
void _sendMessage(String message) async {
const storage = FlutterSecureStorage();
const storageKey = 'kakaoAccessToken';
String token = await storage.read(key: storageKey) ?? "";
if (_stompClient != null) {
_stompClient?.send(
destination: AppUrl.socketTalkUrl,
headers: {'x-access-token': token},
body: json.encode({"content": message}));
}
}
- 소켓을 통해 메시지를 보내는 데에성공하면 아래와 같은 값을 받는다.
- 메시지 전송/수신 모두 아래와 같은 형식의 TALK_MESSAGE 상태를 받는다.
{
"timeStamp":"2023/07/26 23:45:11",
"type":"TALK_MESSAGE",
"message":"라이브톡 메세지 전송",
"data":{
"content":"메세지내용~~",
"sender":{
"userId":"123",
"nickname":"user_1",
"profileImg":"http://이미지url"
}
}
}
- 구독 함수에서 봤듯이 소켓 응답을 받으면 _setStageType라는 함수가 불리는데, 이 함수 내용은 아래와 같다.
- 많은 enum 값들 중 TALK_MESSSAGE 값을 받으면
1. 응답받은 것을 객체화하고
2. Provider의 addTalk 함수에 전달한다.
void _setStageType(BaseSocketResponse response, StompFrame frame) {
switch (response.type) {
// ...
// 메시지 상태
case StageType.TALK_MESSAGE:
// 응답받기
var socketResponse = BaseSocketResponse<TalkMessageResponse>.fromJson(
jsonDecode(frame.body.toString()),
TalkMessageResponse.fromJson(
jsonDecode(frame.body.toString())['data']));
// content와 sender 정보를 provider의 addTalk 함수에 전달
var talk = StageTalkListItem(
content: socketResponse.data!.content,
sender: socketResponse.data!.sender);
_stageProvider.addTalk(talk);
break;
default:
// ...
}
}
- Provider의 addTalk 함수는 다음과 같다.
- provider가 관리하는 채팅 목록인 talkList에 새 채팅 정보를 삽입한다.
class StageProviderImpl extends ChangeNotifier implements StageProvider {
final List<StageTalkListItem> _talkList = [];
List<StageTalkListItem> get talkList => _talkList;
void addTalk(StageTalkListItem talk) {
// 채팅 목록의 0번째에 새 채팅 정보 넣기
_talkList.insert(0, talk);
// noti
notifyListeners();
}
// ...
}
여기까지 하면 실시간으로 채팅 전송하고 받기 성공~~~^^^
참고
- socket
소켓 연결은 안드때부터 꼭 해보고 싶던 기능 중 하나였다.
안드가 아니라 플러터에서 먼저 해보게 될 줄이야...
자료가 많아서 그런지 연결 자체엔 큰 어려움이 없었다.
근데 점점 스테이지 로직이 복잡해지고, 관리해야 하는 상태들이 많아지고...
한 상태와 다른 상태가 서로 복합적이고... 이런 것들을 관리하는 게 힘들었다.
이 코드는 소켓 연결 최대한 간결히 설명한 거다...
재밌었다. 소켓 연결 ㅎㅎ