start method
Begins the startup sequence.
A call while already StartupStatus.running is a no-op. On success, emits StartupStatus.completed. On jailbreak, emits StartupStatus.jailbreakDetected with the exception. When the installed version is below the remote-config minimum, emits StartupStatus.forceUpdateRequired. On any other failure, emits StartupStatus.failed with a diagnostic message.
Implementation
Future<void> start() async {
if (state.status == StartupStatus.running) return;
emit(const StartupState(status: StartupStatus.running));
final sw = Stopwatch()..start();
try {
await _orchestrator.execute(
onProgress: (int completed, int total, String label) {
// Construct a fresh state to guarantee subTaskFraction is cleared at
// every task boundary rather than relying on copyWith ?? semantics.
// Guarded: this callback can fire after the cubit is closed (e.g.
// the splash screen is disposed mid-task), which would otherwise
// throw a StateError on emit().
if (!isClosed) emit(StartupState(status: StartupStatus.running, completedCount: completed, totalCount: total, currentLabel: label));
},
onSubProgress: (double globalFraction) {
if (!isClosed) emit(state.copyWith(subTaskFraction: globalFraction));
},
);
// Allow the splash progress animation to visually reach 100% before
// emitting completed, which triggers GoRouter to navigate away.
// The 200 ms buffer on top of the animation duration absorbs any frame jitter.
await Future<void>.delayed(_progressAnimationDuration + const Duration(milliseconds: 200));
sw.stop();
Sentry.metrics.distribution('startup.total_ms', sw.elapsedMilliseconds.toDouble(), unit: 'millisecond');
Sentry.metrics.count('startup.completed', 1);
if (!isClosed) emit(state.copyWith(status: StartupStatus.completed));
} on JailbreakAppException catch (e) {
_logger.warn('[StartupCubit] Jailbreak detected', error: e);
Sentry.metrics.count('startup.jailbreak_detected', 1);
if (!isClosed) emit(state.copyWith(status: StartupStatus.jailbreakDetected, jailbreakException: e));
} on ForceUpdateException catch (e) {
_logger.warn('[StartupCubit] Force update required — ${e.message}');
Sentry.metrics.count('startup.force_update_required', 1);
if (!isClosed) emit(state.copyWith(status: StartupStatus.forceUpdateRequired, forceUpdateException: e));
} catch (e, st) {
_logger.error('[StartupCubit] Startup failed', error: e, stackTrace: st);
Sentry.metrics.count('startup.failed', 1);
if (!isClosed) emit(state.copyWith(status: StartupStatus.failed, errorMessage: e.toString()));
}
}