Build a Chat App with Firebase and React Native
Tags: reactnative, firebase, javascript
Published: 2019-04-26 16:34:35

---
title: Build a Chat App with Firebase and React Native
published: true
description: In this tutorial. let's build a mobile chat application using React Native, Expo and Firebase - a great way to evolve your JavaScript skills.
tags: reactnative, firebase, javascript
canonical_url: https://blog.jscrambler.com/build-a-chat-app-with-firebase-and-react-native/
cover_image: https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-build-a-chat-app-with-firebase-and-react.jpg
---

**Last updated on: November 26th, 2021**

In this tutorial, you are going to build a chat application using React Native, Expo, and Firebase as the backend service. The application will contain a simple login system using an email address for each specific user. The user will be allowed to upload a profile picture. The chat application will be more of a global chat room but works in real-time.

You can find the complete source code for this tutorial at this [GitHub Repository](https://github.com/amandeepmittal/react-native-examples/tree/master/RNFirebaseChat).

## Installing Dependencies

To get started, you must have Expo CLI installed on your local machine. Run the following commands from your terminal in order to install the CLI and generate a new project using it.

```shell
# To install expo-cli
npm install -g expo-cli

# To generate new project
expo init RNfirebase-chat

# Choose blank template when asked

# traverse inside the project directory
cd RNfirebase-chat
```

Once the project is generated, you can run it in an iOS simulator or an Android emulator to verify that everything works. Android developers should make sure that an Android Virtual Device is running before executing the command below.

```shell
# for iOS simalulor
yarn ios

# for Android device/emulator
yarn android
```

Next, install a dependency called [`react-native-gifted-chat`](https://github.com/FaridSafi/react-native-gifted-chat) that provides a customizable UI for a chat application. For navigating between different screens, we are going to use [`react-navigation`](https://reactnavigation.org/) and lastly, to connect with the Firebase project, we need Firebase SDK.

```shell
npm install @react-navigation/native @react-navigation/stack react-native-gifted-chat

# OR is using yarn 

yarn add @react-navigation/native @react-navigation/stack react-native-gifted-chat

# after the above dependencies install successfully 

expo install firebase expo-constants dotenv react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
```

In order to build the application, we are going to need:

- A user authentication service
- A service to store the user's email
- A service to store messages

All of these services are going to be leveraged from Firebase. When building an authentication flow, we will not go into covering the depths of implementing Firebase Auth with Expo. We have already covered that in-depth in a separate tutorial [here](https://blog.jscrambler.com/how-to-integrate-firebase-authentication-with-an-expo-app/?utm_source=dev.to&utm_medium=referral&utm_campaign=chat-app-react-native).

## Setting up Firebase

Firebase is an application development tool by Google that provides an SDK with services like email and social media authentication, real-time database, machine learning kit, APIs, and so on. Firebase can be integrated with a cloud service, Google Cloud Platform.

In the application, we are going to use email authentication and cloud storage. To set up a Firebase free tier project, visit [the Firebase Console](https://console.firebase.google.com/) and create a new project, enter a name and then click the button **Add Project** button.

<center><img src="https://blog.jscrambler.com/content/images/2021/07/jscrambler-blog-firebase-authentication-with-expo-2.png" alt="setting up firebase"></center>

Next, add the name of the new Firebase project and then click **Continue**. When asked for the Google Analytics setup, you can disable it as it won't be used in this example. Then click **Create Project**.

Once the Firebase project is created, you will be welcomed by the home screen like below. 

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase.png" alt="create firebase project"></center>


Take a look at the side menu bar on the left. This is the main navigation in any Firebase project. First, we need to enable authentication. Click on the **Authentication** tab under the **Build** section, then click on the **Sign-in method**. Enable authentication using **Email/Password** and then hit the **Save** button.

<center><img src="https://blog.jscrambler.com/content/images/2021/07/jscrambler-blog-firebase-authentication-with-expo-8.png" alt="enable authentication"></center>

On the Dashboard screen, in the left side menu, click the settings icon, and then go to the _Project Settings_ page and then look for the section _General > Your apps_. If it's a new project, there won't be any apps.

<center><img src="https://blog.jscrambler.com/content/images/2021/07/jscrambler-blog-firebase-authentication-with-expo-5.png" alt="project settings"></center>

Click the Web button. It will prompt you to enter the details of your app. Enter the app’s nickname, and then click the _Register app_ button.

<center><img src="https://blog.jscrambler.com/content/images/2021/07/jscrambler-blog-firebase-authentication-with-expo-6.png" alt="register app in firebase"></center>

Then, Firebase will provide configuration objects with API keys and other keys that are required to use different Firebase services.

<center><img src="https://blog.jscrambler.com/content/images/2021/07/jscrambler-blog-firebase-authentication-with-expo-7.png" alt="API for firebase"></center>

These API keys can be included in your React Native app as they are not used to access Firebase services’ backend resources. That can only be done by Firebase security rules.
This does not mean that you should expose these keys to a version control host such as GitHub.
In the post [How to integrate Firebase Authentication with an Expo app](https://blog.jscrambler.com/how-to-integrate-firebase-authentication-with-an-expo-app/?utm_source=dev.to&utm_medium=referral&utm_campaign=chat-app-react-native) we discussed how to setup environment variables in `.env` and use them using the `expo-constants` package. We will follow the same methodology here.

Create a `.env` file at the root of your React Native project add the following. Replace the _X’s_ with your actual keys from Firebase.
```shell
API_KEY=XXXX
AUTH_DOMAIN=XXXX
PROJECT_ID=XXXX
STORAGE_BUCKET=XXXX
MESSAGING_SENDER_ID=XXXX
APP_ID=XXX
```
Next, rename the `app.json` file to `app.config.js` at the root of your project. Add the import statement to read the environment variables using the `dotenv` configuration. Since it's a JavaScript file, you will have to export all Expo configuration variables and also add an `extra` object that contains Firebase configuration keys. Here is how the file should look like after this step:

```js
import 'dotenv/config';

export default {
  expo: {
    name: 'expo-firebase-auth-example',
    slug: 'expo-firebase-auth-example',
    version: '1.0.0',
    orientation: 'portrait',
    icon: './assets/icon.png',
    splash: {
      image: './assets/splash.png',
      resizeMode: 'contain',
      backgroundColor: '#ffffff'
    },
    updates: {
      fallbackToCacheTimeout: 0
    },
    assetBundlePatterns: ['**/*'],
    ios: {
      supportsTablet: true
    },
    android: {
      adaptiveIcon: {
        foregroundImage: './assets/adaptive-icon.png',
        backgroundColor: '#FFFFFF'
      }
    },
    web: {
      favicon: './assets/favicon.png'
    },
    extra: {
      apiKey: process.env.API_KEY,
      authDomain: process.env.AUTH_DOMAIN,
      projectId: process.env.PROJECT_ID,
      storageBucket: process.env.STORAGE_BUCKET,
      messagingSenderId: process.env.MESSAGING_SENDER_ID,
      appId: process.env.APP_ID
    }
  }
};
```
Now, all the keys inside the `extra` object are readable app-wide using `expo-constants`. This package allows reading values from `app.json` - or in this case, the `app.config.js` file.

Inside your React Native project, create a new directory at the root called `config/` and add a file called `firebase.js`. Edit the file as shown below:

```js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import Constants from 'expo-constants';

// Firebase config
const firebaseConfig = {
  apiKey: Constants.manifest.extra.apiKey,
  authDomain: Constants.manifest.extra.authDomain,
  projectId: Constants.manifest.extra.projectId,
  storageBucket: Constants.manifest.extra.storageBucket,
  messagingSenderId: Constants.manifest.extra.messagingSenderId,
  appId: Constants.manifest.extra.appId,
  databaseURL: Constants.manifest.extra.databaseURL
};

// initialize firebase
initializeApp(firebaseConfig);

export const auth = getAuth();
export const database = getFirestore();

```



## Setup Firestore Database

The next step is to enable the Database rules. Visit the second tab called **Firestore Database** from the sidebar menu.

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-cloud.png" alt="firestore database"></center>

Click **Create Database**. When asked for security rules, select **test mode** for this example. You can learn more about Security Rules with Firebase [here](https://firebase.google.com/docs/database/security) and later on, update your rules accordingly.

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-cloud-2.png" alt="firebase database settings"></center>

Next, let the location be the default and click **Enable**. 

That's it for the setup part. In the next section, let’s start building the application.

## Chat Screen

The `react-native-gifted-chat` component allows us to display chat messages that are going to be sent by different users. To get started, create a new directory called `screens`. This is where we are going to store all of the screen components. Inside this directory, create a new file, `Chat.js` with the following code snippet.

```js
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'

export default function Chat() {
  return (
    <GiftedChat />
  )
}

```

Now open the `App.js` file and add logic to create a navigational component using the `react-navigation` module. This file will contain a `RootNavigator`, a `ChatStack` navigator that contains only one screen, and later we will add an `AuthStack` navigator with business logic to handle authenticated users to only view chat screen.

```js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import Chat from './screens/Chat';

const Stack = createStackNavigator();

function ChatStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name='Chat' component={Chat} />
    </Stack.Navigator>
  );
}

function RootNavigator() {
  return (
    <NavigationContainer>
      <ChatStack />
    </NavigationContainer>
  );
}

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

Now if you run the simulator device, you will notice that there is a bare minimum chat screen that has a plain white header, background, and at the bottom of the screen, an input area where the user can enter the message. When typing something, a **Send** button automatically appears.

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-screen.png" alt="send button"></center>

However, this **Send** button does not have any functionality right now.

<a href="https://blog.jscrambler.com/how-to-protect-react-native-apps-with-jscrambler/?utm_source=dev.to&utm_medium=referral&utm_campaign=chat-app-react-native" target="_blank" class="protect-react-native-ad"><img alt="Protect your Code with Jscrambler" src="https://media.jscrambler.com/blog/V4-React%20Native.jpg"></a>

## Adding a Login Screen

Create a screen component called `Login.js` inside the `screens/` directory. This component file will contain the structure of components on the Login screen.

The screen itself contains two input fields for the app user to enter their credentials and a button to Login into the app. Another button is provided to navigate to the sign-up screen in case the user has not registered with the app. All of these components are created using React Native.

Start by importing the necessary components from React Native core and `auth` object from `config/firebase.js` file.

The `onHandleLogin` method is going to authenticate a user's credentials using `signInWithEmailAndPassword()` method from Firebase Auth. If the credentials are accurate, the user will navigate to the Chat screen. If not, there will be some error shown in your terminal window. You can add your own business logic to handle these errors.

Here is the complete code snippet for the `Login.js` file:

```js
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../config/firebase';

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

  const onHandleLogin = () => {
    if (email !== '' && password !== '') {
     signInWithEmailAndPassword(auth, email, password)
        .then(() => console.log('Login success'))
        .catch(err => console.log(`Login err: ${err}`));
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome back!</Text>
      <TextInput
        style={styles.input}
        placeholder='Enter email'
        autoCapitalize='none'
        keyboardType='email-address'
        textContentType='emailAddress'
        autoFocus={true}
        value={email}
        onChangeText={text => setEmail(text)}
      />
      <TextInput
        style={styles.input}
        placeholder='Enter password'
        autoCapitalize='none'
        autoCorrect={false}
        secureTextEntry={true}
        textContentType='password'
        value={password}
        onChangeText={text => setPassword(text)}
      />
      <Button onPress={onHandleLogin} color='#f57c00' title='Login' />
      <Button
        onPress={() => navigation.navigate('Signup')}
        title='Go to Signup'
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingTop: 50,
    paddingHorizontal: 12
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
    color: '#444',
    alignSelf: 'center',
    paddingBottom: 24
  },
  input: {
    backgroundColor: '#fff',
    marginBottom: 20,
    fontSize: 16,
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 8,
    padding: 12
  }
});
```

Here is how the screen will look like:

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-screen-login.png" alt="login screen"></center>

## Creating the Signup Screen

The Signup screen is similar to the Login one. It has exactly the same input fields and button with only one exception. The handler method defined in this file called `onHandleSignup` uses `createUserWithEmailAndPassword()` method from Firebase to create a new user account.

Create a new file inside the `screens` directory and let's name it `Signup.js`. Add the following code snippet:

```js
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../config/firebase';

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

  const onHandleSignup = () => {
    if (email !== '' && password !== '') {
  createUserWithEmailAndPassword(auth, email, password)
        .then(() => console.log('Signup success'))
        .catch(err => console.log(`Login err: ${err}`));
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Create new account</Text>
      <TextInput
        style={styles.input}
        placeholder='Enter email'
        autoCapitalize='none'
        keyboardType='email-address'
        textContentType='emailAddress'
        value={email}
        onChangeText={text => setEmail(text)}
      />
      <TextInput
        style={styles.input}
        placeholder='Enter password'
        autoCapitalize='none'
        autoCorrect={false}
        secureTextEntry={true}
        textContentType='password'
        value={password}
        onChangeText={text => setPassword(text)}
      />
      <Button onPress={onHandleSignup} color='#f57c00' title='Signup' />
      <Button
        onPress={() => navigation.navigate('Login')}
        title='Go to Login'
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingTop: 50,
    paddingHorizontal: 12
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
    color: '#444',
    alignSelf: 'center',
    paddingBottom: 24
  },
  input: {
    backgroundColor: '#fff',
    marginBottom: 20,
    fontSize: 16,
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 8,
    padding: 12
  }
});
```

Here is how the screen will look like:

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-screen-signup.png" alt="signup screen"></center>

## Adding authenticated user provider

In Reactjs, the [Context API](https://reactjs.org/docs/context.html#reactcreatecontext) is designed to share data that is considered global for a tree of React components. When you are creating a context there is a requirement to pass a default value. This value is used when a component does not have a matching Provider.

The Provider allows the React components to subscribe to the context changes. These context changes can help us determine a user's logged in state in the chat app.

In this section, we will modify the `App.js` file to two stack navigators for Chat and Auth related screens. Let's start by adding the import statements and then defining a `ChatStack` and an `AuthStack` navigator functions.

```js
import React, { useState, createContext, useContext, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { View, ActivityIndicator } from 'react-native';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from './config/firebase';

import Login from './screens/Login';
import Signup from './screens/Signup';
import Chat from './screens/Chat';

const Stack = createStackNavigator();

function ChatStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name='Chat' component={Chat} />
    </Stack.Navigator>
  );
}

function AuthStack() {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen name='Login' component={Login} />
      <Stack.Screen name='Signup' component={Signup} />
    </Stack.Navigator>
  );
}
```

To create an auth provider, export a function called `AuthenticatedUserProvider`. This provider is going to allow the screen components to access the current user in the application. Define a state variable called user.

Add the following code snippet:

```js
const AuthenticatedUserContext = createContext({});

const AuthenticatedUserProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  return (
    <AuthenticatedUserContext.Provider value={{ user, setUser }}>
      {children}
    </AuthenticatedUserContext.Provider>
  );
};
```

Next, modify the `RootNavigator` function. Inside this function, we will use the Firebase method `onAuthStateChanged()` that is going to handle the user's logged-in state changes. Using the `useEffect` hook, you can subscribe to this state change function and make sure you unsubscribe it when the component unmounts. This method allows you to subscribe to real-time events when the user performs an action. The action here can be, logging in, signing out, and so on.


```js
function RootNavigator() {
  const { user, setUser } = useContext(AuthenticatedUserContext);
  const [isLoading, setIsLoading] = useState(true);

   useEffect(() => {
    // onAuthStateChanged returns an unsubscriber
    const unsubscribeAuth = onAuthStateChanged(
      auth,
      async authenticatedUser => {
        authenticatedUser ? setUser(authenticatedUser) : setUser(null);
        setIsLoading(false);
      }
    );

    // unsubscribe auth listener on unmount
    return unsubscribeAuth;
  }, [user]);


  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size='large' />
      </View>
    );
  }

  return (
    <NavigationContainer>
      {user ? <ChatStack /> : <AuthStack />}
    </NavigationContainer>
  );
}
```

Lastly, wrap the `RootNavigator` with `AuthenticatedUserProvider` inside `App` function:

```js
export default function App() {
  return (
    <AuthenticatedUserProvider>
      <RootNavigator />
    </AuthenticatedUserProvider>
  );
}
```

Firebase authentication is implemented in our app:

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-authentication.gif" alt="firebase authentication implemented"></center>


## Adding Chat Functionality

As the authentication in our chat application is now working, we can move ahead and add the chat functionality itself. This component is going to need the user information from Firebase in order to create a chat message and send it.

Start by importing the necessary component from the React Native Gifted Chat library, and `auth` and `database` object from the firebase config file and other methods from `firebase/firestore` to fetch and add data to the collection.

```js
import React, {
  useState,
  useEffect,
  useLayoutEffect,
  useCallback
} from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { GiftedChat } from 'react-native-gifted-chat';
import {
  collection,
  addDoc,
  orderBy,
  query,
  onSnapshot
} from 'firebase/firestore';
import { signOut } from 'firebase/auth';

