execute method
Executes all tasks in phase order, invoking onProgress before each one
and once more with label 'startup.ready' when all tasks finish.
onProgress receives the number of already-completed tasks, the total task
count, and the label of the task that is about to run. This means the
splash screen always shows the currently-running task name, and the progress
percentage reflects work already done before the current task started.
The optional onSubProgress receives a global progress fraction (0.0–1.0)
when the active task reports intra-task progress. It is only called when
the running task invokes its own onSubProgress callback; tasks that do
not implement sub-progress never trigger it.
Throws JailbreakAppException if a jailbreak is detected — this is a terminal condition and the app should not continue. All other exceptions are logged before being re-thrown.
Implementation
Future<void> execute({
required void Function(int completed, int total, String label) onProgress,
void Function(double globalFraction)? onSubProgress,
}) async {
final int total = _tasks.length;
int completed = 0;
_logger.info('[StartupOrchestrator] Starting $total tasks');
for (final StartupPhase phase in StartupPhase.values) {
final List<StartupTask> phaseTasks = _tasks.where((StartupTask t) => t.phase == phase).toList();
if (phaseTasks.isEmpty) continue;
_logger.info('[StartupOrchestrator] Phase: ${phase.name}');
for (final StartupTask task in phaseTasks) {
_logger.info('[StartupOrchestrator] Running: ${task.id.name}');
// Announce the task BEFORE executing so the label shows what is
// currently running rather than what just finished.
onProgress(completed, total, task.label);
try {
final sw = Stopwatch()..start();
await task.execute(
onSubProgress: onSubProgress == null ? null : (double subFrac) => onSubProgress((completed + subFrac.clamp(0.0, 1.0)) / total),
);
sw.stop();
Sentry.metrics.distribution('startup.task.${task.id.name}_ms', sw.elapsedMilliseconds.toDouble(), unit: 'millisecond');
Sentry.metrics.count('startup.task.completed', 1);
completed++;
} on JailbreakAppException {
rethrow; // Terminal security condition — cubit handles it.
} on ForceUpdateException {
rethrow; // Expected product gate — cubit handles it, not an error.
} catch (e, st) {
_logger.error('[StartupOrchestrator] Task ${task.id.name} failed', error: e, stackTrace: st);
Sentry.metrics.count('startup.task.failed', 1);
rethrow;
}
}
}
onProgress(total, total, Strings.startupReady);
_logger.info('[StartupOrchestrator] All tasks completed');
}