Skip to main content

Building a Chat Application with React Native and Firebase

· 10 min read
Engineering Team

Build React Native chat on Firebase with secure membership rules, paginated messages, optimistic sends, attachment controls, presence tradeoffs, and push notifications. This revised guide emphasizes supported APIs, production tradeoffs, and an upgrade-friendly path without tying the advice to a calendar year.

Building a Chat Application with React Native and Firebase

Modern implementation baseline

Build React Native chat on Firebase with secure membership rules, paginated messages, optimistic sends, attachment controls, presence tradeoffs, and push notifications.

Use modular Firebase APIs, server-enforced Security Rules, explicit data models, and observable background jobs. Authentication state is not authorization: every database and storage path still needs a least-privilege rule.

  • Model conversations and membership for authorization first.
  • Paginate messages and avoid unbounded listeners.
  • Make sends idempotent and reconcile delivery state.

Production checklist

  • Test rules with the emulator suite and denied requests.
  • Design indexes and pagination before production traffic.
  • Handle offline writes, retries, idempotency, and subscription cleanup.

Authoritative references

This tutorial assumes basic knowledge of JavaScript and React Native. No prior Firebase experience is required. By the end, you’ll have a functional chat app with a modern UI, real-time messaging, and secure authentication, ready for iOS and Android deployment.


Prerequisites

Ensure you have the following installed before starting:

  • Node.js (v20.x or later, LTS recommended): Download from nodejs.org.
  • npm (v10.x or later, included with Node.js).
  • Expo CLI: For streamlined React Native project setup.
  • Android Studio: Use the current stable release with compatible Android SDK and NDK tooling.
  • Xcode: Use the current stable release and SDK required by App Store Connect.
  • Firebase Account: Sign up at firebase.google.com.
  • A code editor like VS Code.
  • Git: For cloning repositories or managing code.

Step-by-Step Guide to Building the Chat App

Step 1: Set Up the React Native Project with Expo

  1. Create a TypeScript Expo project:

    npx create-expo-app@latest ChatApp --template blank-typescript
    cd ChatApp
  2. Install compatible dependencies:

    npx expo install firebase @react-navigation/native @react-navigation/native-stack react-native-gifted-chat expo-constants react-native-safe-area-context react-native-screens react-native-reanimated

    Expo resolves native package versions that match the selected SDK. Review the chat library's New Architecture support before shipping.

  3. Test the project: Run the app to ensure the setup works:

    npx expo start

    Press a for Android or i for iOS (macOS only) to launch the app on an emulator or device. You should see a blank Expo app.


Step 2: Configure Firebase

  1. Create a Firebase Project:

    • Go to the Firebase Console.
    • Click Add Project, name it ChatApp, and follow the prompts to create it.
    • Once created, click the Web icon under Project Overview to register a web app (even for mobile).
  2. Enable Authentication:

    • In the Firebase Console, navigate to Authentication > Sign-in method.
    • Enable Email/Password as the sign-in provider to match features in templates available at Instaflutter.
  3. Set Up Firestore:

    • Navigate to Firestore Database > Create Database.
    • Choose Start in test mode (for development) and select a region (e.g., us-central1).
    • This creates a real-time NoSQL database for storing messages, similar to solutions offered by Dopebase.
  4. Add Firebase Config to the Project:

    • In the Firebase Console, go to Project Overview > Web app > Firebase SDK snippet.
    • Copy the firebaseConfig object.
    • Create a file src/firebase.ts in your project:
      import { initializeApp } from 'firebase/app';
      import { getAuth } from 'firebase/auth';
      import { getFirestore } from 'firebase/firestore';

      const firebaseConfig = {
      apiKey: "YOUR_API_KEY",
      authDomain: "YOUR_AUTH_DOMAIN",
      projectId: "YOUR_PROJECT_ID",
      storageBucket: "YOUR_STORAGE_BUCKET",
      messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
      appId: "YOUR_APP_ID"
      };

      const app = initializeApp(firebaseConfig);
      const auth = getAuth(app);
      const db = getFirestore(app);

      export { auth, db };
      Replace the placeholders with your Firebase project’s config values.
  5. Add Firebase to Android and iOS:

    • Android: Download the google-services.json file from Project Settings > Your apps > Android. Place it in ChatApp/android/app/.
    • iOS: Download the GoogleService-Info.plist file and place it in ChatApp/ios/. Update the iOS project by running:
      cd ios && pod install
    • In android/build.gradle, add:
      dependencies {
      classpath 'com.google.gms:google-services:4.4.2'
      }
    • In android/app/build.gradle, add at the bottom:
      apply plugin: 'com.google.gms.google-services'