import { auth, database } from '../config/firebase';
```

Inside the `Chat` function, create a `messages` state and a function to handle logout action using `useLayoutEffect` as well as the business logic to log out a user inside the `onSignOut` handler method.

```js
export default function Chat({ navigation }) {
  const [messages, setMessages] = useState([]);

const onSignOut = () => {
    signOut(auth).catch(error => console.log('Error logging out: ', error));
  };

  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <TouchableOpacity
          style={{
            marginRight: 10
          }}
          onPress={onSignOut}
        >
          <Text>Logout</Text>
        </TouchableOpacity>
      )
    });
  }, [navigation]);
```

To retrieve old messages from the Firestore database, an API call has to be made to the database collection. We will set the collection name to `chats` and use the `useLayoutEffect` hook to make this database call.

To send a message, we will create a custom handler method called `onSend`. This method will use the `useCallback` hook and will store the messages in the Firestore collection called `chats`. It uses the `addDoc` method from Firestore to create a new document with an auto-generated id when a new message is sent.

```js
useLayoutEffect(() => {
    const collectionRef = collection(database, 'chats');
    const q = query(collectionRef, orderBy('createdAt', 'desc'));

    const unsubscribe = onSnapshot(q, querySnapshot => {
      setMessages(
        querySnapshot.docs.map(doc => ({
          _id: doc.data()._id,
          createdAt: doc.data().createdAt.toDate(),
          text: doc.data().text,
          user: doc.data().user
        }))
      );
    });

return unsubscribe;
  });


