Flutter Firebase Chat App – Part 2: Real-Time Messaging Implementation
Learn how to implement real-time messaging in Flutter using Firebase Firestore. Complete guide with send message logic, StreamBuilder, UI structure, and unread count handling.
Introduction
In Part 1, we designed a scalable Firestore structure for our chat system. Now it’s time to implement real-time messaging in Flutter.
In this article, we will build:
- Chat screen UI
- Real-time message stream
- Send message logic
- Update conversation metadata
- Unread count handling
Step 1: Message Model
class ChatMessage {
final String senderId;
final String text;
final Timestamp createdAt;
final String type;
ChatMessage({
required this.senderId,
required this.text,
required this.createdAt,
required this.type,
});
factory ChatMessage.fromMap(Map data) {
return ChatMessage(
senderId: data['senderId'],
text: data['text'],
createdAt: data['createdAt'],
type: data['type'],
);
}
}
Step 2: Real-Time Message Stream
StreamgetMessages(String conversationId) { return FirebaseFirestore.instance .collection('conversations') .doc(conversationId) .collection('messages') .orderBy('createdAt', descending: false) .snapshots(); }
Step 3: Chat Screen UI
StreamBuilder(
stream: getMessages(conversationId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final messages = snapshot.data!.docs;
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message =
ChatMessage.fromMap(messages[index].data());
return Align(
alignment: message.senderId == currentUserId
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
padding: EdgeInsets.all(10),
margin: EdgeInsets.symmetric(vertical: 5),
color: message.senderId == currentUserId
? Colors.blue
: Colors.grey,
child: Text(message.text),
),
);
},
);
},
);
Step 4: Send Message Logic
FuturesendMessage( String conversationId, String text, ) async { final messageRef = FirebaseFirestore.instance .collection('conversations') .doc(conversationId) .collection('messages') .doc(); await messageRef.set({ "senderId": currentUserId, "text": text, "createdAt": Timestamp.now(), "type": "text", }); await FirebaseFirestore.instance .collection('conversations') .doc(conversationId) .update({ "lastMessage": text, "lastMessageTime": Timestamp.now(), }); }
Step 5: Increment Unread Count
await FirebaseFirestore.instance
.collection('conversations')
.doc(conversationId)
.update({
"unreadCount.receiverUserId":
FieldValue.increment(1),
});
Step 6: Reset Unread Count When Opened
await FirebaseFirestore.instance
.collection('conversations')
.doc(conversationId)
.update({
"unreadCount.currentUserId": 0,
});
Pagination for Older Messages
Query query = FirebaseFirestore.instance
.collection('conversations')
.doc(conversationId)
.collection('messages')
.orderBy('createdAt', descending: true)
.limit(20);
Typing Indicator (Basic Example)
await FirebaseFirestore.instance
.collection('conversations')
.doc(conversationId)
.update({
"typing.currentUserId": true
});
Performance Optimization Tips
- Use pagination for large chats
- Limit stream to 20–50 recent messages
- Avoid rebuilding entire list unnecessarily
- Use proper state management (Bloc, Provider, etc.)
Security Rule Reminder
match /conversations/{conversationId} {
allow read, write: if request.auth != null
&& request.auth.uid in resource.data.participants;
}
Common Mistakes
- Not ordering messages properly
- No pagination
- Updating entire conversation document unnecessarily
- Ignoring unread count logic
Production Flow Summary
User types message
↓
sendMessage()
↓
Message added to subcollection
↓
Conversation metadata updated
↓
StreamBuilder updates UI instantly
Conclusion
We now have a real-time working chat system using Firestore. Messages update instantly across devices.
In Part 3, we will implement: Push Notifications for New Messages (FCM Integration + Cloud Function).
Share
What's Your Reaction?
Like
0
Dislike
0
Love
0
Funny
0
Angry
0
Sad
0
Wow
0