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
- Go to the Firebase Console.
- Click Add project and follow the wizard to name and configure your project.
- Once the project is created, click Add app and select the Android icon.
Step 2: Register Your App
- Enter your app's package name (e.g.,
com.yourappname) and click Register app. - Download the
google-services.jsonfile that Firebase generates and place it in theandroid/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:
| Operation | Method | What it does |
|---|---|---|
| Read one document | doc.get() | Returns a DocumentSnapshot; check .exists before calling .data() |
| Create / overwrite | doc.set({ ... }) | Writes the entire document; creates it if absent |
| Update fields | doc.update({ ... }) | Merges fields; throws if document doesn't exist |
| Delete | doc.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.
- Add a
TextInputfordisplayName(only visible whenuseris not null). - Add a Save Name button that calls
firestore.collection('users').doc(user.uid).update({ displayName }). - Read
userData.displayNameand 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.jsoninandroid/app/and the Google Services Gradle plugin. @react-native-firebase/appis the mandatory core; additional services (/auth,/firestore) are separate npm packages installed on demand.firebase.jsserves as a single initialisation and re-export point, keeping Firebase imports consistent across the app.firebase.auth().signInWithEmailAndPasswordreturns aUserCredentialwhose.user.uidis 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
- React Native Firebase documentation — the authoritative reference for every
@react-native-firebasemodule - Firebase Authentication docs — all supported sign-in providers and security rules
- Cloud Firestore data model — collections, documents, subcollections, and query limits explained
- google-services.json reference — what each field means and how the Gradle plugin uses it
- ⬅️ Previous: Redux
- ➡️ Next: Networking