Day 8: Handling Attachments Like Images and Videos


Today, you’ll enhance the chat app by allowing users to send images and videos as message attachments. This will make the chat experience richer and more interactive.

What You Will Do Today:

  1. Add support for selecting and uploading images and videos.
  2. Use Firebase Storage to store media files.
  3. Display media attachments in the chat interface.

Step 1: Install Required Libraries

  1. Install the react-native-image-picker library to allow users to select media files: npm install react-native-image-picker
  2. Link the library for iOS (optional for React Native 0.60+ with auto-linking):
cd ios
pod install
cd ..

Step 2: Configure Firebase Storage

  1. Go to the Firebase Console.
  2. Navigate to Storage > Get Started and enable Firebase Storage.
  3. Set storage rules to allow authenticated users to upload files:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

Step 3: Update ChatScreen to Support Media Messages

  1. Modify ChatScreen.js to include a button for selecting media files:
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button, FlatList, StyleSheet, Text, TouchableOpacity, Image } from 'react-native';
import firestore from '@react-native-firebase/firestore';
import storage from '@react-native-firebase/storage';
import auth from '@react-native-firebase/auth';
import { launchImageLibrary } from 'react-native-image-picker';

function ChatScreen() {
  const [messages, setMessages] = useState([]);
  const [text, setText] = useState('');
  const [media, setMedia] = useState(null);
  const chatId = 'global_chat';

  // Fetch messages
  useEffect(() => {
    const unsubscribe = firestore()
      .collection('messages')
      .where('chatId', '==', chatId)
      .orderBy('createdAt', 'desc')
      .onSnapshot((querySnapshot) => {
        const fetchedMessages = querySnapshot.docs.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        }));
        setMessages(fetchedMessages);
      });

    return unsubscribe;
  }, []);

  const selectMedia = () => {
    launchImageLibrary(
      {
        mediaType: 'mixed',
        quality: 1,
      },
      (response) => {
        if (response.didCancel) {
          console.log('User cancelled media picker');
        } else if (response.errorMessage) {
          console.error('Error selecting media:', response.errorMessage);
        } else {
          const { uri, fileName, type } = response.assets[0];
          setMedia({ uri, fileName, type });
        }
      }
    );
  };

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

    let mediaUrl = null;

    if (media) {
      // Upload media to Firebase Storage
      const filePath = `chats/${chatId}/${Date.now()}_${media.fileName}`;
      const reference = storage().ref(filePath);

      try {
        await reference.putFile(media.uri);
        mediaUrl = await reference.getDownloadURL();
      } catch (error) {
        console.error('Error uploading media:', error);
        return;
      }
    }

    // Save message to Firestore
    try {
      await firestore().collection('messages').add({
        text,
        userId: uid,
        userName: email,
        chatId,
        mediaUrl,
        mediaType: media?.type || null,
        createdAt: firestore.FieldValue.serverTimestamp(),
        status: 'sent',
      });

      setText('');
      setMedia(null); // Reset media after sending
    } catch (error) {
      console.error('Error sending message:', error);
    }
  };

  const renderMessage = ({ item }) => (
    <View style={styles.messageContainer}>
      <Text style={styles.messageUser}>{item.userName}</Text>
      {item.mediaUrl && (
        <Image
          source={{ uri: item.mediaUrl }}
          style={item.mediaType.startsWith('image') ? styles.image : styles.videoPlaceholder}
        />
      )}
      <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
      />
      <View style={styles.inputContainer}>
        <TouchableOpacity onPress={selectMedia} style={styles.mediaButton}>
          <Text style={styles.mediaButtonText}>+</Text>
        </TouchableOpacity>
        <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',
  },
  image: {
    width: 200,
    height: 200,
    borderRadius: 8,
    marginVertical: 8,
  },
  videoPlaceholder: {
    width: 200,
    height: 200,
    backgroundColor: '#ccc',
    justifyContent: 'center',
    alignItems: 'center',
    marginVertical: 8,
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 10,
    borderTopWidth: 1,
    borderTopColor: '#ddd',
    backgroundColor: '#fff',
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    paddingHorizontal: 10,
    marginRight: 10,
  },
  mediaButton: {
    width: 40,
    height: 40,
    backgroundColor: '#ddd',
    borderRadius: 20,
    justifyContent: 'center',
    alignItems: 'center',
    marginRight: 10,
  },
  mediaButtonText: {
    fontSize: 24,
    fontWeight: 'bold',
  },
});

export default ChatScreen;

Step 4: Test Media Attachments

  1. Run the app:
    • For Android: npx react-native run-android
    • For iOS: npx react-native run-ios.
  2. Send an image or video:
    • Tap the + button to select a media file.
    • Verify that the file uploads and appears in the chat.
  3. Verify Firebase Storage:
    • Go to the Firebase Console under Storage > Files.
    • Confirm that the uploaded media is stored.
See also  Docker Desktop on Windows: A Full Guide

Summary

Today, you added support for image and video attachments to your chat app using Firebase Storage. This feature enhances the app’s interactivity and user engagement.

Tomorrow, you’ll work on typing indicators and presence (online/offline) status.


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.