코딩 일기장/Flutter

[Flutter] 소켓 연결 + 구독 + 채팅 보내기/Socket connect + subscribe + send message

minWachya 2023. 8. 22. 20:46
반응형

결과화면

- 웹소켓을 통해 내용 입력하면 실시간으로 아래 채팅 리스트가 변하는 기능을 만들어볼 것이다.

- 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();
  }
  
  // ...
  }

 

여기까지 하면 실시간으로 채팅 전송하고 받기 성공~~~^^^


참고


소켓 연결은 안드때부터 꼭 해보고 싶던 기능 중 하나였다.

안드가 아니라 플러터에서 먼저 해보게 될 줄이야...

 

자료가 많아서 그런지 연결 자체엔 큰 어려움이 없었다.

근데 점점 스테이지 로직이 복잡해지고, 관리해야 하는 상태들이 많아지고...

한 상태와 다른 상태가 서로 복합적이고... 이런 것들을 관리하는 게 힘들었다.

 

이 코드는 소켓 연결 최대한 간결히 설명한 거다...

재밌었다. 소켓 연결 ㅎㅎ

 

반응형