launch_tracker_sdk Free
App launch time tracker with native platform timing, a debug implementation that pushes OTLP spans, and a zero-cost release noop, swapped via
build_prepare.
Version: 2.0.0
Variables
| Variable | Type | Default | Description |
|---|---|---|---|
| appName | string | — | The name of your application |
| isForMonorepo | boolean | true | Whether this is being generated as part of a monorepo |
Usage
Automatic (via flutter_modular_monorepo)
Included unconditionally in Phase 3 of the monorepo post-gen hook — no selection required.
Standalone
archipelago generate launch_tracker_sdkNon-interactive (CI)
archipelago generate launch_tracker_sdk --config my_config.jsonConfig Template
{
"@appName": "The name of your application",
"appName": "MyApp",
"@isForMonorepo": "Whether this is being generated as part of a monorepo",
"isForMonorepo": true
}Architecture
Uses the Impl + Noop (unified class name) pattern with build_prepare swap.
There is no _api package. Both app_launch_tracker_impl and app_launch_tracker_noop export a class named AppLaunchTracker with identical method signatures. build_prepare rewrites the import path in the host app's .dart files — the class name never changes, so the host compiles with either package.
This differs from monitoring_impl / monitoring_noop, which share a contract via monitoring_api. The launch tracker is only ever referenced by the host app's HostAppWidget, so no shared contract package is needed.
See the architecture guide for the full decision tree.
Generated Structure
infrastructure/
├── app_launch_tracker_impl/ # Debug implementation (swapped out in release)
│ ├── android/
│ │ └── src/main/kotlin/com/archipelago/app_launch_tracker/
│ │ └── LaunchTimeTrackerPlugin.kt # Native process start time (Android)
│ ├── ios/
│ │ └── Classes/
│ │ └── LaunchTimeTrackerPlugin.swift # Native process start time (iOS)
│ └── lib/src/
│ ├── app_launch_tracker_impl.dart # AppLaunchTracker (debug)
│ ├── app_launch_metrics.dart # AppLaunchMetrics + MetricTrace (package-private)
│ ├── launch_time_tracker_channel.dart # Platform channel wrapper
│ ├── trace_utils.dart # traceAsync / traceSync / safeTraceProcessAsync
│ └── di/
│ ├── injector.dart # @InjectableInit.microPackage()
│ └── injector.module.dart # AppLaunchTrackerPackageModule
└── app_launch_tracker_noop/ # Release stubs (zero overhead)
└── lib/src/
├── app_launch_tracker_noop.dart # AppLaunchTracker (noop — same class name)
└── di/
├── injector.dart
└── injector.module.dart # AppLaunchTrackerPackageModuleTwo-Phase Launch Lifecycle
The tracker is designed around the monorepo's two-phase init architecture:
| Method | Phase | Description |
|---|---|---|
start() | Pre-launch | Called in HostAppWidget.initState — fetches native timestamp via platform channel, starts stopwatch |
markPreLaunchCompleted() | Pre-launch | Called after initializerController.initialize() resolves |
markFirstFrameRendered() | Post frame | Called in addPostFrameCallback after first frame |
startPostLaunch() | Post-launch | Starts post-launch stopwatch |
markPostLaunchCompleted() | Post-launch | Called after PostLaunchInitializer.initialize() — pushes OTLP spans |
recordStep(String step) | Any | Records a named milestone timestamp |
recordDuration(String name, int ms) | Any | Records a named duration (e.g. from traceAsync) |
seedNativeTimestamps({required int nativeStartMs, required int hostStartMs}) | Testing | Injects pre-captured timestamps (used in tests and by the platform channel callback) |
Native Timing
app_launch_tracker_impl is a Flutter plugin. The platform channel com.archipelago/launch_time_tracker exposes a single method getStartupTimestamps that returns:
| Key | Description |
|---|---|
nativeStartMs | Process creation time (ms since epoch) |
hostStartMs | Time when the Flutter engine attached to the plugin (ms since epoch) |
Android
nativeStartMs is computed from Process.getStartUptimeMillis() (API 24+), converted to wall-clock time using SystemClock.elapsedRealtime() and System.currentTimeMillis(). On API < 24, falls back to System.currentTimeMillis() at engine-attach time.
iOS
nativeStartMs is read via sysctl(CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()) from kinfo_proc.kp_proc.p_starttime (seconds + microseconds since epoch). Falls back to Date().timeIntervalSince1970 * 1000 if sysctl fails.
hostStartMs on both platforms is captured at engine-attach time (onAttachedToEngine / plugin register(with:)).
OTLP Push via AppMonitoring
When markPostLaunchCompleted() is called, the impl pushes timing spans to Grafana (or any configured OTLP backend) via AppMonitoring.recordTransaction. The impl soft-checks GetIt.instance.isRegistered<AppMonitoring>() — if monitoring is not registered, the push is skipped silently.
Spans emitted:
| Span name | Attributes |
|---|---|
app_launch | Parent span wrapping all phases |
native_to_host | duration_ms, phase: native_to_host, flavor, platform |
pre_launch | duration_ms, phase: pre_launch |
first_frame | duration_ms, phase: first_frame |
post_launch | duration_ms, phase: post_launch |
Trace Utilities
trace_utils.dart provides wrapping helpers:
// Wrap an async operation — records to tracker and prints to debug console
final result = await traceAsync('load_config', () => loadConfig());
// Wrap a sync operation
final value = traceSync('parse_response', () => parseResponse(data));
// Wrap an async operation and emit an OTLP span if monitoring is available
final result = await safeTraceProcessAsync(
key: 'fetch_remote_config',
block: () => remoteConfig.fetchAndActivate(),
monitoring: getIt<AppMonitoring>(),
);safeTraceProcessAsync is a fire-and-forget-safe wrapper: if monitoring is null, it simply executes the block without any tracing overhead.
DI Registration
Both _impl and _noop register the same DI module name: AppLaunchTrackerPackageModule. The build_prepare swap is transparent to the host app's injector.
Register it as the first external module so it's available before any other SDK initializes:
@InjectableInit(
externalPackageModulesBefore: [
ExternalModule(AppLaunchTrackerPackageModule),
// ... other modules
],
)
Future<void> initializeInjector() => $initGetIt(getIt);HostAppWidget resolves the tracker via GetIt.I<AppLaunchTracker>() immediately on state creation, starting the clock before the first frame.
build_prepare Swap
build_prepare.yaml at workspace root includes:
build_prepare:
mappings:
- debug: app_launch_tracker_impl
release: app_launch_tracker_noop
target_paths:
- apps/your_appbuild_prepare performs two passes when swapping:
- Replaces
app_launch_tracker_implwithapp_launch_tracker_noop(and vice versa) in everypubspec.yamlundertarget_paths - Rewrites
import 'package:app_launch_tracker_impl/...'→import 'package:app_launch_tracker_noop/...'in every.dartfile under each target'slib/andtest/
The swap is fully reversible: build-prepare:debug restores all original imports exactly.
Run before release builds:
melos run build-prepare:releaseRestore after builds (or if the build fails):
melos run build-prepare:debug