Chapter 9: Firebase Integration
What is Firebase?
Firebase is a **BaaS (Backend-as-a-Service)** platform developed by Google. It provides a complete, cloud-hosted backend for your application, allowing you to build full-stack apps *without* writing a single line of server code yourself.
Think about a feature like "user login." To build this from scratch, you would need to:
- Set up a server (e.g., Node.js, Django).
- Create a database (e.g., MongoDB, PostgreSQL).
- Write API endpoints for `/signup`, `/login`.
- Implement secure password hashing (
bcrypt). - Create and manage user sessions with JWTs.
- Deploy and maintain this server 24/7.
With Firebase, you just add their SDK, write FirebaseAuth.getInstance().createUserWithEmail(...), and you're done. Firebase handles the server, database, and security for you.
Firebase is a *suite* of products. For this chapter, we will cover the four most essential services for Android developers:
- Firebase Authentication: For user sign-up, login, and management (Email, Google, Phone).
- Cloud Firestore: A real-time NoSQL database for storing your app's data (user profiles, posts, etc.).
- Cloud Storage for Firebase: For storing large user-generated files (profile pictures, videos, PDFs).
- Firebase Cloud Messaging (FCM): For sending push notifications to your users.
Firebase Setup: Connecting Your App
Before you can use any Firebase service, you must first create a Firebase project and connect it to your Android app.
Step 1: Create a Firebase Project
- Go to the Firebase Console.
- Log in with your Google account.
- Click **"Add project"**.
- Give your project a name (e.g., "MyTestApp").
- (Optional but Recommended) Enable Google Analytics for this project.
- Click "Create project."
Step 2: Register Your Android App
- Inside your new project's dashboard, click the **Android icon** (
</>) to add an Android app. - You will be asked for three things:
- Android package name: This is the *most important* step. This MUST be the **exact same** as the
applicationIdin yourbuild.gradle.ktsfile (e.g.,com.codewithmsmaxpro.myapp). - App nickname (Optional): A simple name, like "My Android App".
- SHA-1 certificate (Optional): You **need** this for Google Sign-In and other security features.
- Android package name: This is the *most important* step. This MUST be the **exact same** as the
How to get your SHA-1 Key (Critical Step)
Open your Android Studio project. In the Gradle tab (usually on the right), navigate to:
YourApp > Tasks > android > signingReport
Double-click signingReport. A "Run" window will open, and you will see several outputs. Find the **"debug"** variant and copy its **SHA-1** key. It looks like `A1:B2:C3:...:F9`.
Paste this SHA-1 key into the Firebase setup. If you forget, you can add it later in Project Settings.
Step 3: Download and Add `google-services.json`
- After registering, Firebase will ask you to download a config file:
google-services.json. - Download this file.
- In Android Studio, switch your Project Panel from "Android" view to **"Project"** view.
- Drag and drop the
google-services.jsonfile into your **`app`** folder (at the same level as yoursrcandbuild.gradle.ktsfiles).
- This is very important. This file contains all your project's API keys and identifiers. **Never** share this file publicly or commit it to a public GitHub repository.
Step 4: Add Firebase SDKs (Gradle Setup)
Firebase will tell you to add its SDKs to your Gradle files.
build.gradle.kts (Project-level)plugins {
// ...
// Add the Google Services plugin
alias(libs.plugins.google.gms.google.services) apply false
}
build.gradle.kts (Module-level: :app)
plugins {
// ...
// Apply the Google Services plugin
alias(libs.plugins.google.gms.google.services)
}
dependencies {
// ...
// Add the Firebase Bill of Materials (BOM)
// This manages all Firebase library versions for you
implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
// Now, you can add the specific Firebase products you need
// WITHOUT specifying a version number.
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.firebase:firebase-firestore-ktx")
}
Click **"Sync Now"**. That's it! Your app is now connected to Firebase.
Pillar 1: Firebase Authentication
Firebase Authentication provides a complete backend service to handle user sign-up and login. It supports email/password, phone numbers, and federated providers like Google, Facebook, and Twitter, all with a single SDK.
Step 1: Enable Auth Methods in Console
Before you can use any login method, you must "turn it on."
- Go to the Firebase Console.
- Open your project.
- In the left menu, go to **Build > Authentication**.
- Click the **"Get started"** button.
- Go to the **"Sign-in method"** tab.
- Click on **"Email/Password"** and **Enable** it.
- Click on **"Google"**, **Enable** it, and provide a project support email.
Step 2: Add Auth Dependency
If you haven't already, add the Auth dependency to your `build.gradle.kts` file:
implementation("com.google.firebase:firebase-auth-ktx")
Step 3: Get the `FirebaseAuth` Instance
The `FirebaseAuth` object is your single entry point for all auth operations. You can get an instance of it anywhere.
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class MyAuthManager {
private val auth: FirebaseAuth = Firebase.auth
}
Deep Dive: Email/Password Sign-Up
This function creates a new user in the Firebase backend.
AuthViewModel.ktfun signUp(email: String, password: String) {
viewModelScope.launch {
try {
val result = auth.createUserWithEmailAndPassword(email, password).await()
val firebaseUser = result.user
Log.d("Auth", "Sign up successful! User ID: ${firebaseUser?.uid}")
// Now, you should probably save this user's info to Firestore
} catch (e: FirebaseAuthUserCollisionException) {
Log.w("Auth", "Email already in use.")
} catch (e: FirebaseAuthWeakPasswordException) {
Log.w("Auth", "Password is too weak.")
} catch (e: Exception) {
Log.e("Auth", "Sign up failed: ${e.message}")
}
}
}
Note: We use .await() from the kotlinx-coroutines-play-services library to make the old "Task" API work with modern coroutines.
Deep Dive: Email/Password Sign-In
This function logs in an existing user.
AuthViewModel.ktfun signIn(email: String, password: String) {
viewModelScope.launch {
try {
val result = auth.signInWithEmailAndPassword(email, password).await()
val firebaseUser = result.user
Log.d("Auth", "Sign in successful! Welcome, ${firebaseUser?.email}")
// Navigate to home screen
} catch (e: FirebaseAuthInvalidUserException) {
Log.w("Auth", "No account found with this email.")
} catch (e: FirebaseAuthInvalidCredentialsException) {
Log.w("Auth", "Wrong password.")
} catch (e: Exception) {
Log.e("Auth", "Sign in failed: ${e.message}")
}
}
}
Observing Auth State (The "Current User")
How do you know if a user is logged in *right now*? Firebase Auth maintains the user's session for you. You can get the current user at any time.
val currentUser = auth.currentUser
if (currentUser != null) {
// User is logged in
println("Current user ID: ${currentUser.uid}")
} else {
// User is logged out
println("No user is logged in.")
}
Deep Dive: Google Sign-In
This is more complex because it involves another SDK (Google Play Services) to get the Google token, which you then "exchange" for a Firebase credential.
Step 1: Add Google Sign-In Dependency
implementation("com.google.android.gms:play-services-auth:20.7.0")
Step 2: Set up GoogleSignInClient (in your Activity)
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
lateinit var googleSignInClient: GoogleSignInClient
override fun onCreate(savedInstanceState: Bundle?) {
// ...
// 1. Configure Google Sign-In
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
// You get this from your google-services.json file
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
// 2. Build the client
googleSignInClient = GoogleSignIn.getClient(this, gso)
}
Step 3: Register an Activity Result Launcher
This is the modern way to handle the result from the Google Sign-In pop-up.
private val googleSignInLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account.idToken)
} catch (e: ApiException) {
Log.w("Auth", "Google sign in failed", e)
}
}
}
Step 4: Start the Sign-In Flow (e.g., on button click)
googleSignInButton.setOnClickListener {
val signInIntent = googleSignInClient.signInIntent
googleSignInLauncher.launch(signInIntent)
}
Step 5: Exchange the Google Token for a Firebase Credential
private fun firebaseAuthWithGoogle(idToken: String?) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
viewModelScope.launch {
try {
auth.signInWithCredential(credential).await()
Log.d("Auth", "Firebase Google Sign-In Successful")
// Navigate to home screen
} catch (e: Exception) {
Log.w("Auth", "Firebase Google Sign-In Failed", e)
}
}
}
Read More about Firebase Google Sign-In →
Pillar 2: Cloud Firestore
Firebase Authentication only confirms *who* a user is. It doesn't store any *data* about them (like their username, bio, or high score). For that, you need a database. **Cloud Firestore** is Firebase's modern, powerful, and scalable NoSQL database.
Firestore vs. Firebase Realtime Database (RTDB)
Firebase offers *two* databases, which is confusing.
- Realtime Database (RTDB): This is the *original* Firebase database. It's one giant JSON file. It's very fast, but it struggles with complex data and querying.
- Cloud Firestore (Newer): This is the *new and recommended* database. It's **document-based** (NoSQL), which is much more structured. It has more powerful querying, scales better, and is more robust.
Rule: Always use Cloud Firestore for new projects.
Firestore Data Model (Critical Concept)
Firestore is a NoSQL database. It doesn't have tables and rows. Instead, it has:
- Collection: A folder or list of documents (e.g.,
"users","posts"). - Document: A single entry in a collection. It's like a container. Each document has a unique **Document ID**.
- Fields (Data): The actual key-value data *inside* a document (e.g.,
name: "Aman",score: 100).
**Analogy:** A Collection ("users") is a filing cabinet. A Document ("user_123") is a single file folder. The Fields are the papers *inside* that folder (name: "Aman").
Important: You *cannot* have documents directly inside other documents. You can only have **Collections** inside **Documents** (called "sub-collections").
Step 1: Add Firestore Dependency
implementation("com.google.firebase:firebase-firestore-ktx")
Step 2: Get the Firestore Instance
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
val db: FirebaseFirestore = Firebase.firestore
Step 3: Writing Data (Create & Update)
To write data, you first create a Kotlin data class or a Map.
data class UserProfile(
val username: String = "",
val email: String = "",
val joinDate: Long = System.currentTimeMillis()
)
fun saveUserToFirestore() {
val userId = auth.currentUser?.uid ?: return
val email = auth.currentUser?.email ?: ""
val newUser = UserProfile(username = "MSMAXPRO", email = email)
// 1. Get a reference to the document
// This creates a path: /users/USER_ID
val userDocument = db.collection("users").document(userId)
// 2. Set the data (this will CREATE or OVERWRITE)
userDocument.set(newUser)
.addOnSuccessListener { Log.d("Firestore", "User profile saved!") }
.addOnFailureListener { e -> Log.e("Firestore", "Error saving", e) }
}
fun addPost(title: String) {
val post = mapOf("title" to title, "likes" to 0)
// .add() will auto-generate a unique document ID
db.collection("posts")
.add(post)
.addOnSuccessListener { documentReference ->
Log.d("Firestore", "Post added with ID: ${documentReference.id}")
}
}
Step 4: Reading Data (One Time)
To get data just once (e.g., loading a user's profile), you use .get().
fun fetchUserProfile() {
val userId = auth.currentUser?.uid ?: return
db.collection("users").document(userId)
.get()
.addOnSuccessListener { document ->
if (document != null && document.exists()) {
// Convert the document back into our data class
val user = document.toObject(UserProfile::class.java)
Log.d("Firestore", "Username: ${user?.username}")
} else {
Log.d("Firestore", "No such document")
}
}
.addOnFailureListener { e ->
Log.e("Firestore", "Error getting document", e)
}
}
Step 5: Reading Data (Real-time) - The Magic
This is the best part of Firestore. You can "listen" to a document or collection. When *anything* changes (even on another user's phone), Firestore will **automatically** send the new data to your app. This is perfect for a chat app or a live feed.
You use .addSnapshotListener() instead of .get().
// This listener will stay active until the Activity is destroyed
fun listenForMessages(chatRoomId: String) {
db.collection("chatRooms").document(chatRoomId)
.collection("messages")."timestamp")
.addSnapshotListener { snapshots, error ->
if (error != null) {
Log.w("Firestore", "Listen failed.", error)
return@addSnapshotListener
}
if (snapshots != null) {
val messages = snapshots.toObjects(ChatMessage::class.java)
// Update your UI (e.g., your RecyclerView or LazyColumn)
updateChatUI(messages)
}
}
}
Read More about Cloud Firestore →
Pillar 3: Cloud Storage for Firebase
Firestore is great for text, numbers, and booleans. But where do you store large files like profile pictures, videos, or PDFs? **You should never store large files in a database.**
Instead, you use **Cloud Storage**. This service is built specifically to store and serve large binary files.
The common workflow is:
- User picks an image from their phone's gallery (
Uri). - You upload this image file to Cloud Storage.
- Storage gives you back a permanent **Download URL** (a public
https://...link). - You take this URL (which is just a
String) and save it in your **Firestore** document (e.g., in the `profilePictureUrl` field of yourUser).
Step 1: Add Storage Dependency
implementation("com.google.firebase:firebase-storage-ktx")
Step 2: Set Security Rules
By default, no one can read or write to your Storage bucket. You must go to the **Firebase Console > Storage > Rules** tab and set up rules. For development, you can start with this (INSECURE):
// WARNING: Insecure, allows anyone to read/write.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
Step 3: Uploading a File
import com.google.firebase.storage.ktx.storage
import android.net.Uri
val storage = Firebase.storage
val storageRef = storage.reference
// Assume 'imageUri' is the Uri you got from the user's photo gallery
fun uploadProfilePic(userId: String, imageUri: Uri) {
// 1. Create a reference. e.g., "profile_images/USER_ID_123.jpg"
val imageRef = storageRef.child("profile_images/$userId.jpg")
// 2. Start the upload task
val uploadTask = imageRef.putFile(imageUri)
// 3. Listen for success or failure
uploadTask.addOnSuccessListener { taskSnapshot ->
Log.d("Storage", "Image uploaded!")
// 4. IMPORTANT: Get the Download URL
getDownloadUrl(imageRef)
}.addOnFailureListener { e ->
Log.e("Storage", "Upload failed", e)
}
}
fun getDownloadUrl(imageRef: StorageReference) {
imageRef.downloadUrl.addOnSuccessListener { uri ->
val imageUrl = uri.toString()
Log.d("Storage", "Download URL: $imageUrl")
// 5. Now save this URL string to your user's document in Firestore
saveUrlToFirestore(imageUrl)
}
}
Read More about Cloud Storage →
Pillar 4: Firebase Cloud Messaging (FCM)
FCM is the service that lets you send **Push Notifications** to your app, even when the app is closed. This is extremely powerful for user re-engagement (e.g., "You have a new message!" or "A new post is available").
How it Works
- When your app is installed, it registers with FCM and receives a unique **FCM Registration Token**.
- Your app sends this token to your backend (e.g., saves it to your Firestore user document).
- When you want to send a notification, your *server* (not your app) tells the FCM API: "Send this message to *this* token."
- FCM handles delivering the message to the correct device.
Step 1: Add FCM Dependency
implementation("com.google.firebase:firebase-messaging-ktx")
Step 2: Create a Messaging Service
You need to create a Service (see Chapter 4) that extends FirebaseMessagingService. This service will run in the background and "listen" for two things: new tokens and new messages.
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import android.util.Log
class MyFirebaseMessagingService : FirebaseMessagingService() {
/**
* This is called when a new token is generated.
* This happens on first app install, or if the user
* reinstalls the app or clears data.
*/
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d("FCM", "New Token Generated: $token")
// **CRITICAL:** You MUST send this token to your server!
// e.g., saveTokenToFirestore(token)
}
/**
* This is called when a message is received *while the app is in the foreground*.
* If the app is in the background, Android shows a
* system notification tray icon automatically.
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d("FCM", "Message received!")
remoteMessage.notification?.let { notification ->
Log.d("FCM", "Title: ${notification.title}")
Log.d("FCM", "Body: ${notification.body}")
// If the app is in the foreground, the system won't show it.
// So, you have to build a custom notification here.
showCustomNotification(notification.title, notification.body)
}
}
private fun showCustomNotification(title: String?, body: String?) {
// ... (Code to build and show a Notification) ...
}
}
Step 3: Register the Service in `AndroidManifest.xml`
Just like any service, you must register it.
<application ...>
<!-- ... your activities ... -->
<service
android:name=".services.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
That's it! Your app is now ready to receive notifications. You can send your first test notification from the **Firebase Console > Engage > Cloud Messaging** dashboard.
Read More about Firebase Cloud Messaging →