Build an Anonymous Chat App with React Native and Firebase
The anonymous sign-in method provided by Firebase enables you to create a React Native chat app that does not require users to login. Here's how.
6 October 2021
The Crowdbotics platform leverages AI and systematic code reuse to help you build better applications faster and with less risk.
Don’t take our word for it, see what our customers have to say.
By Industry
Everything you need to be successful with Crowdbotics.
Use & Learn
We are on a mission to radically transform the software development lifecycle.
The year 2020 has seen a lot of changes in the React Native world:
react-navigation
adopted a declarative and component-based approach to implement navigation in an appreact-native-firebase
, the go-to package to use Firebase SDK, released its sixth versionIn this tutorial, I am going to walk you through building a simple chat application that a user can log in to without credentials and straightaway enter a chat room using the anonymous sign-in method provided by Firebase.
The purpose of this tutorial is to get you familiar with all the latest updates in React Native world and its libraries like react-navigation
and react-native-firebase
that are used often. If you wish to add a new feature that is not covered in this tutorial, feel free to do that and follow along at your own pace.
The following requirements will make sure you have a suitable development environment:
10.x.x
installed on your local machinewatchman
the file watcher installedreact-native-cli
installed through npm or access via npx
For a complete walkthrough on how you can set up a development environment for React Native, you can go through official documentation here.
Also, do note that the following tutorial is going to use the react-native
version 0.61.5
. Please make sure you are using a version of React Native above 0.60.x
.
To generate a new React Native project you can use the react-native cli tool. Or, if you want to follow along, I am going to generate a new app using the Crowdbotics App Builder.
Register either using your GitHub credentials or your email. Once logged in, you can click the Create App
button to create a new app. The next screen is going to prompt you as to what type of application you want to build. Choose Mobile App
.
Enter the name of the application and click the button Create App
. After you link your GitHub account from the dashboard, you are going to have access to the GitHub repository for the app. This repo generated uses the latest react-native
version and comes with built-in components and complete examples that can be the base foundation for your next app.
You can now clone or download the GitHub repo that is generated by the Crowdbotics app building platform. Once you have access to the repo on your local development environment, make sure to navigate inside it. You will have to install the dependencies for the first time using the command yarn install
. Then, to make it work on the iOS simulator/devices, make sure to install pods using the following commands from a terminal window.
# navigate inside iOS
cd ios/
# install pods
pod install
That’s it. It’s a three-step process. Now, let us get back to our tutorial.
To start, create a new React Native project and install the dependencies to set up and use the react-navigation
library.
# create a new project
npx react-native init rnAnonChatApp
cd rnAnonChatApp
# install core navigation dependencies
yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
From React Native 0.60.x
and higher, linking is automatic so you don’t need to run react-native link
.
To finalize the installation, on iOS, you have to install pods. (Note: Make sure you have Cocoapods installed.)
cd ios/ && pod install
# after pods are installed
cd ..
Similarly, on Android, open android/app/build.gradle
and add the following two lines in the dependencies
section:
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
Lastly, to save the app from crashing in a production environment, add the following line in index.js
.
import 'react-native-gesture-handler'
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
AppRegistry.registerComponent(appName, () => App)
That’s it for setting up the react-navigation
library.
What is a mobile app without the use of icons? One famous library created by Oblador is called react-native-vector-icons
. This library has a set of icons bundled from AntDesign, FontAwesome, Ionicons, MaterialIcons, and so on.
In this section, let us install that. Open up a terminal window and run the command yarn add react-native-vector-icons
to install the library.
After the library is installed, for iOS, copy the following list of fonts inside ios/rnAnonChatApp/Info.plist
.
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>
<string>Entypo.ttf</string>
<string>EvilIcons.ttf</string>
<string>Feather.ttf</string>
<string>FontAwesome.ttf</string>
<string>FontAwesome5_Brands.ttf</string>
<string>FontAwesome5_Regular.ttf</string>
<string>FontAwesome5_Solid.ttf</string>
<string>Foundation.ttf</string>
<string>Ionicons.ttf</string>
<string>MaterialIcons.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Octicons.ttf</string>
<string>Zocial.ttf</string>
</array>
Then, open ios/Podfile
and add the following:
'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
Open a terminal window to install pods.
cd ios/ && pod install
# after pods are installed
cd ..
For Android, make sure you add the following in android/app/build.gradle
:
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
That’s it to set up a vector icons library in a React Native project.
Before you proceed to set up a navigation pattern in this app to switch between the different screens, let us create two different screens first.
Create a new file called src/screens/Login.js
and add the following code snippet. For now, it going to display a message and a button. This screen is going to be displayed when the user isn’t authenticated to enter the app and access a chat room.
// Login.js
import React from 'react'
import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'
export default function Login() {
// firebase login function later
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome to Chat App</Text>
<TouchableOpacity
style={styles.button}
onPress={() => alert('Anonymous login')}>
<Text style={styles.buttonText}>Enter Anonymously</Text>
<Icon name='ios-lock' size={30} color='#cfdce0' />
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#dee2eb'
},
title: {
marginTop: 20,
marginBottom: 30,
fontSize: 28,
fontWeight: '500'
},
button: {
flexDirection: 'row',
borderRadius: 30,
marginTop: 10,
marginBottom: 10,
width: 300,
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#cf6152'
},
buttonText: {
color: '#dee2eb',
fontSize: 24,
marginRight: 5
}
})
Next, create another file in the same directory called ChatRoom.js
with the following code snippet:
//ChatRoom.js
import React from 'react'
import { View, StyleSheet, Text } from 'react-native'
export default function ChatRoom() {
return (
<View style={styles.container}>
<Text style={styles.title}>
You haven't joined any chat rooms yet :'(
</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#dee2eb'
},
title: {
marginTop: 20,
marginBottom: 30,
fontSize: 28,
fontWeight: '500'
}
})
For now, the above screen component is going to display a text message, but later it is going to contain many functionalities and features for the user to interact with.
Start by creating a new directory src/navigation/
and inside it, two new files:
SignInStack.js
SignOutStack.js
Both of these files are self-explanatory by their names. Their functionality is going to contain screens related to the state of the app. For example, the SignInStack.js
is going to have a stack navigator that has screen files (such as ChatRoom.js
) that a user can only access after they are authenticated.
A Stack Navigator provides the React Native app with a way to transition between different screens, similar to how the navigation in a web browser works. It pushes or pops a screen when in the navigational state.
Now that you have an idea of what exactly a stack navigator is, understand what NavigationContainer
and createStackNavigator
do.
NavigationContainer
is a component that manages the navigation tree. It also contains the navigation state and has to wrap all the navigator’s structure.createStackNavigator
is a function used to implement a stack navigation pattern. This function returns two React components: Screen
and Navigator
, which help us configure each component screen.Open SignInStack.js
and add the following code snippet:
import * as React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import ChatRoom from '../screens/ChatRoom.js'
const Stack = createStackNavigator()
export default function SignInStack() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name='ChatRoom'
component={ChatRoom}
options={{ title: 'Chat Room' }}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
In the above snippet, there are two required props with each Stack.Screen
. The prop name
refers to the name of the route, and the prop component
specifies which screen to render at that particular route.
Similarly, in the file SignOutStack.js
, add the following code snippet:
import * as React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import Login from '../screens/Login'
const Stack = createStackNavigator()
export default function SignOutStack() {
return (
<NavigationContainer>
<Stack.Navigator headerMode='none'>
<Stack.Screen name='Login' component={Login} />
</Stack.Navigator>
</NavigationContainer>
)
}
This is how navigators are defined declaratively using version 5 of react-navigation
. It follows a more component-based approach, similar to that of react-router
in web development.
Before we begin to define a custom authentication flow (since this version of react-navigation
does not support a SwitchNavigator
like previous versions), let us add the Firebase SDK. Using the Firebase functions, you can then easily implement an authentication flow to switch between the two stack navigators.
If you have used react-native-firebase
version 5 or below, you may have noticed that it was a monorepo that used to manage all Firebase dependencies from one module.
Version 6 brings you the option to only install those Firebase dependencies required for features that you want to use. For example, in the current app, you are going to start by adding the auth package. Also, do note that the core module @react-native-firebase/app
is always required.
Open a terminal window to install this dependency.
yarn add @react-native-firebase/app
The Firebase provides a GoogleService-Info.plist
file that contains all the API keys as well as other credentials for iOS devices to authenticate the correct Firebase project.
To get the credentials, go to the Firebase console and create a new project. After that, from the dashboard screen of your Firebase project, open Project settings from the side menu.
Then, go to the Your apps section and click on the icon iOS
to select the platform.
Enter the application details and click on Register app.
Then download the GoogleService-Info.plist
file as shown below.
Open Xcode, then open the file /ios/rnAnonChatApp.xcodeproj
file. Right-click on your project name and choose the Add Files
option, then select the file to add to this project.
Now, open ios/rnAnonChatApp/AppDelegate.m
and add the following header.
#import <Firebase.h>
Within the didFinishLaunchingWithOptions
method, add the following configure
method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
Open a terminal window to install pods.
cd ios/ && pod install
# after pods are installed
cd ..
Firebase provides a google-services.json
file that contains all the API keys as well as other credentials for Android devices to authenticate the correct Firebase project.
Go to the Your apps section and click on the icon Android
to select the platform.
Then download the google-services.json
file as shown below.
Now add the downloaded JSON file to your React Native project at the following location: /android/app/google-services.json
.
After that, open android/build.gradle
and add the following:
dependencies {
// ...
classpath 'com.google.gms:google-services:4.2.0'
}
Next, open android/app/build.gradle
file and at the very bottom of it, add the following:
apply plugin: 'com.google.gms.google-services'
To support the anonymous login feature in the current app, make sure you install the following package:
yarn add @react-native-firebase/auth
# Using iOS
cd ios/ && pod install
# After installing
cd ..
Now go back to the Firebase console of the project and navigate to the Authentication section from the side menu.
Go to the second tab Sign-in method and enable the Anonymous sign-in provider.
That’s it! The app is now ready to allow the user to log in anonymously.
To complete the navigation of the current React Native app, you have to set up the authentication flow. Create a new file src/navigation/AuthNavigator.js
and make sure to import the following as well as both stack navigators created early in this tutorial.
import React, { useState, useEffect, createContext } from 'react'
import auth from '@react-native-firebase/auth'
import SignInStack from './SignInStack'
import SignOutStack from './SignOutStack'
Then, create an AuthContext
that is going to expose the user data to only those screens when the user successfully logs in, that is, the screens that are part of the SignInStack
navigator.
export const AuthContext = createContext(null)
Define the AuthNavigator
functional component. Inside it, create two state variables, initializing
and user
. The state variable initializing
is going to be true
by default. It helps to keep track of the changes in the user state.
When the user state changes to authentication, the initializing
variable is set to false
. The change of the user’s state is handled by a helper function called onAuthStateChanged
.
Next, using the hook useEffect
, subscribe to the auth state changes when the navigator component is mounted. On unmount, unsubscribe it.
Lastly, make sure to pass the value of user
data using the AuthContext.Provider
. Here is the complete snippet:
export default function AuthNavigator() {
const [initializing, setInitializing] = useState(true)
const [user, setUser] = useState(null)
// Handle user state changes
function onAuthStateChanged(result) {
setUser(result)
if (initializing) setInitializing(false)
}
useEffect(() => {
const authSubscriber = auth().onAuthStateChanged(onAuthStateChanged)
// unsubscribe on unmount
return authSubscriber
}, [])
if (initializing) {
return null
}
return user ? (
<AuthContext.Provider value={user}>
<SignInStack />
</AuthContext.Provider>
) : (
<SignOutStack />
)
}
To make it work, open App.js
and modify it as below:
import React from 'react'
import AuthNavigator from './src/navigation/AuthNavigator'
const App = () => {
return <AuthNavigator />
}
export default App
Now, build the app for a specific mobile OS by running either of the commands mentioned:
npx react-native run-ios
# or
npx react-native run-android
Open up a simulator device, and you are going to be welcomed by the login screen.
Right now the app is in a state where you can use the Firebase authentication module to implement real-time signing in and signing out functionalities. Start with the screen/Login.js
file to add sign-in functionality. Import the auth
from @react-native-firebase/auth
as shown below:
// rest of the import statements
import auth from '@react-native-firebase/auth'
Then, inside the Login
functional component, define a helper method that will be triggered when the user presses the sign-in button on this screen. This helper method is going to be an asynchronous function.
async function login() {
try {
await auth().signInAnonymously()
} catch (e) {
switch (e.code) {
case 'auth/operation-not-allowed':
console.log('Enable anonymous in your firebase console.')
break
default:
console.error(e)
break
}
}
}
Lastly, add this method as the value of the prop onPress
for TouchableOpacity
.
<TouchableOpacity style={styles.button} onPress={login}>
{/* ... */}
</TouchableOpacity>
To add a sign out button, open navigation/SignInStack.js
. This button is going to be represented by an icon on the right side of the header bar of the ChatRoom
screen.
Start by importing Icon
and auth
statements.
// after rest of the import statements
import { TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'
import auth from '@react-native-firebase/auth'
Then, add a logOut
helper method that will be triggered when the user presses the icon. This method is going to be inside SignInStack
.
async function logOut() {
try {
await auth().signOut()
} catch (e) {
console.error(e)
}
}
Lastly, add headerRight
in the options
of Stack.Screen
for ChatRoom
.
<Stack.Screen
name='ChatRoom'
component={ChatRoom}
options={{
title: 'Chat Room',
headerRight: () => (
<TouchableOpacity style={{ marginRight: 10 }} onPress={logOut}>
<Icon name='ios-log-out' size={30} color='#444' />
</TouchableOpacity>
)
}}
/>
That’s it. Now, go back to the device in which you are running this app and try logging into the app. Then using the icon in the header bar, log out of the app. This completes the authentication flow.
In the Firebase console, check that the user uid
is created whenever a new user signs in the app and the identifier says anonymous
.
To enter the name of the chat room that is going to be saved in the database (which we will setup later using Firestore), create another screen called screens/CreateChatRoom.js
.
This screen component is going to use a state variable called roomName
to set the name of the room and store it in the database. The UI of the screen is going to be an input field as well as a button. The helper method handleButtonPress
(as described below in code snippet) is going to manage the setting of a chat room. Later, you are going to add the business logic of saving the name.
import React, { useState } from 'react'
import {
View,
StyleSheet,
Text,
TextInput,
TouchableOpacity
} from 'react-native'
export default function CreateChatRoom() {
const [roomName, setRoomName] = useState('')
function handleButtonPress() {
if (roomName.length > 0) {
// create new thread using firebase
}
}
return (
<View style={styles.container}>
<TextInput
style={styles.textInput}
placeholder='Thread Name'
onChangeText={roomName => setRoomName(roomName)}
/>
<TouchableOpacity style={styles.button} onPress={handleButtonPress}>
<Text style={styles.buttonText}>Create chat room</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#dee2eb'
},
title: {
marginTop: 20,
marginBottom: 30,
fontSize: 28,
fontWeight: '500'
},
button: {
backgroundColor: '#2196F3',
textAlign: 'center',
alignSelf: 'center',
paddingHorizontal: 40,
paddingVertical: 10,
borderRadius: 5,
marginTop: 10
},
buttonText: {
color: '#fff',
fontSize: 18
},
textInput: {
backgroundColor: '#fff',
marginHorizontal: 20,
fontSize: 18,
paddingVertical: 10,
paddingHorizontal: 10,
borderColor: '#aaa',
borderRadius: 10,
borderWidth: 1,
marginBottom: 5,
width: 225
}
})
Then, go the navigation/SignInStack.js
file to add this newly created screen to the stack. Start by importing the screen itself.
import CreateChatRoom from '../screens/CreateChatRoom'
Next, add the Stack.Screen
for CreateChatRoom
in the stack as the second route in the navigator.
<Stack.Screen
name='CreateChatRoom'
component={CreateChatRoom}
options={{
title: 'Create a room'
}}
/>
Lastly, to navigate to this new screen, using the navigation
prop, add a headerLeft
option in the screen ChatRoom
as shown below. Make sure to convert the options
to a function in order to use the navigation
prop.
<Stack.Screen
name='ChatRoom'
component={ChatRoom}
options={({ navigation }) => ({
title: 'Chat Room',
headerLeft: () => (
<TouchableOpacity
style={{ marginLeft: 10 }}
onPress={() => navigation.navigate(CreateChatRoom)}>
<Icon name='ios-add' size={30} color='#444' />
</TouchableOpacity>
),
headerRight: () => (
<TouchableOpacity style={{ marginRight: 10 }} onPress={logOut}>
<Icon name='ios-log-out' size={30} color='#444' />
</TouchableOpacity>
)
})}
/>
Here is the output you are going to get:
That’s it for this part. By now you have learned how to set up a real-time authentication flow using Firebase and react-navigation
and add an anonymous sign-in method.
In the next part, you are going to explore how to integrate Firestore to use a real-time database with this chat app, integrate react-native-gifted-chat
to implement chat functionalities and UI, and create sub-collections in Firestore.