Capacitor Android crash: FirebaseApp not initialized when push notifications register() called without google-services.json
Capacitor Android app crashes fatally (UncaughtExceptionHandler, app killed) when JS calls PushNotifications.register() on a build that shipped without google-services.json. In Sentry the crash arrives as a 3-deep chain — RuntimeException -> java.lang.reflect.InvocationTargetException -> IllegalStateException: 'Default FirebaseApp is not initialized in this process <package>. Make sure to call FirebaseApp.initializeApp(Context) first.' — and the outer two values carry only framework frames (Bridge.lambda$callPluginMethod$0, PluginHandle.invoke), so the issue title reads as an anonymous bridge crash. Root cause: the stock Capacitor Android template applies the google-services Gradle plugin conditionally on the file's presence, so a build without the file compiles and runs fine until the first register() call, then dies: PushNotificationsPlugin.register -> FirebaseMessaging.getInstance() -> FirebaseApp.getInstance() throws. Nothing on the JS side detects it: Capacitor.isPluginAvailable('PushNotifications') returns true because the plugin IS available; Firebase is not.
Two independent fixes; ship both.
Land the Firebase config so the google-services plugin actually applies: put google-services.json in android/app/ and un-gitignore it (the template's .gitignore often excludes it; it contains only project identifiers, no secrets).
Guard the JS call site, because a build variant without the file can always reappear. Capacitor.isPluginAvailable is NOT the right check. Detect Firebase from native via reflection in a tiny plugin method, since the JS layer cannot see it:
try {
Class<?> fbApp = Class.forName("com.google.firebase.FirebaseApp");
fbApp.getMethod("getInstance").invoke(null); // throws IllegalStateException when uninitialized
available = true;
} catch (Exception e) { available = false; }Gate PushNotifications.register() on that check and log/no-op instead of crashing. Also attach a registrationError listener — the expected non-fatal path for other FCM failures.
Triage note: read the INNERMOST exception value of the Sentry event; the InvocationTargetException wrapper is just the bridge's reflection call and names neither the plugin nor the cause.