Firebase Integration

Android Development โ€” Production & Publishing
Course 3 ยท Chapter 8 ยท Firebase Integration

๐Ÿ”ฅ Firebase Integration

Once an app is live (last chapter), a whole category of question opens up that local testing can never answer: is it crashing on real devices? What are real users actually doing? Firebase covers exactly this โ€” crash reporting, usage analytics, remote-controlled feature flags, and push notifications โ€” closing out this course, and the Android Development series, with the tools that keep a published app healthy after release.

๐Ÿ”Œ Connecting Firebase

A Firebase project is created at the Firebase console, an Android app is registered within it, and a generated google-services.json file is dropped into the app module โ€” a Gradle plugin then reads it at build time to wire everything up:

// project-level build.gradle.kts plugins { id("com.google.gms.google-services") version "4.4.0" apply false } // app/build.gradle.kts plugins { id("com.google.gms.google-services") } dependencies { implementation(platform("com.google.firebase:firebase-bom:32.7.0")) implementation("com.google.firebase:firebase-crashlytics-ktx") implementation("com.google.firebase:firebase-analytics-ktx") implementation("com.google.firebase:firebase-config-ktx") implementation("com.google.firebase:firebase-messaging-ktx") }

The Firebase BOM (Bill of Materials, via platform(...)) pins every Firebase library to mutually-compatible versions automatically โ€” a similar dependency-management idea to how Gradle already resolves compatible versions for other libraries, just made explicit here since Firebase's own libraries need to agree with each other precisely.

๐Ÿ’ฅ Crashlytics โ€” Seeing Crashes You'll Never Reproduce Locally

Crashlytics automatically captures every fatal crash on a real user's device and reports it to the Firebase console โ€” stack trace, device model, Android version, breadcrumbs leading up to it โ€” none of which is visible from Android Studio's own Logcat once an app is out of your hands:

try { repository.refresh() } catch (e: IOException) { FirebaseCrashlytics.getInstance().recordException(e) // a NON-FATAL error, still reported }

Fatal (app-crashing) exceptions are captured automatically with zero code โ€” Crashlytics installs itself as an uncaught-exception handler. recordException(e) is for the other case: an error that was handled gracefully (per Chapter 4's approach to permission denial, or Course 2 Chapter 8's offline-first "silently keep showing cached data" pattern) but is still worth knowing about in aggregate โ€” how often does this actually happen to real users?

๐Ÿ“Š Analytics โ€” What Users Actually Do

FirebaseAnalytics.getInstance(context).logEvent("task_created") { param("has_due_date", hasDueDate) }

Logged events roll up into funnels and behavior reports in the Firebase console โ€” how many users who install the app actually create a first task, how many come back the next day, which screens see the most engagement. This is the direct feedback loop the store listing chapter's ASO work feeds into: getting users to install is one problem; understanding what they do once they're in the app is a separate, ongoing one that only real usage data (not testing) can answer.

๐ŸŽ›๏ธ Remote Config โ€” Changing Behavior Without a New Release

Remote Config lets specific values be changed from the Firebase console and fetched by already-installed apps at runtime โ€” no new build, no Play Console review, no waiting for users to update:

val remoteConfig = Firebase.remoteConfig remoteConfig.setDefaultsAsync(mapOf("show_new_feature" to false)) remoteConfig.fetchAndActivate().addOnCompleteListener { val showNewFeature = remoteConfig.getBoolean("show_new_feature") // Use showNewFeature to conditionally render a composable, e.g. Course 2's UiState pattern }

This is directly the same feature-flag idea Node.js Course 3's deployment chapter touched on for canary/gradual web rollouts โ€” the difference is that on Android, given last chapter's slow, review-gated release cycle and users who don't always update immediately, Remote Config is often the only practical way to adjust behavior quickly across an already-installed user base, rather than one option among several fast-deploy strategies a web team might have.

๐Ÿ“ฌ FCM โ€” Push Notifications From a Server

Chapter 4 covered local notifications โ€” triggered by code running inside the app itself. FCM (Firebase Cloud Messaging) is different: a message sent from a server (or the Firebase console directly) arrives on the device even when the app isn't running, and the app's own code decides how to display it:

class MyFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { val title = message.notification?.title ?: "New message" val body = message.notification?.body ?: "" // Builds and shows the notification using Chapter 4's exact same channel/Builder pattern showTaskReminder(applicationContext, "$title: $body") } override fun onNewToken(token: String) { // Send this device-specific token to your own server, so it can target this device later } }

onMessageReceived ultimately calls the same notification-building code from Chapter 4 โ€” FCM's job is only getting the message to the device reliably; actually displaying it as a notification (channel, NotificationCompat.Builder, permission check) is identical to everything already covered. onNewToken provides a unique identifier for this specific app install, which a backend needs on file to target that device individually later.

Local Notification (Chapter 4) vs FCM Push

Local NotificationFCM Push Notification
Triggered byCode running inside the appA remote server, or the Firebase console
App needs to be running?Yes โ€” it's the one showing the notificationNo โ€” arrives even if the app isn't open
Display mechanismNotificationCompat.Builder + channelSame โ€” FCM delivers the message, app code still builds the notification

๐Ÿ’ป Coding Challenges

Challenge 1: Non-Fatal Error Reporting

Take Course 2 Chapter 8's NoteRepository.refresh() (the try/catch that silently falls back to cached data on IOException) and add a FirebaseCrashlytics.getInstance().recordException(e) call inside the catch block, explaining in a comment why this doesn't change the user-facing behavior at all but is still valuable.

Goal: Practice adding non-fatal error reporting to already-existing, correctly-handled error paths.

โ†’ Solution

Challenge 2: An Analytics Event with Parameters

Add a FirebaseAnalytics logEvent call inside Course 2's TaskViewModel.addTask() function, logging an event named "task_added" with a parameter for the task title's character length (not the title itself โ€” explain in a comment why logging the raw title would be a bad idea).

Goal: Practice logging a meaningful analytics event, and reasoning about what data is appropriate to send.

โ†’ Solution

Challenge 3: A Remote Config Feature Flag

Write a function fun isNewDashboardEnabled(): Boolean using Firebase Remote Config with a default of false, fetched via fetchAndActivate(). Sketch (in a comment) how a composable would use this to conditionally show an old vs new dashboard screen, and explain what advantage this has over shipping the new dashboard directly in a release build.

Goal: Practice the Remote Config fetch pattern and articulate its real-world advantage over Chapter 7's release process.

โ†’ Solution

๐Ÿ’ก Course 3 Complete โ€” And the Android Development Series

Compose animations and theming, testing, background work, permissions, security, performance, publishing, and now post-launch observability โ€” Production & Publishing closes out a 24-chapter arc spanning Fundamentals, Architecture & Data, and this course, going from a first "Hello World" Activity to a signed, published, monitored app on real devices. Every architectural decision made early (Course 1's Activity lifecycle discipline, Course 2's testable repository interfaces, this course's dispatcher hygiene) is exactly what made the later, more production-focused chapters โ€” testing, ANR prevention, non-fatal error reporting โ€” straightforward rather than a fight against the codebase's own structure.

๐ŸŽฏ What's Next

Android Development โ€” Production & Publishing (Course 3) is complete, and with it, the full Android Development series (Fundamentals โ†’ Architecture & Data โ†’ Production & Publishing).