Firebase

Intermediate April 2025 Mohsen Mashkour
Firebase

Introduction

Firebase is Google's comprehensive mobile and web application development platform that provides a wide range of services to help developers build, improve, and grow their apps. From authentication to real-time database, cloud storage to analytics, Firebase offers everything you need to create powerful Android applications.

This comprehensive guide will walk you through integrating Firebase into your Android app, covering authentication, real-time database, cloud storage, and more. Whether you're building a simple app or a complex application, Firebase can significantly accelerate your development process.

Why Use Firebase?

Before diving into implementation, let's understand the benefits of using Firebase:

  • Backend as a Service: No server management required
  • Real-time Database: Automatic data synchronization
  • Authentication: Built-in user management
  • Cloud Storage: Secure file storage
  • Analytics: User behavior insights
  • Crashlytics: Error monitoring and reporting
  • Cloud Functions: Serverless backend logic
  • Hosting: Web app hosting

Setting Up Firebase

Let's start by setting up Firebase in your Android project.

Step 1: Create Firebase Project
  1. Go to the Firebase Console
  2. Click "Create a project" or "Add project"
  3. Enter your project name
  4. Choose whether to enable Google Analytics (recommended)
  5. Click "Create project"
Step 2: Add Android App
  1. In your Firebase project, click the Android icon
  2. Enter your app's package name (e.g., com.example.myapp)
  3. Enter app nickname (optional)
  4. Click "Register app"
  5. Download the google-services.json file
  6. Place the file in your app module directory
Step 3: Configure Gradle
// Project-level build.gradle.kts
buildscript {
    dependencies {
        classpath("com.google.gms:google-services:4.4.0")
    }
}

// App-level build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.gms.google-services")
}

dependencies {
    // Firebase BoM
    implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
    
    // Firebase services
    implementation("com.google.firebase:firebase-analytics")
    implementation("com.google.firebase:firebase-auth")
    implementation("com.google.firebase:firebase-firestore")
    implementation("com.google.firebase:firebase-storage")
    implementation("com.google.firebase:firebase-messaging")
}

Firebase Authentication

Firebase Authentication provides ready-to-use UI libraries and SDKs to authenticate users to your app.

Email/Password Authentication
class AuthActivity : AppCompatActivity() {
    private lateinit var auth: FirebaseAuth
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_auth)
        
        auth = FirebaseAuth.getInstance()
    }
    
    private fun signUp(email: String, password: String) {
        auth.createUserWithEmailAndPassword(email, password)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign up success
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    // Sign up failed
                    val exception = task.exception
                    Log.w("Auth", "createUserWithEmail:failure", exception)
                    updateUI(null)
                }
            }
    }
    
    private fun signIn(email: String, password: String) {
        auth.signInWithEmailAndPassword(email, password)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    // Sign in failed
                    val exception = task.exception
                    Log.w("Auth", "signInWithEmail:failure", exception)
                    updateUI(null)
                }
            }
    }
    
    private fun updateUI(user: FirebaseUser?) {
        if (user != null) {
            // User is signed in
            startActivity(Intent(this, MainActivity::class.java))
            finish()
        } else {
            // User is signed out
            Toast.makeText(this, "Authentication failed", Toast.LENGTH_SHORT).show()
        }
    }
}
Google Sign-In
class GoogleSignInActivity : AppCompatActivity() {
    private lateinit var auth: FirebaseAuth
    private lateinit var googleSignInClient: GoogleSignInClient
    
    companion object {
        private const val RC_SIGN_IN = 9001
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        auth = FirebaseAuth.getInstance()
        
        // Configure Google Sign In
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build()
        
        googleSignInClient = GoogleSignIn.getClient(this, gso)
    }
    
    private fun signIn() {
        val signInIntent = googleSignInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        
        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                val account = task.getResult(ApiException::class.java)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                Log.w("GoogleSignIn", "Google sign in failed", e)
            }
        }
    }
    
    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        auth.signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    Log.w("GoogleSignIn", "signInWithCredential:failure", task.exception)
                    updateUI(null)
                }
            }
    }
}

