Topic 12 of 15 · React Native

Topic 12 : Firebase

Lesson TL;DRTopic 12: Firebase 📖 9 min read · 🎯 advanced · 🧭 Prerequisites: librariesimagepickerreactelements, redux Why this matters Up until now, everything we've built lives only on the device — close the a...
9 min read·advanced·firebase · authentication · firestore · backend-as-a-service

Topic 12: Firebase

📖 9 min read · 🎯 advanced · 🧭 Prerequisites: libraries-image-picker-react-elements, redux

Why this matters

Up until now, everything we've built lives only on the device — close the app, and the data is gone. That works for practice, but real apps need more. Users expect to sign in, pick up where they left off, and see their data on any phone. That's where Firebase comes in. It's Google's ready-made backend — authentication, cloud storage, real-time sync — all without you having to set up a single server. In this lesson, we wire our React Native app into Firebase so users can sign in and have their data live in the cloud.

What You'll Learn

  • Create and configure a Firebase project for a React Native Android app
  • Install and set up @react-native-firebase/app, auth, and firestore packages
  • Initialize Firebase in a dedicated firebase.js module
  • Implement email/password sign-in and sign-out with Firebase Authentication
  • Read and write user documents in Firestore after authentication

The Analogy

Think of Firebase as a fully staffed hotel concierge service for your app. You don't build the kitchen, the security desk, or the records room yourself — you just call the concierge and say "check in this guest" or "retrieve their luggage from storage." The concierge (Firebase) handles identity verification (Authentication), keeps a real-time ledger of guest preferences (Firestore), and scales to ten thousand guests without you hiring extra staff. Your React Native app is the hotel lobby: beautiful, interactive, and completely reliant on the back-of-house operation that Firebase provides invisibly.

Chapter 1: Setting Up Firebase

Before writing a single line of JavaScript, the Firebase project itself must exist and your Android app must be registered within it.

Step 1: Create a Firebase Project

  1. Go to the Firebase Console.
  2. Click Add project and follow the prompts to name and create your project.
  3. Once created, click Add app and select the Android icon.

Step 2: Register Your App

  1. Enter your app's package name (e.g., com.yourappname) and click Register app.
  2. Download the google-services.json file that Firebase generates.
  3. Place google-services.json inside the android/app/ directory of your React Native project.

Step 3: Add Firebase SDK to Your Android Project

Open android/build.gradle and add the Google Services classpath inside the buildscript dependencies block:

buildscript {
    dependencies {
        // Add this line
        classpath 'com.google.gms:google-services:4.3.10'
    }
}

Open android/app/build.gradle and apply the plugin at the very bottom of the file:

apply plugin: 'com.google.gms.google-services'

Still in android/app/build.gradle, add the Firebase SDK dependencies inside the dependencies block:

dependencies {
    // Add these lines
    implementation platform('com.google.firebase:firebase-bom:28.4.2')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-auth'
    implementation 'com.google.firebase:firebase-firestore'
}

The Firebase BOM (Bill of Materials) at version 28.4.2 pins compatible versions for all Firebase libraries automatically — you don't need to track individual version numbers.

Chapter 2: Installing React Native Firebase Packages

The native Android SDK is wired up; now the JavaScript layer needs its counterparts. The @react-native-firebase suite wraps the native SDKs through auto-linking.

Step 1: Install the Core Package

npm install @react-native-firebase/app

This core package is required for every Firebase service. It bootstraps the connection using google-services.json — no manual initializeApp() call is needed when this file is present.

Step 2: Install Additional Firebase Modules

Install only the modules your app actually uses. For Authentication and Firestore:

npm install @react-native-firebase/auth @react-native-firebase/firestore

