Day 5: Managing Message Data in Firestore


Today, you’ll optimize the management of message data in Firebase Firestore to improve scalability and maintainability. You’ll structure the data for efficient queries and implement features like pagination for the chat app.

What You Will Do Today:

  1. Optimize the Firestore collection structure for messages.
  2. Implement message pagination to load messages in batches.
  3. Update the chat interface to support efficient message loading.

Step 1: Optimize Firestore Collection Structure

  1. Each message document in the messages collection should have the following structure:
    • text: String (the content of the message).
    • userId: String (the sender’s user ID).
    • userName: String (the sender’s name or email).
    • createdAt: Timestamp (when the message was sent).
    • chatId: String (a unique identifier for each chat room, e.g., global_chat for general chat).
  2. In Firestore, create a messages collection and add test documents with the above structure.

Step 2: Update Firestore Queries for Chat Rooms

  1. Modify ChatScreen.js to use a specific chatId:
// screens/ChatScreen.js
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button, FlatList, StyleSheet, Text } from 'react-native';
import firestore from '@react-native-firebase/firestore';
import auth from '@react-native-firebase/auth';

function ChatScreen() {
  const [messages, setMessages] = useState([]);
  const [text, setText] = useState('');
  const [lastVisible, setLastVisible] = useState(null);
  const [loading, setLoading] = useState(false);
  const chatId = 'global_chat'; // Static chat room for demonstration

  // Fetch messages with pagination
  const fetchMessages = async (isInitial = false) => {
    setLoading(true);
    let query = firestore()
      .collection('messages')
      .where('chatId', '==', chatId)
      .orderBy('createdAt', 'desc')
      .limit(10);

    if (!isInitial && lastVisible) {
      query = query.startAfter(lastVisible);
    }

    const snapshot = await query.get();
    const fetchedMessages = snapshot.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));

    if (isInitial) {
      setMessages(fetchedMessages);
    } else {
      setMessages((prevMessages) => [...prevMessages, ...fetchedMessages]);
    }

    if (snapshot.docs.length > 0) {
      setLastVisible(snapshot.docs[snapshot.docs.length - 1]);
    }
    setLoading(false);
  };

  useEffect(() => {
    fetchMessages(true);
  }, []);

  const sendMessage = async () => {
    if (text.trim()) {
      const { uid, email } = auth().currentUser;

      try {
        await firestore().collection('messages').add({
          text,
          userId: uid,
          userName: email,
          chatId,
          createdAt: firestore.FieldValue.serverTimestamp(),
        });
        setText('');
      } catch (error) {
        console.error('Error sending message:', error);
      }
    }
  };

  const renderMessage = ({ item }) => (
    <View style={styles.messageContainer}>
      <Text style={styles.messageUser}>{item.userName}</Text>
      <Text style={styles.messageText}>{item.text}</Text>
      <Text style={styles.messageTimestamp}>
        {item.createdAt?.toDate().toLocaleTimeString()}
      </Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <FlatList
        data={messages}
        renderItem={renderMessage}
        keyExtractor={(item) => item.id}
        inverted
        onEndReached={() => fetchMessages()}
        onEndReachedThreshold={0.5}
        ListFooterComponent={loading ? <Text>Loading...</Text> : null}
      />
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          placeholder="Type a message..."
          value={text}
          onChangeText={setText}
        />
        <Button title="Send" onPress={sendMessage} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  messageContainer: {
    marginVertical: 8,
    padding: 10,
    backgroundColor: '#e1ffc7',
    borderRadius: 8,
    alignSelf: 'flex-start',
  },
  messageUser: {
    fontWeight: 'bold',
    marginBottom: 4,
  },
  messageText: {
    fontSize: 16,
  },
  messageTimestamp: {
    fontSize: 12,
    color: '#888',
    marginTop: 4,
    alignSelf: 'flex-end',
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 10,
    borderTopWidth: 1,
    borderTopColor: '#ddd',
    backgroundColor: '#fff',
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    paddingHorizontal: 10,
    marginRight: 10,
  },
});

export default ChatScreen;

Step 3: Implement Pagination

  1. Modify the fetchMessages function to fetch the next batch when the user scrolls to the end.
  2. Use the onEndReached and onEndReachedThreshold props in FlatList to detect when the user scrolls near the bottom of the list.
See also  How to troubleshoot and restart WSL after running wsl --shutdown from Windows

Step 4: Test the Pagination and Chat Functionality

  1. Run the app:
    • For Android: npx react-native run-android
    • For iOS: npx react-native run-ios
  2. Test message loading:
    • Scroll up to load more messages.
    • Verify that messages load in batches.
  3. Verify Firestore structure:
    • Go to the Firebase Console under Firestore Database.
    • Confirm that messages are stored with the correct chatId, createdAt, and other fields.

Summary

Today, you optimized message management by introducing chat rooms and implementing pagination to load messages in batches. This approach ensures scalability and efficient data querying for larger chat systems.

Tomorrow, you’ll work on sending notifications when a new message is received.


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.