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.
_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