Topic 53 of 56 · Full Stack Advanced

Topic 12 : Firebase

Lesson TL;DRTopic 12: Firebase 📖 9 min read · 🎯 advanced · 🧭 Prerequisites: actionsandreducers, redux Why this matters Here's the thing — every app idea you've had probably involves users logging in, saving da...
9 min read·advanced·firebase · react-native · authentication · firestore

Topic 12: Firebase

📖 9 min read · 🎯 advanced · 🧭 Prerequisites: actions-and-reducers, redux

Why this matters

Here's the thing — every app idea you've had probably involves users logging in, saving data, maybe syncing in real time. But building all that from scratch means setting up servers, writing auth logic, managing databases. That's weeks of work before your app even does anything useful. Firebase hands you all of it — authentication, a real-time database, file storage — ready to plug in. Today we're connecting Firebase to React Native, end to end, so you can build apps that actually store and sync data without touching a single server.

What You'll Learn

  • Create a Firebase project and register an Android app using google-services.json
  • Add the Firebase SDK to the Android Gradle build files
  • Install and configure @react-native-firebase/app, /auth, and /firestore
  • Implement email/password sign-in and sign-out with Firebase Authentication
  • Read and write user documents to a Firestore collection

The Analogy

Think of Firebase as a fully staffed hotel concierge floor. You check in at the front desk (Authentication), drop your bags in a room that gets a unique key (Firestore document ID), and the staff tracks everything in a live ledger that updates the moment anyone changes a booking (real-time sync). You never see the plumbing behind the walls — the boilers, the switchboards, the filing cabinets — you just use the services. Firebase is that concierge floor for your app: a complete suite of backend services you consume through an SDK, with no servers to provision or patch yourself.

Chapter 1: Setting Up Firebase

the trainer started at the foundation — the Firebase Console — before touching a single line of JavaScript.

Step 1: Create a Firebase Project

  1. Go to the Firebase Console.
  2. Click Add project and follow the wizard to name and configure your project.
  3. Once the project is 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 and place it in the android/app/ directory of your React Native project.

This JSON file is how the Android native layer authenticates with Firebase — it contains your project's API keys, project ID, and app identifiers. Never commit it to a public repository.

Step 3: Add the Firebase SDK to Your Android Build

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

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 bottom of the file:

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

Then add the Firebase SDK dependencies inside the dependencies block of android/app/build.gradle:

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 Bill of Materials (firebase-bom) pins all Firebase library versions together so they stay compatible with each other automatically.

Chapter 2: Installing React Native Firebase Packages

With the native Android layer configured, the class moved to the JavaScript side. @react-native-firebase is a collection of modules that bridge the native Firebase SDK to React Native.

Step 1: Install the Core App Package

npm install @react-native-firebase/app

This core package is required by every other Firebase module — it initialises the native Firebase SDK on app start.

Step 2: Install Additional Firebase Modules

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

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

Each module is its own npm package and its own native dependency, so installing only what you need keeps the binary size down.

After installation, run a pod install for iOS (if targeting iOS as well):

cd ios && pod install && cd ..

Chapter 3: Configuring Firebase in React Native

Create a dedicated firebase.js file at the root of your project. This file imports the modules, activates them by side-effect, and re-exports the firebase instance so the rest of the app has a single import point.

firebase.js:

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

// Optional: Explicit config (not required when using google-services.json)
// 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 };

When google-services.json is present, @react-native-firebase/app reads it automatically — no manual initializeApp call is needed. The commented-out block shows how you would override configuration if you needed to target a different Firebase environment (e.g., a staging project).

Chapter 4: Using Firebase Authentication

the trainer pulled up the auth flow diagram on the class's screen. "We need two paths," he said. "One for signed-in users, one for guests."

Step 1: Create the Authentication UI

The App.js below uses firebase.auth().signInWithEmailAndPassword to authenticate and firebase.auth().signOut to terminate the session. State is kept locally — no Redux needed for this single-screen example.

App.js:

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,
  },
});

signInWithEmailAndPassword returns a UserCredential. The .user object contains the UID, email, display name, and other profile fields that Firebase knows about. On failure it throws an error with a code property (e.g., auth/wrong-password) that you can surface to the user.

Chapter 5: Using Firestore

Authentication tells Firebase who the user is. Firestore is where you store what the user has done. The class wired them together so that every sign-in also fetches (or creates) a user document.

Step 1: Export the Firestore Instance from firebase.js

Update firebase.js to export a firestore reference alongside firebase:

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: Update App.js to Read and Write Firestore on Sign-In

