Skip to content

Commit 31b2afb

Browse files
feat: ttid (#1910)
* Change app start integration in a way that works with ttid as well * Formatting * Update * add visibleForTesting * Update * update * Add app start info test * Remove set app start info null * Review improvements * Add TTID * Improvements * Improvements * Fix integration test * Update * Clear after tracking * Update CHANGELOG * Format * Update * Update * remove import * Update sentry tracer * Add (not all) improvements for pr review * combine transaction handler * Refactor trackAppStart and trackRegularRoute to use private method * Fix dart analyzer * Remove clear * Clear in tearDown * Apply suggestions from code review Co-authored-by: Philipp Hofmann <[email protected]> * Apply PR suggestions * fix analyze * update * update * Fix tests * Fix analyze * revert sample * Update * Update * Fix test * Move clear to the beginning of function * Fix start time * Fix analyze * remove comment * Formatting * fix test * add ttid duration assertion and determineEndTime timeout * Rename finish transaction and do an early exit with enableAutoTransactions * Rename function * Remove static and getter for in navigator observer * Expose SentryDisplayWidget as public api and add it to example app * Fix dart analyze * Fix dart doc * Improve tests * Reduce fake frame finishing time and improve tests * Improve test names * Fix tests * Apply formatting * Add extra assertion in tests --------- Co-authored-by: Philipp Hofmann <[email protected]>
1 parent 1d9ee98 commit 31b2afb

18 files changed

+961
-72
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010

1111
### Features
1212

13+
- Add TTID (time to initial display), which allows you to measure the time it takes to render the first frame of your screen ([#1910](https://github.com/getsentry/sentry-dart/pull/1910))
14+
- Requires using the [routing instrumentation](https://docs.sentry.io/platforms/flutter/integrations/routing-instrumentation/).
15+
- Introduces two modes:
16+
- `automatic` mode is enabled by default for all screens and will yield only an approximation result.
17+
- `manual` mode requires manual instrumentation and will yield a more accurate result.
18+
- To use `manual` mode, you need to wrap your desired widget: `SentryDisplayWidget(child: MyScreen())`.
19+
- You can mix and match both modes in your app.
20+
- Other significant fixes
21+
- `didPop` doesn't trigger a new transaction
22+
- Change transaction operation name to `ui.load` instead of `navigation`
1323
- Use `recordHttpBreadcrumbs` to set iOS `enableNetworkBreadcrumbs` ([#1884](https://github.com/getsentry/sentry-dart/pull/1884))
1424
- Apply `beforeBreadcrumb` on native iOS crumbs ([#1914](https://github.com/getsentry/sentry-dart/pull/1914))
1525
- Add `maxQueueSize` to limit the number of unawaited events sent to Sentry ([#1868](https://github.com/getsentry/sentry-dart/pull/1868))

dart/lib/sentry.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export 'src/utils/http_header_utils.dart';
4949
// ignore: invalid_export_of_internal_element
5050
export 'src/sentry_trace_origins.dart';
5151
// ignore: invalid_export_of_internal_element
52+
export 'src/sentry_span_operations.dart';
53+
// ignore: invalid_export_of_internal_element
5254
export 'src/utils.dart';
5355
// spotlight debugging
5456
export 'src/spotlight.dart';

dart/lib/src/sentry_measurement.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ class SentryMeasurement {
3939
value = duration.inMilliseconds,
4040
unit = DurationSentryMeasurementUnit.milliSecond;
4141

42+
/// Duration of the time to initial display in milliseconds
43+
SentryMeasurement.timeToInitialDisplay(Duration duration)
44+
: assert(!duration.isNegative),
45+
name = 'time_to_initial_display',
46+
value = duration.inMilliseconds,
47+
unit = DurationSentryMeasurementUnit.milliSecond;
48+
4249
final String name;
4350
final num value;
4451
final SentryMeasurementUnit? unit;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:meta/meta.dart';
2+
3+
@internal
4+
class SentrySpanOperations {
5+
static const String uiLoad = 'ui.load';
6+
static const String uiTimeToInitialDisplay = 'ui.load.initial_display';
7+
}

dart/lib/src/sentry_trace_origins.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ class SentryTraceOrigins {
2727
static const autoDbDriftQueryExecutor = 'auto.db.drift.query.executor';
2828
static const autoDbDriftTransactionExecutor =
2929
'auto.db.drift.transaction.executor';
30+
static const autoUiTimeToDisplay = 'auto.ui.time_to_display';
31+
static const manualUiTimeToDisplay = 'manual.ui.time_to_display';
3032
}

flutter/example/android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ android {
4848

4949
defaultConfig {
5050
applicationId "io.sentry.samples.flutter"
51-
minSdkVersion 19
51+
minSdkVersion flutter.minSdkVersion
5252
targetSdkVersion 33
5353
versionCode flutterVersionCode.toInteger()
5454
versionName flutterVersionName

flutter/example/lib/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
1313
import 'package:sentry_isar/sentry_isar.dart';
1414
import 'package:sentry_sqflite/sentry_sqflite.dart';
1515
import 'package:sqflite/sqflite.dart';
16+
1617
// import 'package:sqflite_common_ffi/sqflite_ffi.dart';
1718
// import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
1819
import 'package:universal_platform/universal_platform.dart';
@@ -80,6 +81,7 @@ Future<void> setupSentry(
8081
// going to log too much for your app, but can be useful when figuring out
8182
// configuration issues, e.g. finding out why your events are not uploaded.
8283
options.debug = true;
84+
options.spotlight = Spotlight(enabled: true);
8385

8486
options.maxRequestBodySize = MaxRequestBodySize.always;
8587
options.maxResponseBodySize = MaxResponseBodySize.always;
@@ -732,7 +734,7 @@ void navigateToAutoCloseScreen(BuildContext context) {
732734
context,
733735
MaterialPageRoute(
734736
settings: const RouteSettings(name: 'AutoCloseScreen'),
735-
builder: (context) => const AutoCloseScreen(),
737+
builder: (context) => SentryDisplayWidget(child: const AutoCloseScreen()),
736738
),
737739
);
738740
}

flutter/lib/sentry_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export 'src/screenshot/sentry_screenshot_quality.dart';
1616
export 'src/user_interaction/sentry_user_interaction_widget.dart';
1717
export 'src/binding_wrapper.dart';
1818
export 'src/sentry_widget.dart';
19+
export 'src/navigation/sentry_display_widget.dart';

flutter/lib/src/integrations/native_app_start_integration.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ class AppStartInfo {
114114
final AppStartType type;
115115
final DateTime start;
116116
final DateTime end;
117+
Duration get duration => end.difference(start);
117118

118119
SentryMeasurement toMeasurement() {
119-
final duration = end.difference(start);
120120
return type == AppStartType.cold
121121
? SentryMeasurement.coldAppStart(duration)
122122
: SentryMeasurement.warmAppStart(duration);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'time_to_initial_display_tracker.dart';
3+
4+
import '../frame_callback_handler.dart';
5+
6+
/// A widget that reports the Time To Initially Displayed (TTID) of its child widget.
7+
///
8+
/// This widget wraps around another widget to measure and report the time it takes
9+
/// for the child widget to be initially displayed on the screen. This method
10+
/// allows a more accurate measurement than what the default TTID implementation
11+
/// provides. The TTID measurement begins when the route to the widget is pushed and ends
12+
/// when `addPostFramecallback` is triggered.
13+
///
14+
/// Wrap the widget you want to measure with [SentryDisplayWidget], and ensure that you
15+
/// have set up Sentry's routing instrumentation according to the Sentry documentation.
16+
///
17+
/// ```dart
18+
/// SentryDisplayWidget(
19+
/// child: MyWidget(),
20+
/// )
21+
/// ```
22+
///
23+
/// Make sure to configure Sentry's routing instrumentation in your app by following
24+
/// the guidelines provided in Sentry's documentation for Flutter integrations:
25+
/// https://docs.sentry.io/platforms/flutter/integrations/routing-instrumentation/
26+
///
27+
/// See also:
28+
/// - [Sentry's documentation on Flutter integrations](https://docs.sentry.io/platforms/flutter/)
29+
/// for more information on how to integrate Sentry into your Flutter application.
30+
class SentryDisplayWidget extends StatefulWidget {
31+
final Widget child;
32+
final FrameCallbackHandler _frameCallbackHandler;
33+
34+
SentryDisplayWidget({
35+
super.key,
36+
required this.child,
37+
@visibleForTesting FrameCallbackHandler? frameCallbackHandler,
38+
}) : _frameCallbackHandler =
39+
frameCallbackHandler ?? DefaultFrameCallbackHandler();
40+
41+
@override
42+
_SentryDisplayWidgetState createState() => _SentryDisplayWidgetState();
43+
}
44+
45+
class _SentryDisplayWidgetState extends State<SentryDisplayWidget> {
46+
@override
47+
void initState() {
48+
super.initState();
49+
TimeToInitialDisplayTracker().markAsManual();
50+
51+
widget._frameCallbackHandler.addPostFrameCallback((_) {
52+
TimeToInitialDisplayTracker().completeTracking();
53+
});
54+
}
55+
56+
@override
57+
Widget build(BuildContext context) {
58+
return widget.child;
59+
}
60+
}

0 commit comments

Comments
 (0)