Firebase Firestore

Firestore is a NoSQL document database that lets you easily store, sync, and query data for your mobile and web apps at global scale.

Basic Firestore Operations
class FirestoreExample {
    private val db = FirebaseFirestore.getInstance()
    
    // Add document
    fun addUser(user: User) {
        db.collection("users")
            .add(user)
            .addOnSuccessListener { documentReference ->
                Log.d("Firestore", "Document added with ID: ${documentReference.id}")
            }
            .addOnFailureListener { e ->
                Log.w("Firestore", "Error adding document", e)
            }
    }
    
    // Get document
    fun getUser(userId: String) {
        db.collection("users").document(userId)
            .get()
            .addOnSuccessListener { document ->
                if (document != null && document.exists()) {
                    val user = document.toObject(User::class.java)
                    Log.d("Firestore", "User: $user")
                } else {
                    Log.d("Firestore", "No such document")
                }
            }
            .addOnFailureListener { exception ->
                Log.d("Firestore", "get failed with ", exception)
            }
    }
    
    // Update document
    fun updateUser(userId: String, updates: Map) {
        db.collection("users").document(userId)
            .update(updates)
            .addOnSuccessListener {
                Log.d("Firestore", "Document successfully updated")
            }
            .addOnFailureListener { e ->
                Log.w("Firestore", "Error updating document", e)
            }
    }
    
    // Delete document
    fun deleteUser(userId: String) {
        db.collection("users").document(userId)
            .delete()
            .addOnSuccessListener {
                Log.d("Firestore", "Document successfully deleted")
            }
            .addOnFailureListener { e ->
                Log.w("Firestore", "Error deleting document", e)
            }
    }
}

// Data class for User
data class User(
    val name: String = "",
    val email: String = "",
    val age: Int = 0,
    val createdAt: Timestamp = Timestamp.now()
)
Real-time Listeners
class RealTimeExample {
    private val db = FirebaseFirestore.getInstance()
    
    fun listenToUsers() {
        db.collection("users")
            .addSnapshotListener { snapshot, e ->
                if (e != null) {
                    Log.w("Firestore", "Listen failed.", e)
                    return@addSnapshotListener
                }
                
                val users = mutableListOf()
                for (doc in snapshot!!) {
                    val user = doc.toObject(User::class.java)
                    user?.let { users.add(it) }
                }
                
                Log.d("Firestore", "Users: $users")
            }
    }
    
    fun listenToUser(userId: String) {
        db.collection("users").document(userId)
            .addSnapshotListener { snapshot, e ->
                if (e != null) {
                    Log.w("Firestore", "Listen failed.", e)
                    return@addSnapshotListener
                }
                
                if (snapshot != null && snapshot.exists()) {
                    val user = snapshot.toObject(User::class.java)
                    Log.d("Firestore", "User: $user")
                } else {
                    Log.d("Firestore", "Current data: null")
                }
            }
    }
}
Queries
class QueryExample {
    private val db = FirebaseFirestore.getInstance()
    
    // Simple query
    fun getUsersByName(name: String) {
        db.collection("users")
            .whereEqualTo("name", name)
            .get()
            .addOnSuccessListener { documents ->
                for (document in documents) {
                    Log.d("Firestore", "${document.id} => ${document.data}")
                }
            }
    }
    
    // Complex query
    fun getUsersByAgeRange(minAge: Int, maxAge: Int) {
        db.collection("users")
            .whereGreaterThanOrEqualTo("age", minAge)
            .whereLessThanOrEqualTo("age", maxAge)
            .orderBy("age", Query.Direction.ASCENDING)
            .limit(10)
            .get()
            .addOnSuccessListener { documents ->
                for (document in documents) {
                    Log.d("Firestore", "${document.id} => ${document.data}")
                }
            }
    }
    
    // Compound queries
    fun getActiveUsers() {
        db.collection("users")
            .whereEqualTo("isActive", true)
            .whereGreaterThan("lastLogin", Timestamp.now().toDate().time - 24*60*60*1000)
            .get()
            .addOnSuccessListener { documents ->
                for (document in documents) {
                    Log.d("Firestore", "${document.id} => ${document.data}")
                }
            }
    }
}

