What you’ll be building. See live and Git Repo . demo Here Introduction Let’s be real, if you haven’t built a chat app yet, you are still a little bit behind the cutting edge of software development. You need to upskill your app development to the next level. Luckily, cross-platform frameworks such as can get you building a modern chat app in no time like the one seen above. React Native In this tutorial, you will learn how to use; React Native, , and Firebase to build a chat app with a stunning UI. CometChat one-on-one If you are ready, let’s get started… Prerequisite To understand this tutorial, you should already be familiar with React Native. The rest of the stack is simple to grasp. The packages used to create this application are listed below. React Native Firebase CometChat Expo NodeJs Installing The Project Dependencies First, download and install on your machine. If you don’t have it installed already, go to their and follow the installation process. The must then be installed on your computer using the command below. You can get to their doc page by clicking on this . NodeJs website Expo-CLI LINK # Install Expo-CLI npm install --global expo-cli After that, open the terminal and create a new expo project called , selecting the blank template when prompted. Use the example below to demonstrate this. privex #Create a new expo project and navigate to the directory expo init privex cd privex #Start the newly created expo project expo start Running the above commands on the terminal will create a new react-native project and start it up on the browser. Now you will have the option of launching the IOS, Android, or the Web interface by simply selecting the one that you want. To spin up the development server on or you will need a simulator for that, use the instruction found here to use an IOS or Android simulator, otherwise, use the web interface and follow up the tutorial. IOS Android Great, now follow the instructions below to install these critical dependencies for our project. is the expo's default package manager; see the codes below. Yarn # Install the native react navigation libraries yarn add @react-navigation/native yarn add @react-navigation/native-stack #Installing dependencies into an Expo managed project expo install react-native-screens react-native-safe-area-context react-native-gesture-handler # Install an Icon pack and a state manager yarn add react-native-vector-icons react-hooks-global-state Nice, now let’s set up for this project. Firebase Setting Up Firebase Run the command below to properly install firebase in the project. #Install firebase with the command expo install firebase Let's get started by configuring the Firebase console for this project, including the services we'll be using. If you do not already have a Firebase account, create one for yourself. After that, go to Firebase and create a new project called , then activate the Google authentication service, as detailed below. privex Firebase supports authentication via a variety of providers. For example, social authentication, phone numbers, and the traditional email and password method. Because we'll be using the Google authentication in this tutorial, we'll need to enable it for the project we created in Firebase, as it's disabled by default. Click the sign-in method under the authentication tab for your project, and you should see a list of providers currently supported by Firebase. Super, that will be all for the firebase authentication, let's generate the Firebase SDK configuration keys. You need to go and register your application under your Firebase project. On the project’s overview page, select the add app option and pick as the platform. web Return to the project overview page after completing the SDK config registration, as shown in the image below. Now you click on the project settings to copy your SDK configuration setups. The configuration keys shown in the image above must be copied to a separate file that will be used later in this project. Create a file called in the root of this project and paste the following codes into it before saving. firebase.js import { initializeApp, getApps } from 'firebase/app' import { getAuth, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile, signOut, } from 'firebase/auth' import { getFirestore, collection, addDoc, setDoc, getDoc, getDocs, doc, onSnapshot, serverTimestamp, query, orderBy, collectionGroup, arrayUnion, arrayRemove, updateDoc, } from 'firebase/firestore' const firebaseConfig = { apiKey: 'xxx-xxx-xxx-xxx-xxx', authDomain: 'xxx-xxx-xxx-xxx-xxx-xxx-xxx', projectId: 'xxx-xxx-xxx-xxx-xxx', storageBucket: 'xxx-xxx-xxx-xxx-xxx-xxx-xxx', messagingSenderId: 'xxx-xxx-xxx', appId: 'xxx-xxx-xxx-xxx-xxx-xxx-xxx-xxx-xxx-xxx', } if (!getApps().length) initializeApp(firebaseConfig) export { getAuth, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile, signOut, collection, collectionGroup, addDoc, getFirestore, onSnapshot, serverTimestamp, query, orderBy, getDoc, getDocs, setDoc, doc, arrayUnion, arrayRemove, updateDoc, } You are awesome if you followed everything correctly. We'll do something similar for next. CometChat Setting CometChat Head to and signup if you don’t have an account with them. Next, log in and you will be presented with the screen below. CometChat To create a new app, click the . You will be presented with a modal where you can enter the app details. The image below shows an example. Add New App button Following the creation of your app, you will be directed to your dashboard, which should look something like this. You must also copy those keys to a separate file in the manner described below. Simply create a file called in the project's root directory and paste the code below into it. Now include this file in the file, which is also located at the root of this project; this will ensure that it is not published online. CONSTANTS.js .gitIgnore export const CONSTANTS = { APP_ID: 'xxx-xxx-xxx', REGION: 'us', Auth_Key: 'xxx-xxx-xxx-xxx-xxx-xxx-xxx-xxx', } Finally, delete the preloaded users, and groups as shown in the images below. Awesome, that will be enough for the setups, let’s start integrating them all into our application, we will start with the components. The Components Directory This project contains several directories; let's begin with the components folder. Within the root of this project, create a folder called . Let's begin with the component. components Header The Header Component This is a styled component supporting the beauty of our app. Within it is the avatar element which displays the current user's profile picture. Also with this avatar, you can log out from the application. Create this file by going into the components directory and creating a file named , afterward, paste the code below into it. HomeHeader.js import { StyleSheet, TouchableOpacity, View } from 'react-native' import Icon from 'react-native-vector-icons/FontAwesome5' import { Avatar, Text } from 'react-native-elements' import { getAuth, signOut } from '../firebase' import { CometChat } from '@cometchat-pro/react-native-chat' import { setGlobalState } from '../store' const HomeHeader = () => { const PLACEHOLDER_AVATAR = 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png' const auth = getAuth() const signOutUser = async () => { try { await signOut(auth).then(() => { CometChat.logout() .then(() => { console.log('Logout completed successfully') setGlobalState('currentUser', null) setGlobalState('isLoggedIn', false) }) .catch((error) => console.log('Logout failed with exception:', { error }) ) }) } catch (error) { console.log(error) } } return ( <View style={{ paddingVertical: 15, paddingHorizontal: 30 }}> <View style={styles.container}> <TouchableOpacity onPress={signOutUser} activeOpacity={0.5}> <Avatar rounded source={{ uri: auth?.currentUser?.photoURL || PLACEHOLDER_AVATAR, }} /> </TouchableOpacity> <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }} > <TouchableOpacity style={{ marginRight: 15 }} activeOpacity={0.5}> <Icon name="search" size={18} color="white" /> </TouchableOpacity> </View> </View> <Text h4 style={{ color: 'white', marginTop: 15 }}> Messages </Text> </View> ) } export default HomeHeader const styles = StyleSheet.create({ container: { justifyContent: 'space-between', alignItems: 'center', flexDirection: 'row', }, }) The ChatContainer Component This component is responsible for showcasing the recent conversations of a user. It does more than that, it can also display users' stories and a floating button that launches a modal for showing all the users registered in our app. We will look at each of them separately, shown below is the code for it. import { CometChat } from '@cometchat-pro/react-native-chat' import { useEffect, useState } from 'react' import { ScrollView, StyleSheet, useWindowDimensions, View, TouchableOpacity, } from 'react-native' import { Avatar, Text } from 'react-native-elements' import { useGlobalState } from '../store' import { getAuth } from '../firebase' const auth = getAuth() const timeAgo = (date) => { let seconds = Math.floor((new Date() - date) / 1000) let interval = seconds / 31536000 if (interval > 1) { return Math.floor(interval) + 'yr' } interval = seconds / 2592000 if (interval > 1) { return Math.floor(interval) + 'mo' } interval = seconds / 86400 if (interval > 1) { return Math.floor(interval) + 'd' } interval = seconds / 3600 if (interval > 1) { return Math.floor(interval) + 'h' } interval = seconds / 60 if (interval > 1) { return Math.floor(interval) + 'm' } return Math.floor(seconds) + 's' } const ChatContainer = ({ navigation }) => { return ( <View style={styles.container}> <Stories /> <ChatList navigation={navigation} /> </View> ) } const Stories = () => { const [stories] = useGlobalState('stories') return ( <ScrollView style={[ styles.shadow, { flexGrow: 0, position: 'absolute', left: 0, right: 0, top: 0, paddingVertical: 15, }, ]} horizontal={true} showsHorizontalScrollIndicator={false} > {stories.map((story) => ( <View key={story.id} style={{ alignItems: 'center', marginHorizontal: 5 }} > <Avatar size={60} rounded source={{ uri: story.avatar }} /> <Text style={{ fontWeight: 600 }}>{story.fullname}</Text> </View> ))} </ScrollView> ) } const ChatList = ({ navigation }) => { const viewport = useWindowDimensions() const [conversations, setConversations] = useState([]) const getConversations = () => { let limit = 30 let conversationsRequest = new CometChat.ConversationsRequestBuilder() .setLimit(limit) .build() conversationsRequest .fetchNext() .then((conversationList) => setConversations(conversationList)) .catch((error) => { console.log('Conversations list fetching failed with error:', error) }) } useEffect(() => { getConversations() }, [navigation]) return ( <ScrollView style={{ minHeight: viewport.height.toFixed(0) - 189, marginTop: 50, paddingTop: 15, }} showsVerticalScrollIndicator={false} > {conversations.map((conversation, index) => ( <Conversation key={index} currentUser={auth.currentUser.uid.toLowerCase()} owner={conversation.lastMessage.receiverId.toLowerCase()} conversation={conversation.lastMessage} navigation={navigation} /> ))} </ScrollView> ) } const Conversation = ({ conversation, currentUser, owner, navigation }) => { const possessor = (key) => { return currentUser == owner ? conversation.sender[key] : conversation.receiver[key] } const handleNavigation = () => { navigation.navigate('ChatScreen', { id: possessor('uid'), name: possessor('name'), avatar: possessor('avatar'), }) } return ( <TouchableOpacity style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 20, }} onPress={handleNavigation} > <Avatar size={50} rounded source={{ uri: possessor('avatar') }} /> <View style={{ flex: 1, marginLeft: 15, flexDirection: 'row', justifyContent: 'space-between', }} > <View> <Text h5 style={{ fontWeight: 700 }}> {possessor('name')} </Text> <Text style={{ color: 'gray' }}> {conversation.text.slice(0, 30) + '...'} </Text> </View> <Text style={{ color: 'gray' }}> {timeAgo(new Date(Number(conversation.sentAt) * 1000).getTime())} </Text> </View> </TouchableOpacity> ) } export default ChatContainer const styles = StyleSheet.create({ container: { backgroundColor: 'white', borderTopLeftRadius: 40, borderTopRightRadius: 40, paddingTop: 30, overflow: 'hidden', paddingHorizontal: 15, }, shadow: { shadowColor: '#171717', shadowOffsetWidth: 0, shadowOffsetHeight: 2, shadowOpacity: 0.2, shadowRadius: 3, backgroundColor: 'white', zIndex: 9999, }, }) The FloatingButton Component This component is responsible for launching the modal which allows us to chat with a new user on our platform. Here is the code snippet for it. UserList import { View, StyleSheet, TouchableOpacity } from 'react-native' import Icon from 'react-native-vector-icons/FontAwesome5' import { setGlobalState } from '../store' const FloatingButton = () => { return ( <View style={styles.container}> <TouchableOpacity onPress={() => setGlobalState('showUsers', true)} style={styles.button} activeOpacity={0.5} > <Icon name="plus" size={24} color="white" /> </TouchableOpacity> </View> ) } const styles = StyleSheet.create({ container: { position: 'absolute', bottom: 60, right: 30, }, button: { shadowColor: '#171717', shadowOffsetWidth: 0, shadowOffsetHeight: 2, shadowOpacity: 0.2, shadowRadius: 3, paddingVertical: 7, paddingHorizontal: 9, borderRadius: 50, backgroundColor: '#122643', }, }) export default FloatingButton The UserList Component This component is launched by the . It displays all the users that are on our platform and enables you to have a first-time conversation with them. After then, they can appear in your conversation list. Below is the code responsible for its implementation. FloatingButton import { CometChat } from '@cometchat-pro/react-native-chat' import { useEffect, useState } from 'react' import { View, Text, StyleSheet, TouchableOpacity, ScrollView, useWindowDimensions, } from 'react-native' import { Avatar, Button, Overlay } from 'react-native-elements' import { setGlobalState, useGlobalState } from '../store' const timeAgo = (date) => { let seconds = Math.floor((new Date() - date) / 1000) let interval = seconds / 31536000 if (interval > 1) { return Math.floor(interval) + 'yr' } interval = seconds / 2592000 if (interval > 1) { return Math.floor(interval) + 'mo' } interval = seconds / 86400 if (interval > 1) { return Math.floor(interval) + 'd' } interval = seconds / 3600 if (interval > 1) { return Math.floor(interval) + 'h' } interval = seconds / 60 if (interval > 1) { return Math.floor(interval) + 'm' } return Math.floor(seconds) + 's' } const UserList = ({ navigation }) => { const viewport = useWindowDimensions() const [showUsers] = useGlobalState('showUsers') const [users, setUsers] = useState([]) const toggleOverlay = () => setGlobalState('showUsers', !showUsers) const getUsers = () => { const limit = 30 const usersRequest = new CometChat.UsersRequestBuilder() .setLimit(limit) .build() usersRequest .fetchNext() .then((userList) => setUsers(userList)) .catch((error) => { console.log('User list fetching failed with error:', error) }) } useEffect(() => { getUsers() }, []) return ( <Overlay isVisible={showUsers} onBackdropPress={toggleOverlay} overlayStyle={{ backgroundColor: 'white', justifyContent: 'center', alignItems: 'center', minWidth: viewport.width.toFixed(0) - 200, maxWidth: viewport.width.toFixed(0) - 194, }} > <ScrollView style={{ maxHeight: viewport.height.toFixed(0) - 196, padding: 20, width: '100%', }} showsVerticalScrollIndicator={false} > {users.map((user, index) => ( <User user={user} key={index} navigation={navigation} /> ))} </ScrollView> </Overlay> ) } const User = ({ navigation, user }) => { const handleNavigation = () => { navigation.navigate('ChatScreen', { id: user.uid, name: user.name, avatar: user.avatar, }) setGlobalState('showUsers', false) } return ( <TouchableOpacity style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 20, }} onPress={handleNavigation} > <View> <Avatar size={50} rounded source={{ uri: user.avatar }} placeholderStyle={{ opacity: 0 }} /> </View> <View style={{ flex: 1, marginLeft: 15, flexDirection: 'row', justifyContent: 'space-between', }} > <View> <Text h5 style={{ fontWeight: 700 }}> {user.name} </Text> <Text style={{ color: 'gray' }}>{user.status}</Text> </View> <Text style={{ color: 'gray' }}> {timeAgo(new Date(Number(user.lastActiveAt) * 1000).getTime())} </Text> </View> </TouchableOpacity> ) } const styles = StyleSheet.create({}) export default UserList Fantastic, we have just finished building the dedicated components, let’s proceed to craft out the screens now. The Screens Directory The screens are similar to website pages; each screen represents a page, and you can navigate from one to the next using the React navigator package. Let's proceed with the . Native LoginScreen The LoginScreen This well-crafted screen does a lot of things behind the scene. It uses Firebase Google authentication to sign you into the system. Once you are signed in, Firebase function will take note of you as a logged-in user. This is carried out in the file. But before you are let into the system, your authenticated details will be retrieved and sent to CometChat either for signing up or signing in. Once is done, you are then let into the system. See the code below for a full breakdown. authStateChange AuthNavigation CometChat import { Image, ImageBackground, Pressable, StyleSheet, Text, View, } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { provider, signInWithPopup, getAuth } from '../firebase' import { CONSTANTS } from '../CONSTANTS' import { CometChat } from '@cometchat-pro/react-native-chat' import { setGlobalState } from '../store' const LoginScreen = () => { const signInPrompt = () => { const auth = getAuth() signInWithPopup(auth, provider) .then((result) => { const user = result.user console.log(user) }) .catch((error) => console.log(error)) } return ( <SafeAreaView style={styles.container}> <ImageBackground style={styles.bgfy} source={{ uri: 'https://images.pexels.com/photos/3137056/pexels-photo-3137056.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260', }} > <View style={styles.wrapper}> <Image style={styles.logo} source={require('../assets/privex.png')} /> <Text style={{ color: '#fff' }}> Your fully functional secured chat app solution. </Text> <Pressable titleSize={20} style={styles.button}> <Text style={styles.buttonText} onPress={signInPrompt}> Log In with Google </Text> </Pressable> <Pressable style={{ marginTop: 20 }}> <Text style={{ color: 'white' }}>New User? Sign up</Text> </Pressable> </View> </ImageBackground> </SafeAreaView> ) } export default LoginScreen const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, bgfy: { width: '100%', height: '100%', resizeMode: 'contain', flex: 1, justifyContent: 'center', alignItems: 'center', }, wrapper: { backgroundColor: 'rgba(18, 38, 67, 0.96);', flex: 1, justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%', }, logo: { width: 200, height: 50, resizeMode: 'contain', }, button: { backgroundColor: '#0caa92', alignItems: 'center', justifyContent: 'center', minHeight: 42, borderRadius: 20, paddingHorizontal: 40, paddingVertical: 10, marginTop: 30, }, buttonText: { color: 'white', fontWeight: 600, fontSize: 20, }, }) The HomeScreen This well-crafted screen brings together all the dedicated components in the components directory to one space. Each of the components knows how to perform its duties. Not many words here, let the code below do all the explaining. import { SafeAreaView, StyleSheet } from 'react-native' import ChatContainer from '../components/ChatContainer' import FloatingButton from '../components/FloatingButton' import HomeHeader from '../components/HomeHeader' import UserList from '../components/UserList' const HomeScreen = ({ navigation }) => { return ( <SafeAreaView style={styles.container}> <HomeHeader /> <ChatContainer navigation={navigation} /> <FloatingButton /> <UserList navigation={navigation} /> </SafeAreaView> ) } export default HomeScreen const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#122643', }, }) The ChatScreen Lastly for the screens, we have the chat screen that lets us perform one-on-one conversations with another user. It heavily uses the for its operations, let's take a look at the function of the code. CometChat SDK import { CometChat } from '@cometchat-pro/react-native-chat' import { useEffect, useRef, useState } from 'react' import { SafeAreaView, ScrollView, StyleSheet, TouchableOpacity, useWindowDimensions, View, } from 'react-native' import { Avatar, Input, Text } from 'react-native-elements' import Icon from 'react-native-vector-icons/FontAwesome5' import { getAuth } from '../firebase' const ChatScreen = ({ navigation, route }) => { return ( <SafeAreaView style={styles.container}> <Header navigation={navigation} route={route} /> <MessageContainer route={route} /> </SafeAreaView> ) } const Header = ({ navigation, route }) => ( <View style={[styles.flexify, { paddingHorizontal: 15, paddingVertical: 25 }]} > <TouchableOpacity onPress={() => navigation.goBack()} activeOpacity={0.5}> <Icon name="arrow-left" size={18} color="white" /> </TouchableOpacity> <View style={[styles.flexify, { flex: 1, marginLeft: 15 }]}> <View style={styles.flexify}> <Avatar rounded source={{ uri: route.params.avatar }} placeholderStyle={{ opacity: 0 }} /> <Text style={{ color: 'white', fontWeight: 600, marginLeft: 10, textTransform: 'capitalize', }} > {route.params.name} </Text> </View> <View style={styles.flexify}> <TouchableOpacity activeOpacity={0.5} style={{ marginRight: 25 }}> <Icon name="video" size={18} color="white" /> </TouchableOpacity> <TouchableOpacity activeOpacity={0.5}> <Icon name="phone-alt" size={18} color="white" /> </TouchableOpacity> </View> </View> </View> ) const MessageContainer = ({ route }) => { const viewport = useWindowDimensions() const [message, setMessage] = useState('') const [messages, setMessages] = useState([]) const scrollViewRef = useRef() const auth = getAuth() const sendMessage = () => { let receiverID = route.params.id let messageText = message let receiverType = CometChat.RECEIVER_TYPE.USER let textMessage = new CometChat.TextMessage( receiverID, messageText, receiverType ) CometChat.sendMessage(textMessage).then( (message) => { setMessages((prevState) => [...prevState, message]) setMessage('') console.log('Message sent successfully:', message) }, (error) => { console.log('Message sending failed with error:', error) } ) } const getMessages = () => { let UID = route.params.id let limit = 30 let messagesRequest = new CometChat.MessagesRequestBuilder() .setUID(UID) .setLimit(limit) .build() messagesRequest .fetchPrevious() .then((messages) => setMessages(messages)) .catch((error) => { console.log('Message fetching failed with error:', error) }) } const listenForMessage = () => { const listenerID = Math.random().toString(16).slice(2) CometChat.addMessageListener( listenerID, new CometChat.MessageListener({ onTextMessageReceived: (message) => { setMessages((prevState) => [...prevState, message]) }, }) ) } useEffect(() => { getMessages() listenForMessage() }, [route]) return ( <> <ScrollView ref={scrollViewRef} onContentSizeChange={(width, height) => scrollViewRef.current.scrollTo({ y: height }) } style={{ backgroundColor: 'white', maxHeight: viewport.height.toFixed(0) - 162, padding: 20, }} showsVerticalScrollIndicator={false} > <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', marginVertical: 5, }} ></View> {messages.map((message, index) => ( <Message key={index} currentUser={auth.currentUser.uid.toLowerCase()} owner={message.receiverId.toLowerCase()} message={message} /> ))} </ScrollView> <View style={[styles.flexify, styles.positAtBottom, styles.shadow]}> <TouchableOpacity style={{ paddingVertical: 5, paddingHorizontal: 7, borderRadius: 50, backgroundColor: '#122643', }} activeOpacity={0.5} > <Icon name="plus" size={12} color="white" /> </TouchableOpacity> <View style={{ flex: 1 }}> <Input placeholder="Write a message..." inputContainerStyle={{ borderBottomWidth: 0 }} onSubmitEditing={() => sendMessage()} onChangeText={(text) => setMessage(text)} value={message} inputStyle={{ fontSize: 12 }} autoFocus={true} /> </View> <TouchableOpacity style={{ paddingVertical: 5, paddingHorizontal: 7, borderRadius: 50, backgroundColor: '#c5c5c5', }} activeOpacity={0.5} disabled={message.length < 1} onPress={() => sendMessage()} > <Icon name="arrow-right" size={12} color="black" /> </TouchableOpacity> </View> </> ) } const Message = ({ message, currentUser, owner }) => { const dateToTime = (date) => { let hours = date.getHours() let minutes = date.getMinutes() let ampm = hours >= 12 ? 'pm' : 'am' hours = hours % 12 hours = hours ? hours : 12 minutes = minutes < 10 ? '0' + minutes : minutes let strTime = hours + ':' + minutes + ' ' + ampm return strTime } const isDateToday = (date) => { const today = new Date().getDate() const day = new Date(date * 1000).getDate() return today == day } return currentUser == owner ? ( <View style={[styles.flexify, styles.spaceMsg]}> <Avatar placeholderStyle={{ opacity: 0 }} rounded source={{ uri: message.sender.avatar }} /> <View style={[styles.msgBg, { marginLeft: 10 }]}> <Text style={{ fontWeight: 800, fontSize: 13, color: '#4c4c4c', textTransform: 'capitalize', }} > {message.sender.name} </Text> <Text style={{ fontWeight: 600, marginVertical: 5 }}> {message.text} </Text> <Text style={{ fontWeight: 600 }}> {dateToTime(new Date(message.sentAt * 1000))} </Text> </View> </View> ) : ( <View style={[styles.flexify, styles.spaceMsg]}> <View style={[styles.msgBg, { backgroundColor: '#c5c5c5', marginRight: 10 }]} > <Text style={{ fontWeight: 800, fontSize: 13, color: '#4c4c4c', textTransform: 'capitalize', }} > {message.sender.name} </Text> <Text styles={{ fontWeight: 600, marginVertical: 5 }}> {message.text} </Text> <Text style={{ fontWeight: 600 }}> {dateToTime(new Date(message.sentAt * 1000))} </Text> </View> <Avatar placeholderStyle={{ opacity: 0 }} rounded source={{ uri: message.sender.avatar }} /> </View> ) } export default ChatScreen const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#122643', }, flexify: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, msgBg: { flex: 1, backgroundColor: '#efefef', borderRadius: 20, padding: 10, }, spaceMsg: { alignItems: 'flex-end', marginVertical: 5, }, shadow: { shadowColor: '#171717', shadowOffsetWidth: 0, shadowOffsetHeight: 2, shadowOpacity: 0.2, shadowRadius: 3, backgroundColor: 'white', }, positAtBottom: { position: 'absolute', left: 0, right: 0, bottom: 0, paddingHorizontal: 15, paddingVertical: 15, }, }) There are three functions you should take note of, they are the meat of this screen. The , , and functions. They utilize the and each one of them performs their operations according to their names. getMessages sendMessage listenForMessage CometChat SDK That’s the last screen for the application, let’s seal it up with the App component and the router set up… Setting Up The Router Now that we've finished coding the project, let's set up the navigation routers and guards. To do so, create and paste the following codes as directed below. This categorizes the screens into two groups: those that require authentication and those that do not. Make a new file called in the project's root and paste the code below into it. The Navigation file "navigation.js" import React from 'react' import { createStackNavigator } from '@react-navigation/stack' import { NavigationContainer } from '@react-navigation/native' import HomeScreen from './screens/HomeScreen' import LoginScreen from './screens/LoginScreen' import ChatScreen from './screens/ChatScreen' const Stack = createStackNavigator() const screenOption = { headerShown: false, } export const SignedInStack = () => ( <NavigationContainer> <Stack.Navigator initialRouteName="HomeScreen" screenOptions={screenOption}> <Stack.Screen name="HomeScreen" component={HomeScreen} /> <Stack.Screen name="ChatScreen" component={ChatScreen} /> </Stack.Navigator> </NavigationContainer> ) export const SignedOutStack = () => ( <NavigationContainer> <Stack.Navigator initialRouteName="LoginScreen" screenOptions={screenOption} > <Stack.Screen name="LoginScreen" component={LoginScreen} /> </Stack.Navigator> </NavigationContainer> ) This file displays screens logically to you based on the of the firebase authentication service. It is also responsible for signing a user to depending on whether they are registering or logging in into the system. To proceed, create a new file in the project's root called and paste the code below into it. The AuthNavigation file authState CometChat AuthNavigation.js import React, { useEffect, useState } from 'react' import { SignedInStack, SignedOutStack } from './navigation' import { onAuthStateChanged, getAuth } from './firebase' import { CometChat } from '@cometchat-pro/react-native-chat' import { CONSTANTS } from './CONSTANTS' const AuthNavigation = () => { const [currentUser, setCurrentUser] = useState(null) const auth = getAuth() const userHandler = (user) => user ? setCurrentUser(user) : setCurrentUser(null) const signUpWithCometChat = (data) => { const authKey = CONSTANTS.Auth_Key const user = new CometChat.User(data.uid) user.setName(data.displayName) user.setAvatar(data.photoURL) CometChat.createUser(user, authKey) .then((res) => { console.log('User signed up...', res) CometChat.login(data.uid, authKey) .then((u) => { console.log(u) userHandler(data) }) .catch((error) => console.log(error)) }) .catch((error) => { console.log(error) alert(error.message) }) } const loginWithCometChat = (data) => { const authKey = CONSTANTS.Auth_Key CometChat.login(data.uid, authKey) .then((u) => { console.log('User Logged in...', u) userHandler(data) }) .catch((error) => { if (error.code === 'ERR_UID_NOT_FOUND') { signUpWithCometChat(data) } else { console.log(error) } }) } useEffect( () => onAuthStateChanged(auth, (user) => { if (currentUser == null && user) { loginWithCometChat(user) } else { userHandler(null) } }), [] ) return <>{currentUser ? <SignedInStack /> : <SignedOutStack />}</> } export default AuthNavigation Finally, the App component… This component puts together every part of this project. Please replace the content of this file with the code below. The App Component import { CometChat } from '@cometchat-pro/react-native-chat' import { useEffect } from 'react' import AuthNavigation from './AuthNavigation' import { CONSTANTS } from './CONSTANTS' export default function App() { const initCometChat = () => { let appID = CONSTANTS.APP_ID let region = CONSTANTS.REGION let appSetting = new CometChat.AppSettingsBuilder() .subscribePresenceForAllUsers() .setRegion(region) .build() CometChat.init(appID, appSetting) .then(() => console.log('Initialization completed successfully')) .catch((error) => console.log('Initialization failed with error:', error)) } useEffect(() => initCometChat(), []) return <AuthNavigation /> } Cheers, you just smashed this app now it’s time you do more than this with this new trick you’ve learned. You can spin up your server using the code below on your terminal if you have not done that already. # Start your ReactNative local server on the web view yarn web The App should function like the one in the image below. Conclusion We've reached the end of this tutorial; hopefully, you learned something new. Becoming a modern-day developer can be difficult, but it is not impossible; you can accomplish more than you think; all you need is a little guidance. And now you know how to use React Native, Firebase, and to create a fantastic chat app with a beautiful interface. I have more of these tutorials that will show you how to make a private or public group chat. I'm excited to see your magnificent creations. CometChat All the best! About the Author Gospel Darlington kick-started his journey as a software engineer in 2016. Over the years, he has grown full-blown skills in JavaScript stacks such as React, React Native, VueJs, and more. He is currently freelancing, building apps for clients, writing technical tutorials teaching others how to do what he does. Gospel Darlington is open and available to hear from you. You can reach him on , , , or on his . LinkedIn Facebook Github website Previously published . on Medium’s subdomain