Files
crowd-funder-for-time-pwa/doc/android-firebase-gms.md
T

5.9 KiB

Android — Firebase, Google Play Services, and FOSS Distribution

Overview

The app is designed to work on Android devices with and without Google Play Services (GMS). Firebase/FCM push notifications are an opt-in feature at build time; all other functionality works on GMS-less devices (F-Droid, LineageOS without OpenGApps, etc.).

How the opt-in guard works

android/app/build.gradle applies the com.google.gms.google-services Gradle plugin only when both conditions are true:

  1. android/google-services.json is present on disk
  2. The Gradle property firebaseEnabled is explicitly passed
if (servicesJSON.exists() && servicesJSON.text && project.hasProperty('firebaseEnabled')) {
    apply plugin: 'com.google.gms.google-services'
}

This means the file can live on disk for development purposes without accidentally activating Firebase.

Build commands

Target Command Firebase
APK / sideload / Zapstore ./gradlew assembleRelease off
Aurora / Play Store without FCM ./gradlew bundleRelease off
Play Store with FCM push notifications ./gradlew bundleRelease -PfirebaseEnabled on
F-Droid ./gradlew assembleRelease off (required)

Behavior on non-GMS devices

When built with -PfirebaseEnabled, Firebase SDKs check for GMS availability at startup and degrade gracefully if it is absent:

  • Firebase initializes but detects no GMS
  • FCM skips token registration silently (no token, no notifications)
  • The app continues to work normally

This means a single Play Store AAB (bundleRelease -PfirebaseEnabled) covers both GMS and non-GMS users. GMS users get push notifications; non-GMS users get a fully functional app without them.

Aurora Store pulls the exact APK from Play Store servers, so Aurora users get whichever variant was uploaded. The Play Store AAB built with -PfirebaseEnabled is correct for Aurora.

F-Droid is stricter: their build policy rejects any APK with GMS dependencies at the binary level, even with graceful degradation. F-Droid submission would require a separate assembleRelease build (no flag) and a dedicated F-Droid listing.

Push registration in builds without Firebase

A build without -PfirebaseEnabled contains the @capacitor/push-notifications plugin, but no Firebase config. In that build, PushNotifications.register() throws Default FirebaseApp is not initialized on the Capacitor plugin thread, which kills the app; a JS try/catch cannot intercept it.

The JS therefore asks native code first. NotificationInspector.isFirebaseConfigured() (android/app/src/main/java/app/timesafari/notifications/NotificationInspectorPlugin.java) reports whether the google_app_id string resource exists; the google-services Gradle plugin generates that resource only when Firebase is enabled, and it is the same signal Firebase's auto-initialization uses. firebaseMessagingClient.ts skips register() on Android when it is absent (or when the check fails), logging No Firebase config in this Android build; skipping push registration. Listener setup and the notification permission request run either way, since local daily notifications need that permission.

NotificationInspector must be listed in scripts/restore-local-plugins.js: MainActivity registers plugins after the Capacitor bridge is built, so a plugin missing from capacitor.plugins.json reports "not implemented on android" and the check falls back to skipping push.

Any code that calls PushNotifications.register() or other FirebaseMessaging APIs on Android must go through the same check.

The google-services.json file

  • Gitignored (android/.gitignore line 80) — never commit it
  • Contains Firebase project credentials (project number, app ID, API key)
  • Safe to leave on disk; has no effect unless -PfirebaseEnabled is passed
  • Obtain from the Firebase console: Project Settings → Your apps → Android app → Download google-services.json

Known GMS dependency: MLKit barcode scanner

@capacitor-mlkit/barcode-scanning unconditionally depends on com.google.android.gms:play-services-code-scanner (present since the plugin was first added at v6.0.0). This merges com.google.android.gms.version and GoogleApiActivity into the APK manifest regardless of the -PfirebaseEnabled flag.

Practical impact:

  • GMS devices: barcode scanning works normally
  • Non-GMS devices: barcode scanning fails at scan time (not at startup); the app launches and runs normally otherwise

This is an accepted trade-off. Removing it would require either forking the plugin or introducing a foss product flavor that excludes the MLKit plugin entirely — work to undertake if/when F-Droid submission is planned.

Incident: June 2026

Jose Olarte III's notify-api branch placed a production google-services.json in android/ to test Firebase Cloud Messaging. The branch was never merged to master, but because the file is gitignored it persisted on disk after switching branches. At the time, the Gradle conditional activated Firebase based on file presence alone (no opt-in flag), so all subsequent local builds embedded Firebase and required Google Play Services. This silently broke APK/Aurora/Zapstore distribution.

Fix applied: deleted google-services.json from disk, changed the Gradle conditional to require -PfirebaseEnabled, and documented the rule in AGENTS.md.

Incident: September 2026

After the June fix, local builds without -PfirebaseEnabled (including every scripts/build-android.sh build and the FOSS APKs from v1.4.4 on) crashed at launch as soon as notification permission was granted, because startup code called PushNotifications.register() unconditionally. Found while running npm run build:android:prod:run on an emulator.

Fix applied: the isFirebaseConfigured() gate described in "Push registration in builds without Firebase", plus adding NotificationInspector to scripts/restore-local-plugins.js so the check is reachable on Android.