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

Stream getMessages(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

Future sendMessage(
  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 Like 0
Dislike Dislike 0
Love Love 0
Funny Funny 0
Angry Angry 0
Sad Sad 0
Wow Wow 0