flutterdartstream-builderflutter-streambuilder

StreamBuilder not displaying fetched data from MongoDB database in Flutter


I am trying to implement streambuilder without Firebase, using a MongoDB database. The aim is to build a simple chat app, live streaming the messages. So far, the live streaming when I click on the send button works since I see the message displayed in the UI. I also push that message to my DB successfully. The problem strives when I try to display the messages fetched from my datbase. They are fetched correctly, but not displayed.

final StreamController<ChatMessageModel> _chatMessagesStreamController =
    StreamController<ChatMessageModel>.broadcast();
final Stream<ChatMessageModel> _chatMessagesStream =
    _chatMessagesStreamController.stream;

class MessagesStream extends StatefulWidget {
  var usermail;
  var usermailTo;
  var title;

  MessagesStream(this.usermail, this.usermailTo, this.title);

  @override
  _MessagesStreamState createState() => _MessagesStreamState();
}

class _MessagesStreamState extends State<MessagesStream> {
  final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
  Future<List<dynamic>>? futureMessages;

  Future fetchMessagesFromBack4App(
      String usermail, String usermailTo, String dogName) async {
    final queryBuilder = QueryBuilder(ParseObject('Messages'))
      ..whereEqualTo('sender', usermail)
      ..whereEqualTo('receiver', usermailTo)
      ..whereEqualTo('dogname', dogName)
      ..orderByAscending('date');
    final response = await queryBuilder.query();
    if (response.success && response.results != null) {
      for (var message in response.results!) {
        //check if message was already put into stream
        bool messageFoundInAllMessageLogged = false;
        for (int i = 0; i < _allMessagesContainedInTheStream.length; i++) {
          if (message["sender"] == _allMessagesContainedInTheStream[i].sender &&
              message["receiver"] ==
                  _allMessagesContainedInTheStream[i].receiver &&
              message["date"] == _allMessagesContainedInTheStream[i].date &&
              message["dogname"] ==
                  _allMessagesContainedInTheStream[i].dogname &&
              message["message"] ==
                  _allMessagesContainedInTheStream[i].message) {
            messageFoundInAllMessageLogged = true;
            break;
          }
        }

        // Add message to stream if it was not logged yet
        if (!messageFoundInAllMessageLogged) {
          ChatMessageModel chatMessageModelRecord = ChatMessageModel(
              receiver: message["receiver"],
              message: message["message"],
              sender: message["sender"],
              dogname: message["dogname"],
              date: DateTime.parse(message["date"]));

          _allMessagesContainedInTheStream.add(chatMessageModelRecord);
          debugPrint("putting message to stream: " + message['message']);
        }
      }
 
    } else {
      return [];
    }
  }

  @override
  void initState() {
    fetchMessagesFromBack4App(widget.usermail, widget.usermailTo, widget.title);

    _chatMessagesStream.listen((streamedMessages) {
      // _allMessagesContainedInTheStream.clear();

      debugPrint('Value from controller: $streamedMessages');
      _allMessagesContainedInTheStream.add(streamedMessages);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<ChatMessageModel>(
      stream: _chatMessagesStream,
      builder: (context, snapshot) {
        return Expanded(
          child: ListView.builder(
            // reverse: true,
            padding:
                const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
            itemCount: _allMessagesContainedInTheStream.length,
            itemBuilder: (BuildContext context, int index) {
              if (snapshot.hasData) {
                return UserChatBubble(
                  chatMessageModelRecord:
                      _allMessagesContainedInTheStream[index],
                );
              } else {
                print(snapshot.connectionState);
                return Container();
              }
            },
          ),
        );
      },
    );
  }
}

class UserChatBubble extends StatelessWidget {
  final ChatMessageModel chatMessageModelRecord;

  const UserChatBubble({
    Key? key,
    required this.chatMessageModelRecord,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(
            vertical: 5,
            horizontal: 5,
          ),
          child: Container(
            constraints: BoxConstraints(
              maxWidth: MediaQuery.of(context).size.width * 7 / 10,
            ),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.only(
                bottomLeft: Radius.circular(15.0),
                bottomRight: Radius.circular(15.0),
                topLeft: Radius.circular(15.0),
              ),
              color: primaryColor,
            ),
            padding: const EdgeInsets.symmetric(
              vertical: 8,
              horizontal: 20,
            ),
            child: ListTile(
              title: Text("${chatMessageModelRecord.message}"),
              subtitle: Text(chatMessageModelRecord.date.toString()),
            ),
          ),
        ),
      ],
    );
  }
}

The method fetchMessagesFromBack4App fetches correctly the data and add records to _allMessagesContainedInTheStream. However, when the method ends this _allMessagesContainedInTheStream list is empty (despite of inside the method is adding records). Therefore, snapshot is empty too. Only when I press the send button then I am able to see all the messages: the fetched ones and the sent ones.

Summarizing: snapshot has no data when I navigate to my chat screen. It receives the data only when I press the send button to send a message.


Solution

  • _chatMessagesStream.listen() will listen to the _chatMessagesStream stream and if any event occur, everything in the block will execute.

    your fetchMessagesFromBack4App() does not emit any new event to the above stream, but add value to _allMessagesContainedInTheStream which is a List

    to sum up you need to change _allMessagesContainedInTheStream.add(chatMessageModelRecord); to _chatMessagesStreamController.add(chatMessageModelRecord) add new event to the stream in other for your StreamBuilder to rebuild