Firebase Storage

Firebase Storage provides secure file uploads and downloads for your Firebase apps.

Upload Files
class StorageExample {
    private val storage = FirebaseStorage.getInstance()
    private val storageRef = storage.reference
    
    fun uploadImage(imageUri: Uri, fileName: String) {
        val imageRef = storageRef.child("images/$fileName")
        
        imageRef.putFile(imageUri)
            .addOnSuccessListener { taskSnapshot ->
                // Upload successful
                val downloadUrl = taskSnapshot.storage.downloadUrl
                Log.d("Storage", "File uploaded successfully")
            }
            .addOnFailureListener { exception ->
                // Upload failed
                Log.e("Storage", "Upload failed", exception)
            }
    }
    
    fun uploadImageWithProgress(imageUri: Uri, fileName: String) {
        val imageRef = storageRef.child("images/$fileName")
        
        val uploadTask = imageRef.putFile(imageUri)
        
        uploadTask.addOnProgressListener { taskSnapshot ->
            val progress = (100.0 * taskSnapshot.bytesTransferred / taskSnapshot.totalByteCount)
            Log.d("Storage", "Upload is $progress% done")
        }.addOnPauseListener {
            Log.d("Storage", "Upload is paused")
        }.addOnCancelListener {
            Log.d("Storage", "Upload is cancelled")
        }.addOnSuccessListener {
            Log.d("Storage", "Upload is successful")
        }.addOnFailureListener {
            Log.d("Storage", "Upload failed")
        }
    }
}
Download Files
class DownloadExample {
    private val storage = FirebaseStorage.getInstance()
    
    fun downloadImage(fileName: String, context: Context) {
        val imageRef = storage.reference.child("images/$fileName")
        
        val localFile = File(context.cacheDir, fileName)
        
        imageRef.getFile(localFile)
            .addOnSuccessListener {
                Log.d("Storage", "File downloaded successfully")
                // Use the downloaded file
            }
            .addOnFailureListener { exception ->
                Log.e("Storage", "Download failed", exception)
            }
    }
    
    fun getDownloadUrl(fileName: String) {
        val imageRef = storage.reference.child("images/$fileName")
        
        imageRef.downloadUrl
            .addOnSuccessListener { uri ->
                Log.d("Storage", "Download URL: $uri")
                // Use the download URL
            }
            .addOnFailureListener { exception ->
                Log.e("Storage", "Failed to get download URL", exception)
            }
    }
}

Firebase Cloud Messaging (FCM)

Firebase Cloud Messaging is a cross-platform messaging solution that lets you reliably deliver messages at no cost.

Setting Up FCM
// Add to dependencies
implementation("com.google.firebase:firebase-messaging")

// Create FCM Service
class MyFirebaseMessagingService : FirebaseMessagingService() {
    
    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Log.d("FCM", "Refreshed token: $token")
        
        // Send this token to your server
        sendRegistrationToServer(token)
    }
    
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        
        Log.d("FCM", "From: ${remoteMessage.from}")
        
        // Check if message contains a data payload
        remoteMessage.data.isNotEmpty().let {
            Log.d("FCM", "Message data payload: ${remoteMessage.data}")
        }
        
        // Check if message contains a notification payload
        remoteMessage.notification?.let {
            Log.d("FCM", "Message Notification Body: ${it.body}")
        }
        
        // Handle the message
        handleMessage(remoteMessage)
    }
    
    private fun sendRegistrationToServer(token: String) {
        // Send token to your server
        Log.d("FCM", "Sending token to server: $token")
    }
    
    private fun handleMessage(remoteMessage: RemoteMessage) {
        // Handle the received message
        // Show notification, update UI, etc.
    }
}
Sending Notifications
class NotificationHelper(private val context: Context) {
    
    fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "default",
                "Default Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val notificationManager = context.getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }
    }
    
    fun showNotification(title: String, body: String) {
        val notificationManager = context.getSystemService(NotificationManager::class.java)
        
        val notification = NotificationCompat.Builder(context, "default")
            .setContentTitle(title)
            .setContentText(body)
            .setSmallIcon(R.drawable.ic_notification)
            .setAutoCancel(true)
            .build()
        
        notificationManager.notify(0, notification)
    }
}

Firebase Analytics

Firebase Analytics helps you understand how users interact with your app.

Basic Analytics
class AnalyticsExample {
    private val analytics = FirebaseAnalytics.getInstance(context)
    
    fun logEvent(eventName: String, parameters: Bundle?) {
        analytics.logEvent(eventName, parameters)
    }
    
    fun logUserProperty(propertyName: String, propertyValue: String) {
        analytics.setUserProperty(propertyName, propertyValue)
    }
    
    fun logScreenView(screenName: String, screenClass: String) {
        val bundle = Bundle().apply {
            putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName)
            putString(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass)
        }
        analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle)
    }
    
    fun logPurchase(currency: String, value: Double, itemId: String) {
        val bundle = Bundle().apply {
            putString(FirebaseAnalytics.Param.CURRENCY, currency)
            putDouble(FirebaseAnalytics.Param.VALUE, value)
            putString(FirebaseAnalytics.Param.ITEM_ID, itemId)
        }
        analytics.logEvent(FirebaseAnalytics.Event.PURCHASE, bundle)
    }
}

Best Practices

Security Rules
  • Firestore Security Rules: Always set up proper security rules
  • Storage Rules: Restrict file access based on user authentication
  • Authentication: Validate user permissions before data access
  • Data Validation: Validate data on both client and server
Performance
  • Offline Persistence: Enable offline data access
  • Pagination: Use limit() and startAfter() for large datasets
  • Caching: Implement proper caching strategies
  • Indexing: Create composite indexes for complex queries
Error Handling
  • Network Errors: Handle network connectivity issues
  • Authentication Errors: Handle auth state changes
  • Data Validation: Validate data before sending to Firebase
  • User Feedback: Provide clear error messages to users

Common Pitfalls

Avoiding Common Mistakes
  • Don't store sensitive data: Never store passwords or API keys in Firestore
  • Don't ignore security rules: Always implement proper security
  • Don't forget offline handling: Consider offline scenarios
  • Don't over-fetch data: Only retrieve what you need
Debugging Tips
  • Use Firebase Console: Monitor your app in real-time
  • Enable Debug Logging: Use Firebase's debug features
  • Test Security Rules: Use the Firebase Emulator Suite
  • Monitor Performance: Use Firebase Performance Monitoring

Practice Exercises

Try these exercises to reinforce your Firebase knowledge:

Exercise 1: User Management App
// Create an app with:
// - User registration and login
// - User profile management
// - Profile picture upload
// - Real-time user status updates
Exercise 2: Chat Application
// Build a chat app with:
// - Real-time messaging
// - User authentication
// - Message persistence
// - Push notifications
Exercise 3: E-commerce App
// Create an e-commerce app with:
// - Product catalog
// - Shopping cart
// - User orders
// - Payment integration

Next Steps

Now that you have a solid foundation in Firebase, explore these advanced topics:

  • Firebase Functions: Serverless backend logic
  • Firebase Hosting: Web app hosting
  • Firebase ML Kit: Machine learning features
  • Firebase Performance: App performance monitoring
  • Firebase Test Lab: Automated testing
  • Firebase Extensions: Pre-built solutions

Resources

Summary

Firebase provides a comprehensive suite of tools that can significantly accelerate your Android development process. From authentication to real-time databases, cloud storage to analytics, Firebase offers everything you need to build powerful, scalable applications.

You've learned the fundamentals of Firebase integration, including authentication, Firestore database operations, cloud storage, and push notifications. The key to mastering Firebase is practice - build real applications, experiment with different services, and stay updated with the latest features.

Remember that Firebase is constantly evolving with new features and improvements. Keep exploring the documentation, join the Firebase community, and don't hesitate to experiment with new capabilities as they become available.