The updated App.js uses a useEffect that fires whenever user changes. If the user's document already exists in the users collection it reads it; otherwise it creates it with the user's email address.

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,
  },
});

Key Firestore operations used here:

OperationMethodWhat it does
Read one documentdoc.get()Returns a DocumentSnapshot; check .exists before calling .data()
Create / overwritedoc.set({ ... })Writes the entire document; creates it if absent
Update fieldsdoc.update({ ... })Merges fields; throws if document doesn't exist
Deletedoc.delete()Removes the document
sequenceDiagram
    participant App
    participant FirebaseAuth
    participant Firestore

    App->>FirebaseAuth: signInWithEmailAndPassword(email, password)
    FirebaseAuth-->>App: UserCredential { user }
    App->>Firestore: collection('users').doc(user.uid).get()
    alt Document exists
        Firestore-->>App: DocumentSnapshot (existing data)
        App->>App: setUserData(doc.data())
    else New user
        Firestore-->>App: DocumentSnapshot (not exists)
        App->>Firestore: doc.set({ email: user.email })
        App->>App: setUserData({ email })
    end

🧪 Try It Yourself

Task: Extend the sign-in flow to let signed-in users update a displayName field in their Firestore document.

  1. Add a TextInput for displayName (only visible when user is not null).
  2. Add a Save Name button that calls firestore.collection('users').doc(user.uid).update({ displayName }).
  3. Read userData.displayName and render it next to the welcome message.

Starter snippet:

const saveName = async () => {
  try {
    await firestore
      .collection('users')
      .doc(user.uid)
      .update({ displayName });
    setUserData((prev) => ({ ...prev, displayName }));
  } catch (error) {
    console.error(error);
  }
};

Success criterion: After tapping Save Name, reload the app, sign back in, and confirm your display name appears in the welcome message — confirming it was persisted to Firestore, not just local state.

🔍 Checkpoint Quiz

Q1. Why does @react-native-firebase/app not require a manual firebase.initializeApp() call when the google-services.json file is present?

A) The google-services.json is read automatically by the native Android layer at build time, and @react-native-firebase picks up the initialised native SDK
B) Firebase initialises itself over the network on first request
C) React Native reads all JSON files in the project root on startup
D) The Gradle plugin replaces initializeApp at compile time

Q2. Given this snippet, what happens if the user signs in for the very first time (no existing Firestore document)?

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) An error is thrown because the document doesn't exist
B) doc.data() returns null and userData is set to null
C) A new document is created in the users collection with the user's email, and userData is set to { email: user.email }
D) Nothing happens — .get() only resolves if the document exists

Q3. What is the difference between doc.set({ field: value }) and doc.update({ field: value }) in Firestore?

A) set requires all fields; update only accepts one field at a time
B) set overwrites the entire document (or creates it); update merges the specified fields and throws if the document doesn't exist
C) set is synchronous; update is asynchronous
D) There is no difference — they are aliases

Q4. You want to listen for real-time changes to a user's Firestore document and update UI automatically whenever the data changes server-side. Which method would you use instead of .get()?

A) doc.fetch()
B) doc.watch(callback)
C) doc.onSnapshot(callback)
D) doc.subscribe(callback)

A1. A — When google-services.json is placed in android/app/ and the Google Services Gradle plugin is applied, the Firebase Android SDK is initialised natively before JavaScript even loads. @react-native-firebase bridges to that already-running native instance.

A2. C — doc.exists is false for a new user, so the else branch runs: userDoc.set(...) creates the document, and setUserData updates local state to match.

A3. B — set is a full overwrite (creates the document if absent); update performs a partial merge but throws NOT_FOUND if the document doesn't already exist. Use set({ ... }, { merge: true }) if you want creation-safe partial writes.

A4. C — doc.onSnapshot(callback) attaches a real-time listener that fires immediately with the current data and again on every subsequent change. Remember to call the returned unsubscribe function in a useEffect cleanup to avoid memory leaks.

🪞 Recap

  • A Firebase project is registered per platform; Android requires google-services.json in android/app/ and the Google Services Gradle plugin.
  • @react-native-firebase/app is the mandatory core; additional services (/auth, /firestore) are separate npm packages installed on demand.
  • firebase.js serves as a single initialisation and re-export point, keeping Firebase imports consistent across the app.
  • firebase.auth().signInWithEmailAndPassword returns a UserCredential whose .user.uid is the stable identifier used as the Firestore document key.
  • Firestore's doc.get() is a one-time read; doc.onSnapshot() is the real-time alternative for live-updating UIs.

📚 Further Reading

Like this topic? It’s one of 56 in Full Stack Advanced.

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