본문 바로가기

플러터(Flutter)

StreamBuilder를 통해 chatting message 가져오기

1. 채팅 메세지 전송 시 추가 사항

메세지 전송 버튼을 클릭해 firestore에 저장할 때, Timestamp를 활용해 시간을 기록

    await _firestore
    .collection('chatting').doc('messages')
    .collection('data').doc(widget.chattingRoomId)
    .collection('chat').add({
      'uid' : _authentication.currentUser!.uid,
      'message' : _sendedMessage,
      'time' : Timestamp.now(),
    });

 

2. StreamBuilder 및 FutureBuilder 사용

2.0. Stream과 Future의 차이

출처 : 유튜브 코딩셰프 - https://youtu.be/YojoXx383TI?list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&t=183

 

2.1. 전체 코드

  Widget _buildProfileBody() {
    return Column(
      children: [
        Expanded(
          // StreamBuilder를 통해 firestore에 chat 정보가 입력되면 실시간으로 가져온다
          child: StreamBuilder(
            stream: _firestore
                    .collection('chatting').doc('messages').collection('data')
                    .doc(widget.chattingInfo.cid).collection('chat')
                    .orderBy('time', descending: true).snapshots(), // 첫 로딩 시 가장 마지막 message로 가기 위해 내림차순으로 정렬
            builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return _messageData;
              }

              ChattingMessageHandler handler = ChattingMessageHandler(widget.chattingInfo.cid);
              final chatDocs = snapshot.data!.docs;
              // chat bubble을 만들기 위해서는 async 함수를 호출해야 한다.
              // 이때 StreamBuilder의 builder는 async 함수화가 되지 않는다.
              // 따라서 FutureBuilder를 이용해 async 함수를 호출
              return FutureBuilder(
                future: handler.getChattingMessages(chatDocs),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.done) {
                    _messageData = ListView.builder(
                      controller: _scrollController,
                      reverse: true,  // 첫 로딩 시 가장 마지막 message로 가기 위해 사용
                      itemCount: chatDocs.length,
                      itemBuilder: (context, index) {
                        return makeChattingCommentWidget(snapshot.data![index]);
                      },
                    );
                  }

                  return _messageData;
                },
              );
            },
          ),
        ),
        MessageSendBar(chattingRoomId: widget.chattingInfo.cid, scrollController: _scrollController),
      ],
    );
  }

StreamBuilder의 builder 내부에서 Future값을 리턴받아야 하는데 builder는 async 키워드를 사용할 수 없음으로, FutureBuilder를 통해 Future값을 리턴받게 해주었다.

 

2.2. StreamBuilder의 stream

            stream: _firestore
                    .collection('chatting').doc('messages').collection('data')
                    .doc(widget.chattingInfo.cid).collection('chat')
                    .orderBy('time', descending: true).snapshots(), // 첫 로딩 시 가장 마지막 message로 가기 위해 내림차순으로 정렬

stream에 들어오는 정보를 통해 StreamBuilder는 정보에 변화가 있을 때 마다 정보를 가져온다.

firestore에서는 snapshots을 통해 stream을 제공해주고 있다.

위 코드에서는 /chatting/messages/data/0/chat/ 아래 문서에 추가, 삭제, 변경 등 변화가 있을 경우 stream을 통해 그 변경된 값이 들어온다.

이때 채팅을 시간 순으로 출력해주기 위해 orderBy를 사용해 시간 순으로 정렬해 준다.

 

snapshots의 return type

Stream<QuerySnapshot<Map<String, dynamic>>> Function({bool includeMetadataChanges})

 

2.3. StreamBuilder의 builder

            builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return _messageData;
              }

              ChattingMessageHandler handler = ChattingMessageHandler(widget.chattingInfo.cid);
              final chatDocs = snapshot.data!.docs;

builder 내에서는 내가 원하는 Widget을 만들어서 return해주면 된다.

이때 snapshot을 통해 변경된 데이터가 들어오게 된다. 따라서 snapshot의 state를 보고 새롭게 Widget을 만들어서 return할지, 이전 Widget 또는 로딩 화면을 return할지 판단한다.

snapshot의 데이터를 활용하려면 'snapshot.data!.docs' 구문을 통해 문서를 모두 가져온 후 사용하면 된다.

 

2.4. FutureBuilder의 future

                future: handler.getChattingMessages(chatDocs),

FutureBuilder의 future에는 async 함수를 지정해줄 수 있다.

여기서 설정된 future return 값은 아래 builder 함수의 snapshot에 저장이 된다.

 

2.5. FutureBuilder의 builder

                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.done) {
                    _messageData = ListView.builder(
                      controller: _scrollController,
                      reverse: true,  // 첫 로딩 시 가장 마지막 message로 가기 위해 사용
                      itemCount: chatDocs.length,
                      itemBuilder: (context, index) {
                        return makeChattingCommentWidget(snapshot.data![index]);
                      },
                    );
                  }

                  return _messageData;
                },

FutureBuilder의 builder도 StreamBuilder의 builder와 똑같이 내가 원하는 Widget를 return해주면 된다.

이때 snapshot의 state를 똑같이 확인을 해주어야 한다.