Files
crowd-funder-for-time-pwa/doc/android-firebase-gms.md
T
Jose Olarte III a271cd1434 Enable Firebase by default for Android and iOS builds so normal local, test, and production builds no longer depend on passing -PfirebaseEnabled.
Opt out with -PfirebaseEnabled=false or FIREBASE_ENABLED=false. A missing google-services.json or GoogleService-Info.plist now fails the build instead of silently skipping Firebase.
2026-10-12 10:20:39 -04:00

6.8 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 is on by default for local, test, and production builds. GMS-less devices still run the app; FCM degrades when Play Services is absent. F-Droid and other builds that must omit Firebase opt out explicitly.

How the switch works

android/app/build.gradle applies com.google.gms.google-services unless firebaseEnabled is false. android/gradle.properties sets firebaseEnabled=true, so Android Studio, ./gradlew, and scripts/build-android.sh all pick it up. -PfirebaseEnabled and -PfirebaseEnabled=true stay on. The config file does not decide the switch.

When Firebase is on, android/app/google-services.json must exist, be non-empty, and contain mobilesdk_app_id. Otherwise configuration fails with an error that names that path. When Firebase is off, the file is ignored even if it is on disk.

scripts/build-android.sh forwards an opt-out: FIREBASE_ENABLED=false adds -PfirebaseEnabled=false to its Gradle commands. npm scripts do not pass -PfirebaseEnabled; they rely on the default.

iOS uses the same default. ios/App/scripts/bundle-firebase-config.sh copies ios/App/App/GoogleService-Info.plist into the app and the share extension. scripts/build-ios.sh runs it with --check before a device build, and both Xcode targets run it as a build phase. FIREBASE_ENABLED=false skips the plist. A missing or empty plist fails the build when Firebase is on.

Build commands

Target Command Firebase
Local / test / production APK or AAB ./gradlew assembleRelease or bundleRelease on
Play Store with FCM ./gradlew bundleRelease on
iOS (Xcode or scripts/build-ios.sh) default on
F-Droid / FOSS Android ./gradlew assembleRelease -PfirebaseEnabled=false off
iOS without Firebase FIREBASE_ENABLED=false off

Behavior on non-GMS devices

When Firebase is enabled (the default), 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, Firebase on by default) 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 default Play Store AAB (Firebase on) is the one to upload.

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 -PfirebaseEnabled=false build and a dedicated F-Droid listing.

Push registration in builds without Firebase

A build with -PfirebaseEnabled=false 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)
  • Required for the default (Firebase on) build. Place it at android/app/google-services.json
  • Ignored when you pass -PfirebaseEnabled=false; leaving it on disk does not re-enable Firebase
  • 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.