Chat app with React Native : Firebase User Authentication with react-native-firebase

In this tutorial, let’s start using a backend service to add real-time features to the chat app. For our backend services, I’m going to use Firebase. We’ll look at how to install and configure Firebase SDK in a React Native app with the help of react-native-firebase module, as well as set up and configure email authentication. In order to follow this tutorial and future posts, you’ll need to have an active Firebase project.

Create a new Firebase project from the console

To access the Firebase credentials for each mobile OS platform and configure them to use the Firebase SDK, create a new Firebase project. If you’ve created one already, you can skip this step.

Create a new project as shown below:

Complete the details of your Firebase project:

When you click the button “Create project”, you’ll be redirected to the dashboard screen. That’s it. You’ve successfully created a new Firebase project.

Now make sure that the email sign-in method is enabled. From the Firebase console, navigate to the Authentication section from the side menu.

Go to the second tab (Sign-in method) and make sure you enable the email sign-in provider.

Add the Firebase SDK to the app

If you’ve used react-native-firebase version 5 or below, you’ve likely noticed that it was a monorepo that used to manage all Firebase dependencies from one module.

Version 6 of this library wants you to only install those dependencies based on Firebase features that you want to use. For example, in the current app, to support the email authentication feature, you’ll to install the auth and core app packages.

From the terminal window, execute the following command:

yarn add @react-native-firebase/app @react-native-firebase/auth

Add Firebase credentials to your iOS app

Firebase provides a file called GoogleService-Info.plist that contains all the API keys as well as other authentication credentials for iOS devices.

To get these credentials, go to back to the Firebase console in a browser window. From the dashboard screen of your Firebase project, open “Project settings” from the side menu.

Go to the “Your apps” section and click on the iOS icon 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 /ios/ChatApp.xcodeproj file. Right-click on the project name and select the “Add Files” option, and then choose the file to add to this project.

Then open ios/ChatApp/AppDelegate.m and add the following header:

#import <Firebase.h>

In the same file, within the didFinishLaunchingWithOptions method, add the following configure method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    if ([FIRApp defaultApp] == nil) {
      [FIRApp configure];
    }

Lastly, go back to the terminal window to install pods:

cd ios/ && pod install
# after pods are installed
cd ..

Make sure you build the iOS app:

npx react-native run-ios

That’s it. The configurations for the Firebase SDK and credentials in a React Native app are complete.

Create a home screen

In the previous post, we successfully configured an Auth stack that displays those screens when the end user is not authorized or logged into the app. Put another way, there are a set of screens that are only going to be accessible to the user when they are logged in. Let’s call the group of screens—that are visible after login—the home stack.

One of the screens is going to be a home screen where all the chat rooms will be listed. In this section, let’s start by creating a basic home screen, such that we have a functional navigational flow between the home stack and the auth stack.

Create a new screen component called HomeScreen.js inside the src/screens/ directory with the following code snippet:

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Title } from 'react-native-paper';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Title>Home Screen</Title>
      <Title>All chat rooms will be listed here</Title>
      <FormButton modeValue='contained' title='Logout' />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#f5f5f5',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
});

Create home stack navigator

Create a new stack navigator file called HomeStack.js inside src/navigation.js. Here, we’ll keep those routes, which are only available after logging in. You can think of these as protected routes.

Open this file and add the following code snippet. Nothing new is going on in terms of creating a stack navigator:

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';

const Stack = createStackNavigator();

export default function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name='Home' component={HomeScreen} />
    </Stack.Navigator>
  );
}

Create an auth provider

In this section, we’ll create an authentication provider to check whether the user is logged in or not, and access them if they are logged in.

Create a new file called AuthProvider.js inside src/navigation/. Start by importing the following statements:

import React, { createContext, useState } from 'react';
import auth from '@react-native-firebase/auth';

Then create an AuthContext and make sure to export it since you’re going to use it on several different screens.

export const AuthContext = createContext({});

In React.js, the Context API is designed to share global data for a tree of React components. When you’re creating a context (like above), there’s a requirement to pass a default value. This value is used when a component doesn’t have a matching provider.

