Skip to content

Commit 4133230

Browse files
authored
feat: Add sentry widget that includes other sentry widgets (#1846)
* add sentry widget * Update CHANGELOG.md * Update CHANGELOG.md
1 parent bd1b990 commit 4133230

10 files changed

+258
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Features
66

7+
- Add `SentryWidget` ([#1846](https://github.com/getsentry/sentry-dart/pull/1846))
8+
- Prefer to use `SentryWidget` now instead of `SentryScreenshotWidget` and `SentryUserInteractionWidget` directly
9+
- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
710
- Performance monitoring support for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
811
- Tracing without performance for Dio integration ([#1837](https://github.com/getsentry/sentry-dart/pull/1837))
912
- Accept `Map<String, dynamic>` in `Hint` class ([#1807](https://github.com/getsentry/sentry-dart/pull/1807))

flutter/example/lib/main.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
4343
Future<void> main() async {
4444
await setupSentry(
4545
() => runApp(
46-
SentryScreenshotWidget(
47-
child: SentryUserInteractionWidget(
48-
child: DefaultAssetBundle(
49-
bundle: SentryAssetBundle(),
50-
child: const MyApp(),
51-
),
46+
SentryWidget(
47+
child: DefaultAssetBundle(
48+
bundle: SentryAssetBundle(),
49+
child: const MyApp(),
5250
),
5351
),
5452
),

flutter/lib/sentry_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export 'src/screenshot/sentry_screenshot_widget.dart';
1515
export 'src/screenshot/sentry_screenshot_quality.dart';
1616
export 'src/user_interaction/sentry_user_interaction_widget.dart';
1717
export 'src/binding_wrapper.dart';
18+
export 'src/sentry_widget.dart';

flutter/lib/src/screenshot/sentry_screenshot_widget.dart

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:flutter/material.dart';
22
import 'package:meta/meta.dart';
33

4+
import '../../sentry_flutter.dart';
5+
46
/// Key which is used to identify the [RepaintBoundary]
57
@internal
68
final sentryScreenshotWidgetGlobalKey =
@@ -22,20 +24,37 @@ final sentryScreenshotWidgetGlobalKey =
2224
/// - You can only have one [SentryScreenshotWidget] widget in your widget tree at all
2325
/// times.
2426
class SentryScreenshotWidget extends StatefulWidget {
25-
const SentryScreenshotWidget({super.key, required this.child});
26-
2727
final Widget child;
28+
late final Hub _hub;
29+
30+
SentryFlutterOptions? get _options =>
31+
// ignore: invalid_use_of_internal_member
32+
_hub.options is SentryFlutterOptions
33+
// ignore: invalid_use_of_internal_member
34+
? _hub.options as SentryFlutterOptions
35+
: null;
36+
37+
SentryScreenshotWidget({
38+
super.key,
39+
required this.child,
40+
@internal Hub? hub,
41+
}) : _hub = hub ?? HubAdapter();
2842

2943
@override
3044
_SentryScreenshotWidgetState createState() => _SentryScreenshotWidgetState();
3145
}
3246

3347
class _SentryScreenshotWidgetState extends State<SentryScreenshotWidget> {
48+
SentryFlutterOptions? get _options => widget._options;
49+
3450
@override
3551
Widget build(BuildContext context) {
36-
return RepaintBoundary(
37-
key: sentryScreenshotWidgetGlobalKey,
38-
child: widget.child,
39-
);
52+
if (_options?.attachScreenshot ?? false) {
53+
return RepaintBoundary(
54+
key: sentryScreenshotWidgetGlobalKey,
55+
child: widget.child,
56+
);
57+
}
58+
return widget.child;
4059
}
4160
}

flutter/lib/src/sentry_widget.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:flutter/cupertino.dart';
2+
import '../sentry_flutter.dart';
3+
4+
/// This widget serves as a wrapper to include Sentry widgets such
5+
/// as [SentryScreenshotWidget] and [SentryUserInteractionWidget].
6+
class SentryWidget extends StatefulWidget {
7+
final Widget child;
8+
9+
const SentryWidget({super.key, required this.child});
10+
11+
@override
12+
_SentryWidgetState createState() => _SentryWidgetState();
13+
}
14+
15+
class _SentryWidgetState extends State<SentryWidget> {
16+
@override
17+
Widget build(BuildContext context) {
18+
Widget content = widget.child;
19+
content = SentryScreenshotWidget(child: content);
20+
content = SentryUserInteractionWidget(child: content);
21+
return content;
22+
}
23+
}

flutter/lib/src/user_interaction/sentry_user_interaction_widget.dart

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,16 @@ class _SentryUserInteractionWidgetState
281281

282282
@override
283283
Widget build(BuildContext context) {
284-
return Listener(
285-
behavior: HitTestBehavior.translucent,
286-
onPointerDown: _onPointerDown,
287-
onPointerUp: _onPointerUp,
288-
child: widget.child,
289-
);
284+
if ((_options?.enableUserInteractionTracing ?? true) ||
285+
(_options?.enableUserInteractionBreadcrumbs ?? true)) {
286+
return Listener(
287+
behavior: HitTestBehavior.translucent,
288+
onPointerDown: _onPointerDown,
289+
onPointerUp: _onPointerUp,
290+
child: widget.child,
291+
);
292+
}
293+
return widget.child;
290294
}
291295

292296
void _onPointerDown(PointerDownEvent event) {

flutter/test/event_processor/screenshot_event_processor_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void main() {
3333
final sut = fixture.getSut(renderer, isWeb);
3434

3535
await tester.pumpWidget(SentryScreenshotWidget(
36+
hub: fixture.hub,
3637
child: Text('Catching Pokémon is a snap!',
3738
textDirection: TextDirection.ltr)));
3839

@@ -178,8 +179,14 @@ void main() {
178179
}
179180

180181
class Fixture {
182+
late Hub hub;
181183
SentryFlutterOptions options = SentryFlutterOptions(dsn: fakeDsn);
182184

185+
Fixture() {
186+
options.attachScreenshot = true;
187+
hub = Hub(options);
188+
}
189+
183190
ScreenshotEventProcessor getSut(
184191
FlutterRenderer? flutterRenderer, bool isWeb) {
185192
options.rendererWrapper = MockRendererWrapper(flutterRenderer);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@TestOn('vm')
2+
// ignore_for_file: invalid_use_of_internal_member
3+
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:sentry_flutter/sentry_flutter.dart';
7+
8+
import '../mocks.dart';
9+
10+
void main() {
11+
late Fixture fixture;
12+
setUp(() {
13+
fixture = Fixture();
14+
TestWidgetsFlutterBinding.ensureInitialized();
15+
});
16+
17+
testWidgets(
18+
'$SentryScreenshotWidget does not apply when attachScreenshot is false',
19+
(tester) async {
20+
await tester.pumpWidget(
21+
fixture.getSut(
22+
attachScreenshot: false,
23+
),
24+
);
25+
26+
final widget = find.byType(MyApp);
27+
final repaintBoundaryFinder = find.descendant(
28+
of: widget,
29+
matching: find.byType(RepaintBoundary),
30+
);
31+
expect(repaintBoundaryFinder, findsNothing);
32+
},
33+
);
34+
35+
testWidgets(
36+
'$SentryScreenshotWidget applies when attachScreenshot is true',
37+
(tester) async {
38+
await tester.pumpWidget(
39+
fixture.getSut(
40+
attachScreenshot: true,
41+
),
42+
);
43+
44+
final widget = find.byType(MyApp);
45+
final repaintBoundaryFinder = find.ancestor(
46+
of: widget,
47+
matching: find.byKey(sentryScreenshotWidgetGlobalKey),
48+
);
49+
expect(repaintBoundaryFinder, findsOneWidget);
50+
},
51+
);
52+
}
53+
54+
class Fixture {
55+
final _options = SentryFlutterOptions(dsn: fakeDsn);
56+
late Hub hub;
57+
58+
SentryScreenshotWidget getSut({
59+
bool attachScreenshot = false,
60+
}) {
61+
_options.attachScreenshot = attachScreenshot;
62+
63+
hub = Hub(_options);
64+
65+
return SentryScreenshotWidget(
66+
hub: hub,
67+
child: MaterialApp(home: MyApp()),
68+
);
69+
}
70+
}
71+
72+
class MyApp extends StatelessWidget {
73+
const MyApp({super.key});
74+
75+
@override
76+
Widget build(BuildContext context) {
77+
return const Text('test');
78+
}
79+
}

flutter/test/sentry_widget_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:sentry_flutter/sentry_flutter.dart';
4+
5+
void main() {
6+
group('SentryWidget', () {
7+
const testChild = Text('Test Child');
8+
9+
setUp(() async {
10+
TestWidgetsFlutterBinding.ensureInitialized();
11+
});
12+
13+
testWidgets('SentryWidget wraps child with SentryUserInteractionWidget',
14+
(WidgetTester tester) async {
15+
await tester.pumpWidget(
16+
MaterialApp(
17+
home: SentryWidget(child: testChild),
18+
),
19+
);
20+
21+
expect(find.byType(SentryUserInteractionWidget), findsOneWidget);
22+
expect(find.byWidget(testChild), findsOneWidget);
23+
});
24+
25+
testWidgets('SentryWidget wraps child with SentryScreenshotWidget',
26+
(WidgetTester tester) async {
27+
await tester.pumpWidget(
28+
MaterialApp(
29+
home: SentryWidget(child: testChild),
30+
),
31+
);
32+
33+
expect(find.byType(SentryScreenshotWidget), findsOneWidget);
34+
expect(find.byWidget(testChild), findsOneWidget);
35+
});
36+
});
37+
}

flutter/test/user_interaction/sentry_user_interaction_widget_test.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,75 @@ void main() {
3232
},
3333
);
3434

35+
testWidgets(
36+
'$SentryUserInteractionWidget does not apply when enableUserInteractionTracing and enableUserInteractionBreadcrumbs is false',
37+
(tester) async {
38+
await tester.runAsync(() async {
39+
await tester.pumpWidget(
40+
fixture.getSut(
41+
enableUserInteractionTracing: false,
42+
enableUserInteractionBreadcrumbs: false,
43+
),
44+
);
45+
final specificChildFinder = find.byType(MyApp);
46+
47+
expect(
48+
find.ancestor(
49+
of: specificChildFinder,
50+
matching: find.byType(Listener),
51+
),
52+
findsNothing,
53+
);
54+
});
55+
},
56+
);
57+
58+
testWidgets(
59+
'$SentryUserInteractionWidget does apply when enableUserInteractionTracing is true',
60+
(tester) async {
61+
await tester.runAsync(() async {
62+
await tester.pumpWidget(
63+
fixture.getSut(
64+
enableUserInteractionTracing: true,
65+
enableUserInteractionBreadcrumbs: false,
66+
),
67+
);
68+
final specificChildFinder = find.byType(MyApp);
69+
70+
expect(
71+
find.ancestor(
72+
of: specificChildFinder,
73+
matching: find.byType(Listener),
74+
),
75+
findsOne,
76+
);
77+
});
78+
},
79+
);
80+
81+
testWidgets(
82+
'$SentryUserInteractionWidget does apply when enableUserInteractionBreadcrumbs is true',
83+
(tester) async {
84+
await tester.runAsync(() async {
85+
await tester.pumpWidget(
86+
fixture.getSut(
87+
enableUserInteractionTracing: false,
88+
enableUserInteractionBreadcrumbs: true,
89+
),
90+
);
91+
final specificChildFinder = find.byType(MyApp);
92+
93+
expect(
94+
find.ancestor(
95+
of: specificChildFinder,
96+
matching: find.byType(Listener),
97+
),
98+
findsOne,
99+
);
100+
});
101+
},
102+
);
103+
35104
group('$SentryUserInteractionWidget crumbs', () {
36105
testWidgets('Add crumb for MaterialButton', (tester) async {
37106
await tester.runAsync(() async {

0 commit comments

Comments
 (0)