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:
- Add support for selecting and uploading images and videos.
- Use Firebase Storage to store media files.
- Display media attachments in the chat interface.
Step 1: Install Required Libraries
- Install the react-native-image-picker library to allow users to select media files:
npm install react-native-image-picker
- Link the library for iOS (optional for React Native 0.60+ with auto-linking):
cd ios
pod install
cd ..
Step 2: Configure Firebase Storage
- Go to the Firebase Console.
- Navigate to Storage > Get Started and enable Firebase Storage.
- 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
- 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
- Run the app:
- For Android:
npx react-native run-android
- For iOS:
npx react-native run-ios
.
- For Android:
- Send an image or video:
- Tap the
+
button to select a media file. - Verify that the file uploads and appears in the chat.
- Tap the
- Verify Firebase Storage:
- Go to the Firebase Console under Storage > Files.
- Confirm that the uploaded media is stored.
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.