The provider allows the React components to subscribe to the context changes. To create an auth provider, export a function called AuthProvider. This provider is going to allow the screen components to access the current user in the application. Define a state variable called user.

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

  return (
    <AuthContext.Provider
      value={{
        user,
        setUser,
        login: async (email, password) => {
          try {
            await auth().signInWithEmailAndPassword(email, password);
          } catch (e) {
            console.log(e);
          }
        },
        register: async (email, password) => {
          try {
            await auth().createUserWithEmailAndPassword(email, password);
          } catch (e) {
            console.log(e);
          }
        },
        logout: async () => {
          try {
            await auth().signOut();
          } catch (e) {
            console.error(e);
          }
        }
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

In the value prop above, some functions have also been defined. These functions are now available to be used anywhere in the screens component tree using React Context.

Each of the functions is consuming Firebase methods to interact with the real-time Firebase backend service. Both the login and register functions require the user’s email and password to verify/save credentials. The logout method invokes a simple signOut() method.

All these Firebase methods are available in the @react-native-firebase/auth package. Do note that all these functions are asynchronous actions and thus, using async await syntax helps.

Wrapping routes with auth provider

Now the auth provider is created, but how do we use it for a set of components in the current app tree? Well, we have to wrap this provider around the Routes in order to obtain access to the helper functions and the value of current user (as described in the previous section) in the screen components of the auth navigator.

Open the navigation/index.js file and modify it as follows:

import React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import { AuthProvider } from './AuthProvider';
import Routes from './Routes';

/**
 * Wrap all providers here
 */

export default function Providers() {
  return (
    <PaperProvider>
      <AuthProvider>
        <Routes />
      </AuthProvider>
    </PaperProvider>
  );
}

If you’ll recall from the previous post, we noted that we should wrap all components using one or more providers in this file. That’s exactly what this file is for.

Check if the user is logged in or not

To check if a user is logged or not, let’s modify the navigation/Routes.js file. Using the value of the user from the auth provider, we switch between the stack navigators. To start, we import the following statements:

import React, { useContext, useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import auth from '@react-native-firebase/auth';
import AuthStack from './AuthStack';
import HomeStack from './HomeStack';
import { AuthContext } from './AuthProvider';
import Loading from '../components/Loading';

From the above snippet, ignore the Loading component for now. You’re going to create it at the end of this section.

Now, inside the Routes function, define two state variables, initializing and loading, to check whether the user’s state is logged in or not. Also, from the context value, fetch user and setUser.

Then, define a function called onAuthStateChanged, which is going to handle user state changes. Using the useEffect hook, we can subscribe to this state change function—just make sure you unsubscribe when the component unmounts. This method allows you to subscribe to real-time events when the user performs an action. The action here could be: logging in, signing out, and so on.

export default function Routes() {
  const { user, setUser } = useContext(AuthContext);
  const [loading, setLoading] = useState(true);
  const [initializing, setInitializing] = useState(true);

  // Handle user state changes
  function onAuthStateChanged(user) {
    setUser(user);
    if (initializing) setInitializing(false);
    setLoading(false);
  }

  useEffect(() => {
    const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
    return subscriber; // unsubscribe on unmount
  }, []);

  if (loading) {
    return <Loading />;
  }

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

Lastly, create a new component file called Loading.js inside src/components/ directory. This component is going to be responsible for displaying a loading spinner.

import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';

export default function Loading() {
  return (
    <View style={styles.loadingContainer}>
      <ActivityIndicator size='large' color='#6646ee' />
    </View>
  );
}

const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center'
  }
});

Completing the app

In order for users to perform auth actions in the app, we have to use the Context in each of the screen components for different actions.

Start by opening LoginScreen.js. Import useContext from React and AuthContext from AuthProvider.

import React, { useState, useContext } from 'react';
// rest of the import statements remain same
import { AuthContext } from '../navigation/AuthProvider';

export default function LoginScreen({ navigation }) {
  const { login } = useContext(AuthContext);

  // rest remains statements
}

Inside the LoginScreen function, make sure to add an onPress prop, as shown below:

<FormButton
  title='Login'
  modeValue='contained'
  labelStyle={styles.loginButtonLabel}
  onPress={() => login(email, password)}
/>

Similarly, we have to modify the SignupScreen.js file:

import React, { useState, useContext } from 'react';
// rest of the import statements remain same
import { AuthContext } from '../navigation/AuthProvider';

export default function SignupScreen({ navigation }) {
  const { register } = useContext(AuthContext);
  // rest remains statements
}

// Add the onPress prop to <FormButton />

<FormButton
  title='Signup'
  modeValue='contained'
  labelStyle={styles.loginButtonLabel}
  onPress={() => register(email, password)}
/>;

Lastly, modify the HomeScreen to add a sign out button, and when the user is in the logged-in state, display their user uid (the unique identifier in Firebase to differentiate and store different users).

import React, { useContext } from 'react';
import { View, StyleSheet } from 'react-native';
import { Title } from 'react-native-paper';
import { AuthContext } from '../navigation/AuthProvider';
import FormButton from '../components/FormButton';

export default function HomeScreen() {
  const { user, logout } = useContext(AuthContext);

  return (
    <View style={styles.container}>
      <Title>Home Screen</Title>
      <Title>All chat rooms will be listed here</Title>
      <Title>{user.uid}</Title>
      <FormButton
        modeValue='contained'
        title='Logout'
        onPress={() => logout()}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#f5f5f5',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
});

If we test this on our simulator, we should get similar results as shown below. Perform the prompted steps. Try creating a new user from the sign-up screen, and should get a uid on the home screen.

You can verify the uid of the current user by going to the dashboard screen from the Firebase console.

Conclusion

Congratulations! You’ve completes this tutorial and successfully added an authentication flow between the two stack navigators. In the next part of this series, we’ll explore more features such as creating and storing chat rooms in a collection in Firestore, as well as displaying all chat rooms on the home screen. To create a new chat room, we’ll create a new modal screen and make changes to the current home stack accordingly.

What’s Next?

In the next post of this series, we are going to explore how to create a modal screen using react-navigation stack navigator. This modal screen is going to have separate navigator as well as to be used to create a new chat room.

Then, we are going to add Firebase NoSQL database Firestore and add a query to store the name of a chat room in a collection.

Here is part 3 👇

You can find the complete source code for this project at this Github repo.

👉 Here is a list of resources used in this tutorial:

💙 To learn more about React Native, check out these resources:

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square