feat(rum): report app launch (TTID) on Android - #12
Merged
Fiona2016 merged 4 commits intoJul 28, 2026
Conversation
The native RumAppStartupDetector registers its Activity lifecycle callbacks when the RUM feature initializes. Flutter initializes the SDK from Dart main(), by which time the first Activity has already been created, so the detector never sees a launch and none is ever reported. Report it from the plugin instead, on the launch frame: - DatadogSdkPlugin is now ActivityAware and stamps the UI creation time from onAttachedToActivity. Engine attach is not used: a pre-warmed or headless engine attaches with no UI, which would place the launch start far too early. An engine that never gets an Activity reports no UI creation time, and the SDK falls back to timing from process start. - Dart subscribes a one-shot timings callback and passes how long ago the launch frame finished rasterizing, so neither the callback scheduling nor the method channel round trip is counted as launch time. Measured at ~61ms on a release build, which is no longer charged to the launch. - If the first frame is already on screen when RUM is enabled, no launch is reported at all: the next frame is not the launch frame, and a static page may never render one. - Registration tolerates a missing binding, since enable() is reachable from plain Dart contexts where touching SchedulerBinding.instance throws. Requires cloud.flashcat:dd-sdk-android-rum 0.5.0 for the notifyAppLaunch entry point. Verified end to end against a local RUM intake: startup_type=cold_start, app_launch_metric=ttid, durations 0.6s-2.1s, landing in Doris. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…resh rate on attach Three problems with the first cut, all of them cases where a launch or a vital silently went missing. Gating the app-launch request on Flutter-owned initialization lost the launch entirely on hosts that attach to a natively initialized SDK. Initializing the native SDK before Flutter attaches does not mean it initialized early enough for the native detector to observe the first Activity, so on a host that initializes late neither side reported anything. The request is now unconditional on Android and the native SDK arbitrates through notifyAppLaunchIfAbsent, which is the only side that knows whether its own detector saw the launch. The plugin's own de-duplication is gone with it - one latch, in one place. attachToExisting also stopped reporting a refresh rate: it passed a null vital frequency, so the callback was never registered even with reportFlutterPerformance on. That was a regression against the behaviour before build and refresh-rate timings were split apart. DatadogAttachConfiguration now carries vitalUpdateFrequency, defaulting to average. Finally, performance and refresh-rate reporting each registered their own timings callback and each sent its own platform message. They stay independently configurable but now share one subscription and one message per frame batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… exist VitalsFrequency has frequent, average and rare - there is no never - so the dartdoc link was broken and the advice unfollowable. Assigning null is what actually disables the collection, matching how DatadogRumConfiguration documents the same field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Flutter never reported an app launch on Android.
RumAppStartupDetectorregisters its Activity lifecycle callbacks when the RUM feature initializes, and Flutter initializes the SDK from Dartmain()— by then the first Activity has already been created, so the detector never sees the launch.The launch is now reported from the plugin, on the launch frame.
How
notifyAppLaunchIfAbsent(feat(rum): let cross-platform hosts report an app launch the native detector misses fc-sdk-android#23) arbitrates instead: the detector wins whenever it fired, since it always fires from the first Activity's pre-create, before any frame a host could render. It also de-duplicates several engines in one process, so the plugin keeps no latch of its own.DatadogSdkPluginis nowActivityAwareand stamps the UI creation time fromonAttachedToActivity. Engine attach is deliberately not used: a pre-warmed or headless engine attaches with no UI, which would place the launch start far too early. An engine that never gets an Activity reports no UI creation time, and the native SDK falls back to timing from process start. The stamp usesAtomicLong.compareAndSetso two engines attaching concurrently cannot race.Timeline.nowshares its clock withFrameTiming, which is what makes the subtraction meaningful; an implausible reading falls back to 0.addTimingsCallbackonly delivers later frames, so the next one is not the launch frame — and a static page may never render another at all. Reporting nothing beats reporting something wrong.enable()is reachable from plain Dart contexts (unit tests, background isolates) where touchingSchedulerBinding.instancethrows rather than returning null.iOS measures app launch natively and is unchanged (no-op).
Also fixed here
attachToExistingreports a refresh rate again. It passed a null vital frequency, so the callback was never registered even withreportFlutterPerformanceon — a regression against the behaviour before build and refresh-rate timings were split apart.DatadogAttachConfigurationnow carriesvitalUpdateFrequency, defaulting toVitalsFrequency.average.SchedulerBindingsubscription and send one platform message per frame batch.Depends on
cloud.flashcat:dd-sdk-android-rum0.5.0 fornotifyAppLaunchIfAbsent— feat(rum): let cross-platform hosts report an app launch the native detector misses fc-sdk-android#23.ext.datadog_versionis bumped here.publishonce it merges.Testing
flutter analyzecould reach).flutter analyze libclean.startup_type=cold_start,app_launch_metric=ttid, durations 0.6s–2.1s, landing int_vitals. Cross-checked against the system's ownFully drawnfigure for the same launches.Known and intentional: a hot start (process alive, Activity alive, returning from background) reports nothing — tracked separately.
🤖 Generated with Claude Code