Build a Real-Time Chat App with React Native Expo & Firebase - Complete Tutorial With Source Code

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Bug Ninza
Views: 2,994
Rating: undefined out of 5
Keywords: react native, react native tutorial, react native chat app, react native live coding, react native ui design, react native auth, react native expo, react native ui, build a whatsapp clone, react expo, whatsapp clone react native, react native aws amplify auth, signal clone react native, react tutorial, react native for beginners, build a signal clone, react native chat app with firebase, react native app, react native chat ui, build react native app
Id: _f5csLux0qA
Channel Id: undefined
Length: 79min 58sec (4798 seconds)
Published: Wed Feb 21 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.