Step 3: Set Up Navigation

  1. Configure Navigation: Create a navigation stack for the login, signup, and chat screens. Create src/navigation/AppNavigator.tsx:

    import React from 'react';
    import { NavigationContainer } from '@react-navigation/native';
    import { createNativeStackNavigator } from '@react-navigation/native-stack';
    import LoginScreen from '../screens/LoginScreen';
    import SignupScreen from '../screens/SignupScreen';
    import ChatScreen from '../screens/ChatScreen';

    const Stack = createNativeStackNavigator();

    export default function AppNavigator() {
    return (
    <NavigationContainer>
    <Stack.Navigator initialRouteName="Login">
    <Stack.Screen name="Login" component={LoginScreen} />
    <Stack.Screen name="Signup" component={SignupScreen} />
    <Stack.Screen name="Chat" component={ChatScreen} />
    </Stack.Navigator>
    </NavigationContainer>
    );
    }
  2. Update App Entry Point: Modify App.tsx to use the navigator:

    import React from 'react';
    import AppNavigator from './src/navigation/AppNavigator';

    export default function App() {
    return <AppNavigator />;
    }

Step 4: Implement Authentication Screens

  1. Create Login Screen: Create src/screens/LoginScreen.tsx:

    import React, { useState } from 'react';
    import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
    import { signInWithEmailAndPassword } from 'firebase/auth';
    import { auth } from '../firebase';
    import { useNavigation } from '@react-navigation/native';

    export default function LoginScreen() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const navigation = useNavigation();

    const handleLogin = async () => {
    try {
    await signInWithEmailAndPassword(auth, email, password);
    navigation.navigate('Chat');
    } catch (error) {
    Alert.alert('Login Failed', error.message);
    }
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Login</Text>
    <TextInput
    style={styles.input}
    placeholder="Email"
    value={email}
    onChangeText={setEmail}
    autoCapitalize="none"
    />
    <TextInput
    style={styles.input}
    placeholder="Password"
    value={password}
    onChangeText={setPassword}
    secureTextEntry
    />
    <Button title="Login" onPress={handleLogin} />
    <Button
    title="Go to Signup"
    onPress={() => navigation.navigate('Signup')}
    />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, justifyContent: 'center', padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    input: { borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 },
    });
  2. Create Signup Screen: Create src/screens/SignupScreen.tsx:

    import React, { useState } from 'react';
    import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
    import { createUserWithEmailAndPassword } from 'firebase/auth';
    import { auth } from '../firebase';
    import { useNavigation } from '@react-navigation/native';

    export default function SignupScreen() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const navigation = useNavigation();

    const handleSignup = async () => {
    try {
    await createUserWithEmailAndPassword(auth, email, password);
    navigation.navigate('Chat');
    } catch (error) {
    Alert.alert('Signup Failed', error.message);
    }
    };

    return (
    <View style={styles.container}>
    <Text style={styles.title}>Sign Up</Text>
    <TextInput
    style={styles.input}
    placeholder="Email"
    value={email}
    onChangeText={setEmail}
    autoCapitalize="none"
    />
    <TextInput
    style={styles.input}
    placeholder="Password"
    value={password}
    onChangeText={setPassword}
    secureTextEntry
    />
    <Button title="Sign Up" onPress={handleSignup} />
    <Button
    title="Go to Login"
    onPress={() => navigation.navigate('Login')}
    />
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: { flex: 1, justifyContent: 'center', padding: 20 },
    title: { fontSize: 24, marginBottom: 20, textAlign: 'center' },
    input: { borderWidth: 1, padding: 10, marginBottom: 10, borderRadius: 5 },
    });

Step 5: Build the Chat Screen

  1. Create Chat Screen: Create src/screens/ChatScreen.tsx using react-native-gifted-chat for a polished chat UI and Firebase Firestore for real-time messaging, inspired by solutions at Dopebase:

    import React, { useState, useEffect, useCallback } from 'react';
    import { GiftedChat } from 'react-native-gifted-chat';
    import { collection, addDoc, query, orderBy, onSnapshot } from 'firebase/firestore';
    import { auth, db } from '../firebase';

    export default function ChatScreen() {
    const [messages, setMessages] = useState([]);

    useEffect(() => {
    const q = query(collection(db, 'messages'), orderBy('createdAt', 'desc'));
    const unsubscribe = onSnapshot(q, (snapshot) => {
    const messageList = snapshot.docs.map((doc) => ({
    _id: doc.id,
    text: doc.data().text,
    createdAt: doc.data().createdAt.toDate(),
    user: doc.data().user,
    }));
    setMessages(messageList);
    });

    return () => unsubscribe(); // Cleanup listener on unmount
    }, []);

    const onSend = useCallback(async (newMessages = []) => {
    const message = newMessages[0];
    await addDoc(collection(db, 'messages'), {
    text: message.text,
    createdAt: new Date(),
    user: {
    _id: auth.currentUser.uid,
    name: auth.currentUser.email,
    },
    });
    }, []);

    return (
    <GiftedChat
    messages={messages}
    onSend={(messages) => onSend(messages)}
    user={{
    _id: auth.currentUser.uid,
    name: auth.currentUser.email,
    }}
    />
    );
    }
  2. Explanation of Chat Logic:

    • Firestore Integration: The useEffect hook listens for changes in the messages collection using onSnapshot, updating the UI in real-time, as implemented in templates at Instaflutter.
    • Message Format: Messages are stored in Firestore with text, createdAt, and user fields, formatted for react-native-gifted-chat.
    • Cleanup: The unsubscribe function prevents memory leaks by stopping the Firestore listener when the component unmounts.
    • Sending Messages: The onSend function adds new messages to Firestore, triggering real-time updates across all clients.

