Hello everyone, my name is Rohit and welcome
to my channel. In this video, I am gonna show you how to build a complete chat app using react
native expo and Firebase. For the source code, check out the description. If you are new to
this channel then consider subscribing to my channel. Before jumping into the code, check
out the demo of the final output of our project. Here, the user will first enter the
phone number with the country code. Then, verify the phone number with the
OTP code. If the user is new, they have to fill in some details like name,
date of birth, and gender. After saving the information, the user will be navigated to the
dashboard. However, if the user is not new, just after the OTP verification, they will
be directly navigated to the dashboard page. On the dashboard page, the user has to click on
the name they want to chat with. This leads to the chat screen where the user can seamlessly
engage in conversation. After all that, the user can simply click on
the logout button to log out. That’s exactly what we are going to build. So,
buckle up, grab a cup of coffee, and let’s start! —--
First, we are going to initialize an Expo project. After that, select a template and name your
application; in this case, I named it "chatapp." Once the dependencies download completes, move
to the newly created project directory. Here, we will install several JavaScript npm packages
that we will use in this project. I will list the names of the packages in the description, so you
won't miss any. Let's install them one by one. We are using React Navigation in this project, specifically employing stack navigation for
seamless navigation throughout the application We have to add this line at the
top of the App.js file according to the documentation. So, let’s add that. Ok! These are the dependencies and
their versions I am using in this project. Make sure to use the latest package.
However, if the latest packages are broken, you can install the specific versions of the
dependencies that I am using in this project. Now, I am going to show you my project
structure. Make a 'src' folder inside the root directory of the project. First, we will
create a 'utils' folder inside the 'src' folder, which will contain the 'helpers.js' file. Second,
create a 'navigation' folder. Inside this folder, make an 'AppNavigator.js' file. This file will
contain the navigation routes of the application. Lastly, create a folder called 'components' inside
the 'src' folder. Within the 'components' folder, create three subfolders: 'Auth,' 'Chat,'
and 'Dashboard.' The 'Auth' folder will contain 'Login.js' and 'Detail.js' files. The
'Chat' folder will have a 'ChatScreen.js' file, and the 'Dashboard' folder will
have a 'Dashboard.js' file. Next, let's set up our Firebase app. Go to
firebase.google.com and click on "Get Started." Click on "Add a new project" and then name your
project. Here, I am naming it "chatApp." Click on "Continue" and select the default account for
Google Analytics. Then, wait for a few seconds, as Firebase will take around 20-30 seconds
to set up your Firebase application. You can choose either iOS or Android App.
The process would be the same for iOS, but some changes may be required, so follow
the official docs if needed. However, since I am building the app for Android, I will
click on "Android." Here, you need to fill in the package name and SHA-1 key. Currently, we don't
have that information. To obtain these details, we need to build our app. So, open a new tab and
search for "Development Build Expo." Click on the official link and follow the official
docs, or alternatively, follow the video tutorial. Additionally, install two more
packages as mentioned in the documentation. Keep in mind that you have to create your
account on expo.dev. The next step involves logging into your account, so ensure
that you use the same credentials. If you don't have an account, visit
expo.dev and create one first. Afterward, copy and paste the command to create
a build. Leave the default information as it is, then press enter and 'y' to continue. Once the
build has started, monitor the logs online. In case the build fails, follow the logs and rebuild
your app. If the failure is due to a server issue, rebuild your app using the same
command without any hesitation. —-------------
After the build is complete, you will see your terminal screen displaying
something like this. A QR code will appear; however, we are not going to scan it for now.
Next, input the command 'eas credentials' into the terminal window and press enter. Select
'Android' and choose 'development' since we are currently in the development phase, not
production. Opt for 'keystore' and then select 'setup a new keystore.' Press enter, followed
by 'y' twice to generate a new keystore. Press any key to continue. Now, we have two SHA-1 keys,
and we will use both in the Firebase Android app. Firstly, fill in the package name. To do this, go to the app.json file of the project,
scroll down to the android section, and find the package name. Copy and paste it
here. Next, enter your app nickname; for example, I am naming it 'My Chat App.' Copy one SHA-1
key and paste it here. Click on 'register app.' Do not download the google-service.json file at
this point. We need to add another SHA-1 key. Click on 'Settings,' go to the project settings,
scroll down, click on 'Add Fingerprint,' and paste your other SHA-1 key. Click on 'Save.' After
that, click on 'Download google-service.json file' and place it in the root folder of
the project without changing the name. Inside the android section of the app.json file, specify the location of the
google-service.json file. Also, here we need to add two plugins
for Firebase app and Firebase auth. So, add the plugins something like this.
Now, keep in mind that if you make any changes to the app.json file, you have to
rebuild your app. If you don't rebuild your app, the changes won't take effect. Therefore, we
will rebuild our app again with the same command. Ok! Now our build is complete. Now, scan this
QR code using your phone’s camera or any other scanner app if your phone’s camera doesn’t detect
the QR code. Remember that I am using the phone’s camera, not the Expo Go app to scan the QR
code. Scan the QR code and download the build. Now, install this build on your mobile device. Back to your terminal, run this Expo
project using the command npx expo start. Next, scan the QR code using your Expo
Go application. The Expo Go app will automatically connect to the
build stored on your device. Perfect! You can now see that the
app is rendering the default code response. Any changes made to
the code will render instantly. Now, step by step, we are going to write the code
for the chat application. First, let's write the code for the login screen.
—--------- The code starts by importing necessary
modules from React, React Native, Firebase, and React Navigation. These modules provide the
functionalities needed for the login process. For example, useState is a React Hook that lets
you add React state to function components, and auth and firestore are Firebase services
used for authentication and database operations. Inside the Login component, we declare several
state variables using the useState hook. The phoneNumber and code state variables store the
user's phone number and the verification code, respectively. The confirm state
variable holds the confirmation object returned by Firebase's
signInWithPhoneNumber method. The signInWithPhoneNumber function is
an asynchronous function that handles the initial phone number verification
process. It first checks if the provided phone number matches a specific format using a
regular expression. If the format is invalid, it shows an alert to the user. If the format
is valid, it calls the signInWithPhoneNumber method from the Firebase auth library,
passing the phone number as an argument. This method sends a verification
code to the user's phone number, and the returned confirmation object is
stored in the confirm state variable. The confirmCode function is another asynchronous
function that handles the verification code confirmation process. It first checks if the
provided code is valid (6 digits long) and shows an alert if it's not. If the code is valid,
it calls the confirm method on the confirmation object stored in the confirm state variable,
passing the code as an argument. This method verifies the code and returns a user credential
object. The function then checks if the user is new or existing in the Firestore database by
querying the "users" collection with the user's unique ID (uid). If the user already exists, the
function navigates to the "Dashboard" screen. If the user is new, it navigates to the "Detail"
screen and passes the user's uid as a parameter. In the return statement, we define the
user interface for the Login component. It consists of a View with a black background
and a semi-circular bottom section with a light blue background. Inside this section,
we display the app's logo, a text input field for the phone number or verification
code (depending on the state), and a button to initiate the phone number verification or
confirm the code. The layout is styled using inline styles and various properties like flex,
backgroundColor, position, and borderRadius. The code uses conditional rendering based on the
confirm state variable. If confirm is null (no confirmation object yet), it displays the phone
number input field and the "Verify Phone Number" button. If confirm is not null (confirmation
object exists), it displays the verification code input field and the "Confirm Code" button.
This conditional rendering ensures that the user sees the appropriate input field and button
based on the current state of the login process. When the user taps the "Verify Phone Number"
button, the signInWithPhoneNumber function is called, which initiates the phone number
verification process. When the user enters the verification code and taps the "Confirm Code"
button, the confirmCode function is called, which verifies the code and navigates the user
to either the "Dashboard" or "Detail" screen, depending on whether the user is new or existing. The Login component is a crucial part of our
chat app's authentication flow. It provides a user-friendly interface for users to enter
their phone numbers and verify their identities using a code sent to their phones. By
leveraging Firebase Authentication and Firestore, the component ensures secure user
authentication and efficient data management. Here I used only local Image. So, add
a logo.png file to the assets folder. Additionally, I forgot to enable Firebase phone
authentication and Firestore database. So, let's quickly enable both.
—---- Next, we will write code for the Detail Screen.
This component is responsible for capturing and saving additional details about the user, such
as their name, date of birth, and gender. It uses the Firestore service from React Native Firebase
to store the user's information in the database. Similar to the previous component, we import the
necessary modules and libraries. We import React and the useState hook for managing component
state. We also import various components from React Native, such as View, Text, TextInput,
TouchableOpacity, Dimensions, and Picker, to build the user interface. Additionally,
we import the firestore library from React Native Firebase to interact with the Firestore
service and the DatePicker component from the react-native-date-picker library to allow
the user to select their date of birth. Inside the Detail component, we declare three
state variables using the useState hook: name, dob (date of birth), and gender. The name state
variable stores the user's name, dob stores the user's date of birth as a Date object, and
gender stores the user's selected gender. The Detail component receives the user's unique
ID (uid) through the route.params object, which is passed from the previous screen (like the
Login component) when navigating to this screen. The saveDetails function is an asynchronous
function that handles the process of saving the user's details to the Firestore database. It first
creates a new document in the "users" collection, using the user's uid as the document ID.
It then sets the document's data with the values from the component's state variables:
name, dob (converted to an ISO string format), and gender. Additionally, it sets the
"displayName" field to the user's name. After saving the data, the function
navigates to the "Dashboard" screen. In the return statement, we define the
user interface for the Detail component. It follows a similar layout structure to
the Login component, with a black background and a semi-circular bottom section with a
light blue background. Inside this section, we display a heading text and various input fields
for the user's name, date of birth, and gender. For the name input, we use a TextInput component
that updates the name state variable as the user types. For the date of birth, we use the
DatePicker component, which allows the user to select a date from a calendar interface.
The selected date is stored in the dob state variable. For the gender input, we use a Picker
component, which provides a dropdown menu for the user to select their gender. The selected
gender is stored in the gender state variable. Finally, we have a TouchableOpacity component
that, when pressed, calls the saveDetails function. This button allows the user to
save their details to the Firestore database. The Detail component plays a crucial role
in our chat app's user onboarding process. It provides a user-friendly interface for
users to enter their personal information, which is then securely stored in the Firestore
database using the user's unique ID as the document identifier. By leveraging React
Native components and the Firestore service, this component ensures a smooth and efficient
data collection process for new users. —--------: Next, we will write the code for the
Dashboard screen. This component is responsible for displaying a list of users
that the current user can chat with. It also allows the user to log out of the app.
It uses the Firestore and Authentication services from React Native Firebase to
fetch user data and handle authentication. First, We import the necessary
modules and libraries, Inside the Dashboard component, we declare
three state variables using the useState hook: users, userName, and isFocused. The users
state variable stores an array of user objects fetched from Firestore, userName
stores the name of the current user, and isFocused is a boolean value that indicates
whether the screen is currently focused. We use the useEffect hook to perform side
effects when the component mounts and when the screen becomes focused (the isFocused
value changes). Inside the useEffect callback, we define two asynchronous functions:
fetchUsers and fetchUserName. The fetchUsers function is responsible
for fetching a list of users from the Firestore database. It queries the "users"
collection in Firestore and maps the fetched documents to an array of user objects, which
is then stored in the users state variable. The fetchUserName function is responsible
for fetching the name of the current user from Firestore. It first retrieves the
current user's uid (unique identifier) from the Authentication service. If
a current user exists, it queries the "users" collection in Firestore using the
user's uid to fetch the user's document. It then sets the userName state variable to the
value of the "name" field in the user document. The useEffect hook ensures that the fetchUsers
and fetchUserName functions are called only when the screen is focused (isFocused
is true). This optimization helps to avoid unnecessary data fetching
when the screen is not visible. The navigateToChat function is called when
the user taps on a user in the list. It takes the selected user's ID (userId) and name
(userName) as parameters and navigates to the "ChatScreen" component, passing these
parameters along with the navigation. The handleLogout function is an asynchronous
function that handles the user's logout process. It calls the signOut method from
the Firebase Authentication service to log out the current user. After a successful
logout, it navigates to the "Login" screen. In the return statement, we
define the user interface for the Dashboard component. It consists
of a View with a black background and a semi-circular top section with a light
blue background. Inside the top section, we display a heading text ("Home"), the
current user's name, and a "Logout" button. The FlatList component is used to render a list
of users. It takes the users array from the state as its data prop and maps each user object to a
TouchableOpacity component. When a user is tapped, the navigateToChat function is called
with the corresponding user's ID and name. Inside the FlatList's renderItem function,
we use the LinearGradient component from the expo-linear-gradient library to create
a gradient background for each user item. The gradient goes from black to transparent,
giving a sleek and modern look to the user list. The Dashboard component plays a crucial role in
our chat app by providing a centralized view for users to see a list of other users they can chat
with. It fetches user data from the Firestore database and leverages the Authentication service
to retrieve the current user's information. The FlatList component, combined with the
LinearGradient, creates an attractive and user-friendly interface for displaying the list
of users. Additionally, the logout functionality allows users to securely sign out of the app.
—------- The code you see here defines three
utility functions that can be used in various parts of your chat app. These
functions are designed to perform specific tasks related to data formatting, generating
unique identifiers, and sorting messages. The first function, formatTimestamp, is
responsible for converting a Firebase Timestamp object into a human-readable date and
time format. This function takes a timestamp parameter, which is expected to
be a Firebase Timestamp object. Inside the formatTimestamp function, a new Date
object is created by passing the milliseconds value of the input timestamp object to the
Date constructor. This ensures that the date and time are correctly interpreted
based on the user's local time zone. The function then uses the toLocaleDateString
and toLocaleTimeString methods of the Date object to format the date and time,
respectively. The final result is a string that combines the formatted
date and time, separated by a space. The second function, generateKey, is used to
generate a unique key that can be used as an identifier for messages or other items in your
app. This function does not take any parameters. Inside the generateKey function, the
Math.random() method is used to generate a random number between 0 and 1. This number
is then converted to a base-36 string using the toString(36) method. The substr(2,
10) method is used to extract a substring of 10 characters from the resulting string,
starting from the third character (index 2). The resulting string is a unique identifier
consisting of 10 alphanumeric characters. This identifier can be used as a
key for various data structures, such as arrays or objects, to uniquely
identify messages or other items in your app. The third function, sortMessagesByTimestamp,
is used to sort an array of messages based on their timestamp. This function takes an messages
parameter, which is expected to be an array of message objects, each containing a timestamp
property that is a Firebase Timestamp object. Inside the sortMessagesByTimestamp
function, the sort method is used to sort the messages array. The sort function
takes a comparison function as a parameter, which compares two messages
based on their timestamps. The comparison function subtracts the
toMillis value of the second message's timestamp (b.timestamp.toMillis()) from
the toMillis value of the first message's timestamp (a.timestamp.toMillis()). This
comparison determines the order of the messages based on their timestamps, with earlier
timestamps appearing first in the sorted array. The sortMessagesByTimestamp function
returns the sorted array of messages, allowing you to easily display messages in
chronological order based on their timestamps. These utility functions are essential for various
aspects of your chat app's functionality. The formatTimestamp function ensures that timestamps
are displayed in a user-friendly format, the generateKey function generates unique
identifiers for messages or other items, and the sortMessagesByTimestamp function
sorts messages based on their timestamps, enabling you to display
them in chronological order. —--------- The code you see here is another React Native
component called "ChatScreen". This component is responsible for displaying a chat interface
where users can send and receive messages in real-time. It uses the GiftedChat library, which
provides a ready-to-use chat UI component with various customization options. The
component also integrates with React Native Firebase's Firestore service
to store and retrieve chat messages. We import the necessary modules
and libraries hee first. Inside the ChatScreen component, we declare
a state variable called messages using the useState hook. This variable will store an
array of messages that will be displayed in the chat UI. We also destructure
the userId and userName parameters from the route.params object, which
are passed from the previous screen (likely the Dashboard component) when
navigating to this screen. Additionally, we retrieve the current user's information
from the Firebase Authentication service. We use the useEffect hook to set up
a real-time listener on the Firestore database to receive updates for the chat
messages. Inside the useEffect callback, we generate a unique chatId by combining the
current user's uid and the selected user's userId, sorting them alphabetically, and joining them
with an underscore. We then create a reference to the corresponding chat document in
the "chats" collection of Firestore. The onSnapshot function is used
to listen for updates to the chat document in real-time. When the snapshot
changes (e.g., new messages are added), the callback function is executed. If the
snapshot exists, we retrieve the chat data from the snapshot and update the messages state
with the messages array from the chat data. The useEffect hook returns a cleanup function
that unsubscribes from the real-time listener when the component unmounts or the userId or
currentUser.uid changes. This helps prevent memory leaks and ensures that the listener is
properly cleaned up when it's no longer needed. The onSend function is called when the user
sends a new message. It first generates a unique chatId similar to the one used in the
useEffect hook. It then creates a reference to the corresponding chat document in
the "chats" collection of Firestore. Inside the onSend function, we map the newMessages
array to format the createdAt property of each message to a Date object. This is required by the
GiftedChat library to handle timestamps correctly. The onSend function then uses the
GiftedChat.append function to append the newMessages to the existing messages array.
It then updates the chat document in Firestore by setting the messages field to the updated
messages array. The merge: true option is used to ensure that only the messages field is updated,
leaving other fields in the document untouched. The renderBubble function is a custom rendering
function that allows us to customize the appearance of the message bubbles in
the chat UI. It takes a props object as input and destructures the currentMessage
property. The isReceived variable is calculated based on whether the current message's
user._id matches the current user's uid. Inside the renderBubble function, we return a
customized Bubble component from the GiftedChat library. We use the wrapperStyle
prop to set different background colors for received and sent messages.
We also use the containerStyle prop to adjust the positioning of the bubbles based
on whether the message is received or sent. The renderChatFooter function is
a custom rendering function that allows us to add extra space at the bottom
of the chat UI to ensure that the keyboard doesn't cover the input field when
typing long messages. In this case, we return a View component with a height
of 20 units to add some extra space. In the return statement, we define
the user interface for the ChatScreen component. We wrap the GiftedChat
component inside a LinearGradient component to apply a gradient
background to the chat screen. The GiftedChat component is the main chat
UI component from the GiftedChat library. We pass various props to customize its behavior
and appearance. The messages prop is set to the messages state variable, and the onSend
prop is set to the onSend function to handle sending new messages. The user prop is set to
an object containing the current user's uid and displayName. The renderTime prop is used to
customize the display of timestamps for messages. The renderDay prop is set to null to hide the
day separators in the chat UI. The renderBubble prop is set to the custom renderBubble function
to customize the appearance of message bubbles. The renderChatFooter prop is set to the
custom renderChatFooter function to add extra space at the bottom of the chat UI.
The placeholder prop sets the placeholder text for the input field. The textInputStyle
prop sets the text color of the input field to white. The renderUsernameOnMessage prop is
set to true to display the sender's name on each message. The containerStyle prop is used to
adjust the appearance and layout of the chat UI. Finally, we add a conditional check
for the Platform.OS to render a KeyboardAvoidingView component
on Android devices. This helps to prevent the keyboard from covering
the input field when typing messages. The ChatScreen component is a crucial part
of our chat app's functionality. It provides a user-friendly interface for users to exchange
messages in real-time, leveraging the GiftedChat library for the chat UI and React Native
Firebase's Firestore service for storing and retrieving messages. The component also includes
various customizations to enhance the appearance and user experience of the chat interface.
—----------- The code you see here is a React Native
component called "AppNavigator". This component serves as the main navigation system
for the entire chat app. It manages the navigation between different screens and handles user
authentication using Firebase Authentication. First, We import the necessary
modules and libraries. Inside the AppNavigator component, we declare
two state variables using the useState hook: initializing and user. The initializing state
variable is used to track whether the component is in the initialization phase, and the user state
variable stores the current user's information. The onAuthStateChanged function is
defined to handle changes in the user's authentication state. This function is
called whenever the user logs in, logs out, or the authentication state changes. It
updates the user state variable with the result of the onAuthStateChanged callback
from the Firebase Authentication service. If the component is in the initializing phase, it
sets the initializing state variable to false. We use the useEffect hook to set up a listener for changes in the user's authentication
state. Inside the useEffect callback, we call the onAuthStateChanged method
from the Firebase Authentication service, passing the onAuthStateChanged function
as a callback. The useEffect hook returns a cleanup function that unsubscribes from
the listener when the component unmounts. If the initializing state variable is true (i.e., the component is still in the initialization
phase), the component returns null, which means it doesn't render anything
until the initialization is complete. In the return statement, we define the
navigation structure for the app using the Stack.Navigator component from the
@react-navigation/stack library. The initialRouteName prop is used to set the
initial screen that will be shown based on the user's authentication state. If the
user is authenticated (user is truthy), the initial screen will be "Dashboard";
otherwise, it will be "Login". Inside the Stack.Navigator, we
define the screens that will be part of the navigation stack. Each screen
is defined using the Stack.Screen component, which takes various props to configure
the screen's behavior and appearance. The AppNavigator component is the backbone
of our chat app's navigation system. It handles the authentication flow by checking the
user's authentication state and displaying the appropriate screens based on whether the user
is authenticated or not. The Stack.Navigator component provides a straightforward way to
define the navigation structure and manage the transitions between different screens.
By integrating with Firebase Authentication, the AppNavigator component ensures a secure and
seamless user experience throughout the app. —-------------
Then at last just Import the AppNavigator to the App.js file And we are all set. Now, let’s reload
the app and test our application. There is a problem when I entered my
phone number and pressed the button, due to the plus sign I used for the country code, the code shows an invalid number error. Let's
fix that. I will simply remove most of the phone regex elements. You can customize this
according to your application's requirements. That’s it now it’s working perfectly. The UI is clean and looks amazing. The app
experience is very sleek and simple. You can simply click on any name and
chat with them. Congratulations, you just built a chat app using
Firebase and react native expo. —---
That’s it for this video guys, I hope this video was helpful. You can add this project
to your CV and do some flexes. Let me know your opinion in the comments. Thanks for watching.
See you in my next react native expo video.