Skip to content

feat: Add reportAppFullyDisplayed to RUM for app launch time to full display (TTFD) - #1124

Open
brunovsiqueira wants to merge 3 commits into
DataDog:developfrom
brunovsiqueira:feat/app-fully-displayed
Open

feat: Add reportAppFullyDisplayed to RUM for app launch time to full display (TTFD)#1124
brunovsiqueira wants to merge 3 commits into
DataDog:developfrom
brunovsiqueira:feat/app-fully-displayed

Conversation

@brunovsiqueira

Copy link
Copy Markdown
Contributor

What and why?

Bridges reportAppFullyDisplayed() from the native SDKs to Dart.

Both native SDKs have shipped this API for a while — RUMMonitorProtocol.reportAppFullyDisplayed() on iOS and RumMonitor.reportAppFullyDisplayed() on Android — but it was never exposed through the Flutter plugin. addViewLoadingTime was bridged in #655; the app-launch equivalent was not. Without this method there is no way for a Flutter app to mark the moment its UI is fully displayed, so rum.measure.app.startup_to_full_display never populates for Flutter apps at all.

#944 is the standing request for TTFD support on Flutter.

How?

Purely additive: one new no-argument method threaded through the existing layers — DatadogRumDdRumPlatform → method channel → DatadogRumPlugin on iOS and Android. Web is a no-op, as the Browser SDK has no equivalent (same treatment as addViewLoadingTime).

No existing behaviour changes. addViewLoadingTime is deliberately untouched — that is per-view loading time, a different metric.

Review checklist

  • This pull request has appropriate unit and / or integration tests
  • This pull request references a Github or JIRA issue

Tests mirror the addViewLoadingTime ones: a method-channel test in ddrum_method_channel_test.dart, plugin tests plus contract entries on both DatadogRumPluginTest.kt and DatadogRumPluginTests.swift, and the call is exercised in the manual RUM integration scenario. The existing native mocks already implemented reportAppFullyDisplayed, so they needed no changes.

🤖 Generated with Claude Code

…display (TTFD)

Both native SDKs expose reportAppFullyDisplayed -- RUMMonitorProtocol on
iOS and RumMonitor on Android -- but it was never bridged to Dart, so
rum.measure.app.startup_to_full_display could not populate for Flutter
apps at all. Adds the method through the platform interface, the method
channel and both native plugins, with a no-op on Web since the Browser
SDK has no equivalent.

refs: DataDog#944

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brunovsiqueira
brunovsiqueira requested a review from a team as a code owner August 18, 2026 18:12
@sbarrio
sbarrio requested a review from cdn34dd August 19, 2026 07:07

@fuzzybinary fuzzybinary left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just one very minor request - if we're going to add TTFD to the test scenario we should verify it sent the value.

Future<void> reportAppFullyDisplayed() {
return methodChannel.invokeMethod(
'reportAppFullyDisplayed',
<String, Object?>{},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We tend to add the type annotations on these maps, especially if they're empty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map is already <String, Object?>{}, same as stopSession below. Did you mean invokeMethod<void>?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed a "not" in there. We tend to not add the type annotations on these maps. It slipped in on stopSession, and not a big deal so I'm not going to worry about it.

App launch vitals and operation step vitals share the `vital` event type
but carry different properties, so the decoder now splits them into
separate buckets instead of decoding every vital as an operation step.
Without this the TTFD vital was counted as a fourth operation step in the
manual RUM scenario.

The scenario test now asserts that a single `ttfd` app launch vital is
reported with a plausible duration.

refs: DataDog#944

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fuzzybinary
fuzzybinary previously approved these changes Aug 20, 2026
Future<void> reportAppFullyDisplayed() {
return methodChannel.invokeMethod(
'reportAppFullyDisplayed',
<String, Object?>{},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed a "not" in there. We tend to not add the type annotations on these maps. It slipped in on stopSession, and not a big deal so I'm not going to worry about it.

Comment on lines +194 to +200
final ttfdVitals = rumLog
.where((e) =>
e.eventType == 'vital' &&
RumVitalAppLaunchEventDecoder.isAppLaunchVital(e.rumEvent))
.map((e) => RumVitalAppLaunchEventDecoder(e.rumEvent))
.where((e) => e.appLaunchMetric == 'ttfd')
.toList();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll likely refactor this to have all vitals accessible on the session but for now this is fine.

Comment on lines +208 to +209
expect(ttfdVitals[0].duration,
lessThan(const Duration(seconds: 60).inNanoseconds));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll see if this is enough for CI or if it flakes 😅.

@datadog-prod-us1-6

This comment has been minimized.

@fuzzybinary

Copy link
Copy Markdown
Member

@brunovsiqueira You need to run melos dartfmt

The Android SDK only sends the TTFD app launch vital once it has computed
TTID for the startup scenario. That does not happen in the integration
test app -- it sends no app launch vitals at all, only operation step
vitals -- so there is nothing to assert on there yet and
android-integration-test was failing.

refs: DataDog#944

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment on lines +194 to +198
// This is only checked on iOS. The Browser SDK has no equivalent API, and
// the Android SDK only sends TTFD once it has computed TTID for the startup
// scenario, which does not happen in this app -- it sends no app launch
// vitals at all, so there is nothing to assert on there yet.
if (!kIsWeb && Platform.isIOS) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, we may want to hold off on this change. I know we're in the process of looking into TTID for Android. Let me look into the state of it and I'll get back to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants