dreamicBootstrap function
- 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),
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 owntry/catchresponsibility.
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);
}