dreamicBootstrap function

Future<void> dreamicBootstrap({
  1. required FirebaseOptions firebaseOptions,
  2. required DreamicServicesInitializer servicesInitializer,
  3. Map<String, dynamic>? additionalRemoteConfigDefaults,
  4. DreamicBootstrapHook? afterFirebaseInit,
  5. DreamicBootstrapHook? registerBeforeServices,
  6. DreamicBootstrapHook? registerAfterServices,
  7. DreamicBootstrapHook? captureEntryIntents,
  8. bool appCubitNetworkRequired = true,
  9. Uri? appCubitEntranceUri,
  10. Duration? bootstrapTimeout = const Duration(seconds: 45),
})

Runs the cold-start init chain that today sits in main() — Firebase, error backend attach, remote config, app configs base, emulator connect, DreamicServices.initialize, appInitAppCubit — now behind the splash rather than before runApp. Returns a single Future<void> the DreamicAppInitHost/DreamicAppInitGate gates the router on.

Ordering (fixed)

appInitFirebase
  → [hook] afterFirebaseInit          (Firebase/Firestore instance settings)
  → appInitErrorHandling              (attach Crashlytics + FLUSH early buffer)
  → appInitRemoteConfig
  → appInitAppConfigsBase
  → appInitConnectToFirebaseEmulatorIfNecessary
  → [hook] registerBeforeServices     (DDS-006: AppRouter + UserRepoInt)
  → servicesInitializer               (DreamicServices.initialize)
  → [hook] registerAfterServices
  → appInitAppCubit                   (network check inside the splash)
  → [hook] captureEntryIntents        (cold deep-link capture)

The whole sequence is wrapped in an outer hang-timeout (bootstrapTimeout, default 45s, nullable to disable) so a true hang becomes an init-error (gate errorWidget) rather than an infinite splash.

Per-task failure policy (Issues 81/84)

  • Fatal (uncaught throw aborts the Future → gate retry): appInitFirebase, appInitErrorHandling, appInitAppConfigsBase, emulator connect, servicesInitializer (DreamicServices.initialize), appInitAppCubit, and any uncaught throw from an app hook — dreamic-core does not wrap hooks.
  • Non-fatal dreamic-core-owned: only appInitRemoteConfig, which already internally swallows its fetch error and falls back to defaults, so it needs no extra wrapper here. Non-critical hook work is the app's own try/catch responsibility.

Idempotency (REQUIRED for retry to recover)

Every dreamic-core step is re-runnable: Firebase init is guarded, remote-config / app-cubit registrations are isRegistered-guarded, the emulator-connect and isolate-listener adds are apply-once. App hooks must be idempotent too (the app's responsibility).

Implementation

Future<void> dreamicBootstrap({
  required FirebaseOptions firebaseOptions,
  required DreamicServicesInitializer servicesInitializer,
  Map<String, dynamic>? additionalRemoteConfigDefaults,
  DreamicBootstrapHook? afterFirebaseInit,
  DreamicBootstrapHook? registerBeforeServices,
  DreamicBootstrapHook? registerAfterServices,
  DreamicBootstrapHook? captureEntryIntents,
  bool appCubitNetworkRequired = true,
  Uri? appCubitEntranceUri,
  Duration? bootstrapTimeout = const Duration(seconds: 45),
}) {
  final future = _runBootstrap(
    firebaseOptions: firebaseOptions,
    servicesInitializer: servicesInitializer,
    additionalRemoteConfigDefaults: additionalRemoteConfigDefaults,
    afterFirebaseInit: afterFirebaseInit,
    registerBeforeServices: registerBeforeServices,
    registerAfterServices: registerAfterServices,
    captureEntryIntents: captureEntryIntents,
    appCubitNetworkRequired: appCubitNetworkRequired,
    appCubitEntranceUri: appCubitEntranceUri,
  );

  // Compose the outer hang-timeout INSIDE the bootstrap Future (the gate has no
  // internal timeout). On expiry a `TimeoutException` is thrown → gate
  // `errorWidget`. Null disables (tests, special cases). No in-Future
  // retry-with-backoff — recovery is the user-facing idempotent re-mount
  // (Issue 81). Note: `Future.timeout` does NOT cancel the underlying work
  // (accepted stop-gap, Issue 53).
  if (bootstrapTimeout == null) {
    return future;
  }
  return future.timeout(bootstrapTimeout);
}