appInitFirebase function

Future<FirebaseApp> appInitFirebase(
  1. FirebaseOptions options, {
  2. Duration? settleTimeout,
  3. Duration? recoverIfRegisteredAfter,
})

Initializes the default FirebaseApp (idempotent across gate-retry re-runs).

settleTimeout — the "hung init but app registered" recovery

On web, Firebase.initializeApp() can REGISTER the app (so it appears in Firebase.apps) while its returned Future never settles. CONFIRMED root cause (biblequiz, Sentry, iOS WebKit): initializeApp registers the JS app and then awaits each plugin's ensurePluginInitialized; firebase_auth_web's ends with await onWaitInitState(), which blocks on the first onAuthStateChanged. On iOS WebKit the persisted-session IndexedDB read of firebaseLocalStorageDb never fires its callback, so onWaitInitState — and thus initializeApp — hangs FOREVER (a 90s watcher never saw it settle or error). Desktop Chrome/Blink is unaffected; a brand-new device (no persisted session) resolves the initial state immediately. A manual retry "fixes it instantly" because it hits the Firebase.apps.isNotEmpty guard and short-circuits to Firebase.app(), skipping the hung initializeApp.

When settleTimeout is supplied, the FIRST attempt does the same thing the retry would, via a two-tier bound (see _awaitFirebaseInit):

  • recoverIfRegisteredAfter (short grace, e.g. 4s): if the init hasn't settled by here but the app IS registered, recover immediately — this catches the permanent hang fast (the app registers near-instantly; a plugin init hangs);
  • settleTimeout (outer bound, e.g. 30s): if the app is NOT registered at the grace (a slow-but-healthy SDK load still loading), keep waiting the remainder rather than false-tripping a slow cold start into a retry.

A registered-app recovery is reported as an error (not silently absorbed) via reportBootstrapDiagnostic, so it reaches the backend whether the reporter attached before Firebase (Sentry early-attach → reports now) or after (Crashlytics → deferred + flushed on attach) — see dreamicBootstrap's attachErrorReportingFirst.

If the init genuinely hangs with NO app registered (within settleTimeout), a descriptive TimeoutException is thrown so the caller's timeout/retry/error path handles it. settleTimeout null (the default) keeps the unbounded behavior (await to completion) for callers that don't want the bound (and for direct test callers); recoverIfRegisteredAfter is ignored then.

Implementation

Future<FirebaseApp> appInitFirebase(
  FirebaseOptions options, {
  Duration? settleTimeout,
  Duration? recoverIfRegisteredAfter,
}) async {
  // Guard against a gate-retry re-run: `Firebase.initializeApp` throws
  // `[core/duplicate-app]` on a second unconditional call. If the default app
  // already exists, reuse it directly; otherwise initialize (Issue 17 /
  // idempotency). The `duplicate-app` catch is a belt-and-suspenders fallback
  // for the case where the platform layer has the default app registered while
  // the Dart-side `Firebase.apps` cache is momentarily empty.
  FirebaseApp fbApp;
  if (Firebase.apps.isNotEmpty) {
    fbApp = Firebase.app();
  } else {
    try {
      logBreadcrumb(
        'appInitFirebase: calling Firebase.initializeApp '
        '(settleTimeout=${settleTimeout?.inSeconds ?? "none"}s, '
        'recoverIfRegisteredAfter=${recoverIfRegisteredAfter?.inSeconds ?? "none"}s)',
        category: 'bootstrap',
      );
      final initFuture = Firebase.initializeApp(
        // options: DefaultFirebaseOptions.currentPlatform,
        options: options,
      );
      if (settleTimeout == null) {
        fbApp = await initFuture;
      } else {
        fbApp = await _awaitFirebaseInit(initFuture, settleTimeout, recoverIfRegisteredAfter);
      }
    } on FirebaseException catch (e) {
      if (e.code.contains('duplicate-app')) {
        fbApp = Firebase.app();
      } else {
        rethrow;
      }
    }
  }

  // Mark Firebase as initialized and store the app reference for the rest of the package
  AppConfigBase.isFirebaseInitialized = true;
  AppConfigBase.firebaseApp = fbApp;

  return fbApp;
}