Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Features

- Add `SentryWidget` ([#1846](https://github.com/getsentry/sentry-dart/pull/1846))
- Prefer to use `SentryWidget` now instead of `SentryScreenshotWidget` and `SentryUserInteractionWidget` directly
- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
Copy link
Contributor

@krystofwoldrich krystofwoldrich Jan 29, 2024

Choose a reason for hiding this comment

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

- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) was released in 7.15.0. I'm not sure why it wasn't in the changelog

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was a mistake. We didn't add isar to the craft file so it was never published, I removed it from the 7.15.0 changelog

- Performance monitoring support for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
- Tracing without performance for Dio integration ([#1837](https://github.com/getsentry/sentry-dart/pull/1837))
- Accept `Map<String, dynamic>` in `Hint` class ([#1807](https://github.com/getsentry/sentry-dart/pull/1807))
Expand Down
10 changes: 4 additions & 6 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@ final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
await setupSentry(
() => runApp(
SentryScreenshotWidget(
child: SentryUserInteractionWidget(
child: DefaultAssetBundle(
bundle: SentryAssetBundle(),
child: const MyApp(),
),
SentryWidget(
child: DefaultAssetBundle(
bundle: SentryAssetBundle(),
child: const MyApp(),
),
),
),
Expand Down
1 change: 1 addition & 0 deletions flutter/lib/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export 'src/screenshot/sentry_screenshot_widget.dart';
export 'src/screenshot/sentry_screenshot_quality.dart';
export 'src/user_interaction/sentry_user_interaction_widget.dart';
export 'src/binding_wrapper.dart';
export 'src/sentry_widget.dart';
31 changes: 25 additions & 6 deletions flutter/lib/src/screenshot/sentry_screenshot_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

import '../../sentry_flutter.dart';

/// Key which is used to identify the [RepaintBoundary]
@internal
final sentryScreenshotWidgetGlobalKey =
Expand All @@ -22,20 +24,37 @@ final sentryScreenshotWidgetGlobalKey =
/// - You can only have one [SentryScreenshotWidget] widget in your widget tree at all
/// times.
class SentryScreenshotWidget extends StatefulWidget {
const SentryScreenshotWidget({super.key, required this.child});

final Widget child;
late final Hub _hub;

SentryFlutterOptions? get _options =>
// ignore: invalid_use_of_internal_member
_hub.options is SentryFlutterOptions
// ignore: invalid_use_of_internal_member
? _hub.options as SentryFlutterOptions
: null;

SentryScreenshotWidget({
super.key,
required this.child,
@internal Hub? hub,
}) : _hub = hub ?? HubAdapter();

@override
_SentryScreenshotWidgetState createState() => _SentryScreenshotWidgetState();
}

class _SentryScreenshotWidgetState extends State<SentryScreenshotWidget> {
SentryFlutterOptions? get _options => widget._options;

@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: sentryScreenshotWidgetGlobalKey,
child: widget.child,
);
if (_options?.attachScreenshot ?? false) {
return RepaintBoundary(
key: sentryScreenshotWidgetGlobalKey,
child: widget.child,
);
}
return widget.child;
}
}
23 changes: 23 additions & 0 deletions flutter/lib/src/sentry_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/cupertino.dart';
import '../sentry_flutter.dart';

/// This widget serves as a wrapper to include Sentry widgets such
/// as [SentryScreenshotWidget] and [SentryUserInteractionWidget].
class SentryWidget extends StatefulWidget {
final Widget child;

const SentryWidget({super.key, required this.child});

@override
_SentryWidgetState createState() => _SentryWidgetState();
}

class _SentryWidgetState extends State<SentryWidget> {
@override
Widget build(BuildContext context) {
Widget content = widget.child;
content = SentryScreenshotWidget(child: content);
content = SentryUserInteractionWidget(child: content);
return content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,16 @@ class _SentryUserInteractionWidgetState

@override
Widget build(BuildContext context) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: widget.child,
);
if ((_options?.enableUserInteractionTracing ?? true) ||
(_options?.enableUserInteractionBreadcrumbs ?? true)) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: widget.child,
);
}
return widget.child;
}

void _onPointerDown(PointerDownEvent event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void main() {
final sut = fixture.getSut(renderer, isWeb);

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

Expand Down Expand Up @@ -178,8 +179,14 @@ void main() {
}

class Fixture {
late Hub hub;
SentryFlutterOptions options = SentryFlutterOptions(dsn: fakeDsn);

Fixture() {
options.attachScreenshot = true;
hub = Hub(options);
}

ScreenshotEventProcessor getSut(
FlutterRenderer? flutterRenderer, bool isWeb) {
options.rendererWrapper = MockRendererWrapper(flutterRenderer);
Expand Down
79 changes: 79 additions & 0 deletions flutter/test/screenshot/sentry_screenshot_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
@TestOn('vm')
// ignore_for_file: invalid_use_of_internal_member

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import '../mocks.dart';

void main() {
late Fixture fixture;
setUp(() {
fixture = Fixture();
TestWidgetsFlutterBinding.ensureInitialized();
});

testWidgets(
'$SentryScreenshotWidget does not apply when attachScreenshot is false',
(tester) async {
await tester.pumpWidget(
fixture.getSut(
attachScreenshot: false,
),
);

final widget = find.byType(MyApp);
final repaintBoundaryFinder = find.descendant(
of: widget,
matching: find.byType(RepaintBoundary),
);
expect(repaintBoundaryFinder, findsNothing);
},
);

testWidgets(
'$SentryScreenshotWidget applies when attachScreenshot is true',
(tester) async {
await tester.pumpWidget(
fixture.getSut(
attachScreenshot: true,
),
);

final widget = find.byType(MyApp);
final repaintBoundaryFinder = find.ancestor(
of: widget,
matching: find.byKey(sentryScreenshotWidgetGlobalKey),
);
expect(repaintBoundaryFinder, findsOneWidget);
},
);
}

class Fixture {
final _options = SentryFlutterOptions(dsn: fakeDsn);
late Hub hub;

SentryScreenshotWidget getSut({
bool attachScreenshot = false,
}) {
_options.attachScreenshot = attachScreenshot;

hub = Hub(_options);

return SentryScreenshotWidget(
hub: hub,
child: MaterialApp(home: MyApp()),
);
}
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return const Text('test');
}
}
37 changes: 37 additions & 0 deletions flutter/test/sentry_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

void main() {
group('SentryWidget', () {
const testChild = Text('Test Child');

setUp(() async {
TestWidgetsFlutterBinding.ensureInitialized();
});

testWidgets('SentryWidget wraps child with SentryUserInteractionWidget',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: SentryWidget(child: testChild),
),
);

expect(find.byType(SentryUserInteractionWidget), findsOneWidget);
expect(find.byWidget(testChild), findsOneWidget);
});

testWidgets('SentryWidget wraps child with SentryScreenshotWidget',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: SentryWidget(child: testChild),
),
);

expect(find.byType(SentryScreenshotWidget), findsOneWidget);
expect(find.byWidget(testChild), findsOneWidget);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,75 @@ void main() {
},
);

testWidgets(
'$SentryUserInteractionWidget does not apply when enableUserInteractionTracing and enableUserInteractionBreadcrumbs is false',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(
fixture.getSut(
enableUserInteractionTracing: false,
enableUserInteractionBreadcrumbs: false,
),
);
final specificChildFinder = find.byType(MyApp);

expect(
find.ancestor(
of: specificChildFinder,
matching: find.byType(Listener),
),
findsNothing,
);
});
},
);

testWidgets(
'$SentryUserInteractionWidget does apply when enableUserInteractionTracing is true',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(
fixture.getSut(
enableUserInteractionTracing: true,
enableUserInteractionBreadcrumbs: false,
),
);
final specificChildFinder = find.byType(MyApp);

expect(
find.ancestor(
of: specificChildFinder,
matching: find.byType(Listener),
),
findsOne,
);
});
},
);

testWidgets(
'$SentryUserInteractionWidget does apply when enableUserInteractionBreadcrumbs is true',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(
fixture.getSut(
enableUserInteractionTracing: false,
enableUserInteractionBreadcrumbs: true,
),
);
final specificChildFinder = find.byType(MyApp);

expect(
find.ancestor(
of: specificChildFinder,
matching: find.byType(Listener),
),
findsOne,
);
});
},
);

group('$SentryUserInteractionWidget crumbs', () {
testWidgets('Add crumb for MaterialButton', (tester) async {
await tester.runAsync(() async {
Expand Down