Step 6: Style and Enhance the App

  1. Add Tailwind CSS (Optional): For better styling, install react-native-tailwindcss:

    npm install react-native-tailwindcss@1.0.0

    Update LoginScreen.tsx and SignupScreen.tsx styles using Tailwind classes:

    import { t } from 'react-native-tailwindcss';

    const styles = StyleSheet.create({
    container: [t.flex1, t.justifyCenter, t.p5],
    title: [t.text2xl, t.mB5, t.textCenter],
    input: [t.border, t.p2, t.mB2, t.rounded],
    });
  2. Add Logout Functionality: Update ChatScreen.tsx to include a logout button:

    import { View, Button } from 'react-native';
    import { signOut } from 'firebase/auth';
    import { useNavigation } from '@react-navigation/native';

    // Inside ChatScreen
    const navigation = useNavigation();

    const handleLogout = async () => {
    await signOut(auth);
    navigation.navigate('Login');
    };

    // Add to the GiftedChat render
    <View style={{ padding: 10 }}>
    <Button title="Logout" onPress={handleLogout} />
    </View>
  3. Secure Firestore Rules: Update Firestore rules in the Firebase Console to ensure only authenticated users can read/write messages. For production, use stricter rules to validate field types and timestamps:

    rules_version = '2';
    service cloud.firestore {
    match /databases/{database}/documents {
    match /messages/{document=**} {
    allow read: if request.auth != null;
    allow write: if request.auth != null
    && request.resource.data.text is string
    && request.resource.data.createdAt is timestamp
    && request.resource.data.user._id == request.auth.uid;
    }
    }
    }

    Note: Before deploying to production, update these rules to ensure data integrity, such as requiring valid field types and timestamps.


Step 7: Build and Test the App

  1. Run on Android:

    npx expo start

    Press a to launch on an Android emulator or device (API level 21+).

  2. Run on iOS (macOS only): Press i to launch on an iOS simulator, ensuring Xcode 16+ is used for iOS 18 SDK compliance.

  3. Test the App:

    • Sign up with a new email and password.
    • Log in and send messages, which should appear in real-time, similar to features in Dopebase templates.
    • Verify new users in the Firebase Console under Authentication > Users.
    • Check messages in Firestore Database > messages collection.
    • Test logout and navigation between screens.

Step 8: Enhance the App

  • Add Media Sharing: Use Firebase Storage to upload images/videos, integrating with react-native-image-picker, as seen in Instamobile’s video chat app.
  • Improve UI: Customize react-native-gifted-chat props (e.g., renderBubble, renderAvatar) for a unique look.
  • Private Chats: Create sub-collections in Firestore for one-on-one chats, filtering by user IDs.
  • Notifications: Implement Firebase Cloud Messaging (FCM) for push notifications, as included in templates at Instaflutter.

Troubleshooting Tips

  • Firebase Errors: Ensure google-services.json and GoogleService-Info.plist are correctly placed. Check the Firebase Console for correct API keys.
  • Real-Time Not Working: Verify Firestore rules and ensure onSnapshot is set up correctly.
  • Build Issues: Run npx expo-doctor and confirm that Node.js, Expo, React Native, and Reanimated versions are compatible. Run npm install or pod install if dependencies fail.
  • Performance: Use react-native-gifted-chat’s scrollToBottom prop for smooth scrolling with many messages.

Conclusion

You’ve built a real-time chat application using React Native and Firebase with modular APIs, secure rules, pagination, and a maintained chat UI layer. This app supports user authentication, real-time messaging, and cross-platform deployment, making it a solid foundation for further enhancements. For faster development, explore prebuilt solutions like Instamobile’s video chat app template or customizable templates at Dopebase and Instaflutter, which offer production-ready codebases. Dive into Firebase’s documentation for advanced features like Cloud Functions or Storage, and check out React Native’s community resources at reactnative.dev for additional libraries.

Additional Resources