Each module is a separate package that adds native bindings for that Firebase service. If you later need Cloud Storage, Cloud Messaging, or Remote Config, you install their respective @react-native-firebase/* packages the same way.

Chapter 3: Configuring Firebase in React Native

Rather than importing Firebase ad-hoc across every file, create a single firebase.js module at the project root. This becomes the single source of truth for all Firebase service instances.

firebase.js:

import firebase from '@react-native-firebase/app';
import '@react-native-firebase/auth';
import '@react-native-firebase/firestore';

// Optional: manual config override (not required when google-services.json is present)
// const firebaseConfig = {
//     apiKey: 'YOUR_API_KEY',
//     authDomain: 'YOUR_AUTH_DOMAIN',
//     projectId: 'YOUR_PROJECT_ID',
//     storageBucket: 'YOUR_STORAGE_BUCKET',
//     messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
//     appId: 'YOUR_APP_ID',
// };
// if (firebase.apps.length === 0) {
//     firebase.initializeApp(firebaseConfig);
// }

export { firebase };

Side-effect imports (import '@react-native-firebase/auth') register each service with the core firebase instance. The commented-out firebaseConfig block shows how to manually initialize Firebase — useful in environments where google-services.json is not committed to the repo (e.g., CI secrets). In standard development, the file alone is sufficient.

Chapter 4: Using Firebase Authentication

With Firebase configured, the class implemented email/password authentication — the most common starting point for user identity in mobile apps.

App.js (Authentication UI):

import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { firebase } from './firebase';

export default function App() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [user, setUser] = useState(null);

  const signIn = async () => {
    try {
      const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
      setUser(userCredential.user);
    } catch (error) {
      console.error(error);
    }
  };

  const signOut = async () => {
    try {
      await firebase.auth().signOut();
      setUser(null);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View style={styles.container}>
      {user ? (
        <View>
          <Text>Welcome, {user.email}</Text>
          <Button title="Sign Out" onPress={signOut} />
        </View>
      ) : (
        <View>
          <TextInput
            style={styles.input}
            placeholder="Email"
            value={email}
            onChangeText={setEmail}
          />
          <TextInput
            style={styles.input}
            placeholder="Password"
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />
          <Button title="Sign In" onPress={signIn} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 16,
  },
  input: {
    width: '100%',
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 12,
    paddingHorizontal: 8,
  },
});

Key points:

  • signInWithEmailAndPassword(email, password) returns a UserCredential; the .user property holds the authenticated user object including .uid and .email.
  • signOut() clears the Firebase auth session; resetting local user state drives the UI back to the login form.
  • secureTextEntry on the password field masks input — always include this for credential fields.

Chapter 5: Using Firestore

Authentication gives you an identity; Firestore gives you a place to store everything about that identity. The class updated the app to save and retrieve a user document on every sign-in.

Step 1: Export the Firestore Instance

Update firebase.js to export a firestore instance alongside the core firebase export:

firebase.js:

import firebase from '@react-native-firebase/app';
import '@react-native-firebase/auth';
import '@react-native-firebase/firestore';

export const firestore = firebase.firestore();
export { firebase };

Step 2: Integrate Firestore into Authentication Flow

Update App.js to read from (or create) a Firestore document whenever a user signs in successfully:

App.js:

import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { firebase, firestore } from './firebase';

export default function App() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [user, setUser] = useState(null);
  const [userData, setUserData] = useState({});

  useEffect(() => {
    if (user) {
      const userDoc = firestore().collection('users').doc(user.uid);
      userDoc.get().then((doc) => {
        if (doc.exists) {
          setUserData(doc.data());
        } else {
          userDoc.set({ email: user.email });
          setUserData({ email: user.email });
        }
      });
    }
  }, [user]);

  const signIn = async () => {
    try {
      const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
      setUser(userCredential.user);
    } catch (error) {
      console.error(error);
    }
  };

  const signOut = async () => {
    try {
      await firebase.auth().signOut();
      setUser(null);
      setUserData({});
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View style={styles.container}>
      {user ? (
        <View>
          <Text>Welcome, {userData.email}</Text>
          <Button title="Sign Out" onPress={signOut} />
        </View>
      ) : (
        <View>
          <TextInput
            style={styles.input}
            placeholder="Email"
            value={email}
            onChangeText={setEmail}
          />
          <TextInput
            style={styles.input}
            placeholder="Password"
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />
          <Button title="Sign In" onPress={signIn} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 16,
  },
  input: {
    width: '100%',
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 12,
    paddingHorizontal: 8,
  },
});

Firestore concepts at work here:

  • firestore().collection('users') — references the users top-level collection.
  • .doc(user.uid) — each user gets a document keyed by their Firebase Auth UID, making lookups O(1) and globally unique.
  • doc.get() — fetches the document once (not a real-time listener). doc.exists tells you whether the document was previously created.
  • userDoc.set({ email: user.email }) — creates the document on first login, bootstrapping the user record.
  • On sign-out, both user and userData are reset to avoid stale data leaking into the next session.
sequenceDiagram
    participant U as User
    participant App as React Native App
    participant Auth as Firebase Auth
    participant FS as Firestore

    U->>App: Enter email + password, tap Sign In
    App->>Auth: signInWithEmailAndPassword()
    Auth-->>App: UserCredential { user }
    App->>App: setUser(userCredential.user)
    App->>FS: collection('users').doc(uid).get()
    alt Document exists
        FS-->>App: doc.data()
        App->>App: setUserData(doc.data())
    else First login
        App->>FS: userDoc.set({ email })
        App->>App: setUserData({ email })
    end
    App->>U: Render "Welcome, email"

🧪 Try It Yourself

Task: Extend the Firestore integration to let signed-in users save a display name.

Add a displayName field to the Firestore document on first login and render it instead of the email address.

Starter snippet — replace the userDoc.set call in the useEffect:

// In the useEffect, replace the else branch:
} else {
  const name = user.email.split('@')[0]; // derive a default display name
  userDoc.set({ email: user.email, displayName: name });
  setUserData({ email: user.email, displayName: name });
}

Then update the welcome text:

<Text>Welcome, {userData.displayName || userData.email}</Text>

Success criterion: Sign in for the first time with a new account. The welcome screen should show the portion of the email address before @ as the display name. Open the Firebase Console → Firestore → users collection to confirm the document was written with both email and displayName fields.

🔍 Checkpoint Quiz

Q1. Why is google-services.json placed in android/app/ rather than the project root, and what does @react-native-firebase/app use it for?

Q2. Given the following snippet, what happens if a user signs in for the very first time?

const userDoc = firestore().collection('users').doc(user.uid);
userDoc.get().then((doc) => {
  if (doc.exists) {
    setUserData(doc.data());
  } else {
    userDoc.set({ email: user.email });
    setUserData({ email: user.email });
  }
});

A) Nothing — doc.exists is always true after sign-in
B) The app crashes because doc.data() is called on a non-existent document
C) A new Firestore document is created with the user's email, and local state is seeded with it
D) The existing document is overwritten with the user's email

Q3. What is the purpose of the Firebase BOM (firebase-bom) declared in android/app/build.gradle?

A) It replaces google-services.json
B) It ensures all Firebase Android libraries use mutually compatible versions
C) It enables Firestore offline persistence
D) It registers the app in the Firebase Console automatically

Q4. How would you modify signOut to also clear the Firestore userData from state, and why is this important?

A1. The Android build system (Gradle) reads google-services.json from android/app/ during compilation to generate google-services.xml resource files that the native Firebase SDK uses at runtime. @react-native-firebase/app reads the compiled native configuration — so the file must be in the location Gradle expects, not the JS root.

A2. Cdoc.exists is false for a brand-new user, so the else branch runs: userDoc.set() writes the document to Firestore and setUserData() seeds React state with { email: user.email }.

A3. B — The Firebase BOM pins a set of mutually compatible versions across all Firebase Android libraries. You declare the BOM version once and omit individual version numbers from each implementation line, preventing version-conflict bugs.

A4. Add setUserData({}) inside the signOut async function after setUser(null). Without it, if the component re-renders before unmounting (e.g., the user logs into a different account), stale userData from the previous session could flash on screen before the new Firestore fetch completes.

🪞 Recap

  • Firebase is integrated into React Native Android by placing google-services.json in android/app/ and applying the com.google.gms.google-services Gradle plugin.
  • The @react-native-firebase/app core package plus individual service packages (auth, firestore) provide the JavaScript interface to Firebase services.
  • A dedicated firebase.js module centralizes service initialization and exports reusable instances.
  • signInWithEmailAndPassword and signOut from firebase.auth() handle the full authentication lifecycle.
  • Firestore documents are keyed by user.uid for O(1) per-user lookups; doc.exists guards against overwriting existing records on repeat logins.

📚 Further Reading

Like this topic? It’s one of 15 in React Native.

Block your seat for ₹2,500 and join the next cohort.