const onSend = useCallback((messages = []) => {
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messages)
    );
    const { _id, createdAt, text, user } = messages[0];    
    addDoc(collection(database, 'chats'), {
      _id,
      createdAt,
      text,
      user
    });
  }, []);

```

Lastly, we will use the `GiftedChat` component and its different props. The first prop is `messages` to display messages. The next prop `showAvatarForEveryMessage` is set to true. We will set a random `avatar` for each user who logs in and sends a message for this example. You can replace it with your own logic to add a better avatar-generating solution.

The `onSend` prop is responsible for sending messages. The `user` object is to identify which user is sending the message.

```js
return (
  <GiftedChat
    messages={messages}
    showAvatarForEveryMessage={true}
    onSend={messages => onSend(messages)}
    user={{
      _id: auth?.currentUser?.email,
      avatar: 'https://i.pravatar.cc/300'
    }}
  />
);
```

Here is the output after this step:

<center><img src="https://blog.jscrambler.com/content/images/2021/11/jscrambler-blog-firebase-chat.gif" alt="chat functionality"></center>

## Conclusion

Firebase is a great service in terms of time savings and faster app development. Integrating it with specific use cases (_such as demonstrated in this tutorial_) without building a complete backend from scratch is an advantage for any React Native developer.

**Lastly, if you're building React Native applications with sensitive logic, be sure to protect them against code theft and reverse-engineering with <a href="https://jscrambler.com/code-integrity?utm_source=dev.to&utm_medium=referral&utm_campaign=chat-app-react-native" target="_blank">Jscrambler</a>**.

Originally published on the [Jscrambler Blog](https://blog.jscrambler.com/build-a-chat-app-with-firebase-and-react-native/?utm_source=dev.to&utm_medium=referral&utm_campaign=chat-app-react-native) by Aman Mittal.