Skip to content

Commit d0312c9

Browse files
authored
Add beforeScreenshotCallback to SentryFlutterOptions (#1805)
1 parent 1cdcacf commit d0312c9

File tree

5 files changed

+139
-16
lines changed

5 files changed

+139
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
1212
- Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800))
1313
- Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799))
14+
- Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805))
1415

1516
### Dependencies
1617

flutter/lib/src/event_processor/screenshot_event_processor.dart

+26
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@ class ScreenshotEventProcessor implements EventProcessor {
3030
_hasSentryScreenshotWidget) {
3131
return event;
3232
}
33+
final beforeScreenshot = _options.beforeScreenshot;
34+
if (beforeScreenshot != null) {
35+
try {
36+
final result = beforeScreenshot(event, hint: hint);
37+
bool takeScreenshot;
38+
if (result is Future<bool>) {
39+
takeScreenshot = await result;
40+
} else {
41+
takeScreenshot = result;
42+
}
43+
if (!takeScreenshot) {
44+
return event;
45+
}
46+
} catch (exception, stackTrace) {
47+
_options.logger(
48+
SentryLevel.error,
49+
'The beforeScreenshot callback threw an exception',
50+
exception: exception,
51+
stackTrace: stackTrace,
52+
);
53+
// ignore: invalid_use_of_internal_member
54+
if (_options.automatedTestMode) {
55+
rethrow;
56+
}
57+
}
58+
}
3359

3460
final renderer = _options.rendererWrapper.getRenderer();
3561

flutter/lib/src/native/cocoa/binding.dart

+10-13
Original file line numberDiff line numberDiff line change
@@ -37603,8 +37603,7 @@ class ObjCBlock_bool_ObjCObject_ffiUnsignedLong_bool extends _ObjCBlockBase {
3760337603
ObjCBlock_bool_ObjCObject_ffiUnsignedLong_bool.fromFunctionPointer(
3760437604
SentryCocoa lib,
3760537605
ffi.Pointer<
37606-
ffi
37607-
.NativeFunction<
37606+
ffi.NativeFunction<
3760837607
ffi.Bool Function(ffi.Pointer<ObjCObject> arg0,
3760937608
ffi.UnsignedLong arg1, ffi.Pointer<ffi.Bool> arg2)>>
3761037609
ptr)
@@ -42032,17 +42031,15 @@ class ObjCBlock_bool_ObjCObject_bool extends _ObjCBlockBase {
4203242031
ffi.Pointer<ffi.Bool> arg1)>>
4203342032
ptr)
4203442033
: this._(
42035-
lib
42036-
._newBlock1(
42037-
_cFuncTrampoline ??= ffi.Pointer.fromFunction<
42038-
ffi.Bool Function(
42039-
ffi.Pointer<_ObjCBlock> block,
42040-
ffi.Pointer<ObjCObject> arg0,
42041-
ffi.Pointer<ffi.Bool> arg1)>(
42042-
_ObjCBlock_bool_ObjCObject_bool_fnPtrTrampoline,
42043-
false)
42044-
.cast(),
42045-
ptr.cast()),
42034+
lib._newBlock1(
42035+
_cFuncTrampoline ??= ffi.Pointer.fromFunction<
42036+
ffi.Bool Function(
42037+
ffi.Pointer<_ObjCBlock> block,
42038+
ffi.Pointer<ObjCObject> arg0,
42039+
ffi.Pointer<ffi.Bool> arg1)>(
42040+
_ObjCBlock_bool_ObjCObject_bool_fnPtrTrampoline, false)
42041+
.cast(),
42042+
ptr.cast()),
4204642043
lib);
4204742044
static ffi.Pointer<ffi.Void>? _cFuncTrampoline;
4204842045

flutter/lib/src/sentry_flutter_options.dart

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import 'dart:async';
2+
13
import 'package:meta/meta.dart';
24
import 'package:sentry/sentry.dart';
35
import 'package:flutter/widgets.dart';
46

57
import 'binding_wrapper.dart';
68
import 'renderer/renderer.dart';
79
import 'screenshot/sentry_screenshot_quality.dart';
10+
import 'event_processor/screenshot_event_processor.dart';
811

9-
/// This class adds options which are only availble in a Flutter environment.
12+
/// This class adds options which are only available in a Flutter environment.
1013
/// Note that some of these options require native Sentry integration, which is
1114
/// not available on all platforms.
1215
class SentryFlutterOptions extends SentryOptions {
@@ -169,6 +172,11 @@ class SentryFlutterOptions extends SentryOptions {
169172
/// Only attach a screenshot when the app is resumed.
170173
bool attachScreenshotOnlyWhenResumed = false;
171174

175+
/// Sets a callback which is executed before capturing screenshots. Only
176+
/// relevant if `attachScreenshot` is set to true. When false is returned
177+
/// from the function, no screenshot will be attached.
178+
BeforeScreenshotCallback? beforeScreenshot;
179+
172180
/// Enable or disable automatic breadcrumbs for User interactions Using [Listener]
173181
///
174182
/// Requires adding the [SentryUserInteractionWidget] to the widget tree.
@@ -289,3 +297,10 @@ class SentryFlutterOptions extends SentryOptions {
289297
/// The [navigatorKey] is used to add information of the currently used locale to the contexts.
290298
GlobalKey<NavigatorState>? navigatorKey;
291299
}
300+
301+
/// Callback being executed in [ScreenshotEventProcessor], deciding if a
302+
/// screenshot should be recorded and attached.
303+
typedef BeforeScreenshotCallback = FutureOr<bool> Function(
304+
SentryEvent event, {
305+
Hint? hint,
306+
});

flutter/test/event_processor/screenshot_event_processor_test.dart

+86-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ void main() {
1414
TestWidgetsFlutterBinding.ensureInitialized();
1515
late Fixture fixture;
1616

17+
late SentryEvent event;
18+
late Hint hint;
19+
1720
setUp(() {
1821
fixture = Fixture();
1922
});
@@ -34,8 +37,8 @@ void main() {
3437
textDirection: TextDirection.ltr)));
3538

3639
final throwable = Exception();
37-
final event = SentryEvent(throwable: throwable);
38-
final hint = Hint();
40+
event = SentryEvent(throwable: throwable);
41+
hint = Hint();
3942
await sut.apply(event, hint: hint);
4043

4144
expect(hint.screenshot != null, added);
@@ -91,6 +94,87 @@ void main() {
9194
await _addScreenshotAttachment(tester, null,
9295
added: true, isWeb: false, expectedMaxWidthOrHeight: widthOrHeight);
9396
});
97+
98+
group('beforeScreenshot', () {
99+
testWidgets('does add screenshot if beforeScreenshot returns true',
100+
(tester) async {
101+
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
102+
return true;
103+
};
104+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
105+
added: true, isWeb: false);
106+
});
107+
108+
testWidgets('does add screenshot if async beforeScreenshot returns true',
109+
(tester) async {
110+
fixture.options.beforeScreenshot =
111+
(SentryEvent event, {Hint? hint}) async {
112+
await Future<void>.delayed(Duration(milliseconds: 1));
113+
return true;
114+
};
115+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
116+
added: true, isWeb: false);
117+
});
118+
119+
testWidgets('does not add screenshot if beforeScreenshot returns false',
120+
(tester) async {
121+
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
122+
return false;
123+
};
124+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
125+
added: false, isWeb: false);
126+
});
127+
128+
testWidgets(
129+
'does not add screenshot if async beforeScreenshot returns false',
130+
(tester) async {
131+
fixture.options.beforeScreenshot =
132+
(SentryEvent event, {Hint? hint}) async {
133+
await Future<void>.delayed(Duration(milliseconds: 1));
134+
return false;
135+
};
136+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
137+
added: false, isWeb: false);
138+
});
139+
140+
testWidgets('does add screenshot if beforeScreenshot throws',
141+
(tester) async {
142+
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
143+
throw Error();
144+
};
145+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
146+
added: true, isWeb: false);
147+
});
148+
149+
testWidgets('does add screenshot if async beforeScreenshot throws',
150+
(tester) async {
151+
fixture.options.beforeScreenshot =
152+
(SentryEvent event, {Hint? hint}) async {
153+
await Future<void>.delayed(Duration(milliseconds: 1));
154+
throw Error();
155+
};
156+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
157+
added: true, isWeb: false);
158+
});
159+
160+
testWidgets('passes event & hint to beforeScreenshot callback',
161+
(tester) async {
162+
SentryEvent? beforeScreenshotEvent;
163+
Hint? beforeScreenshotHint;
164+
165+
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
166+
beforeScreenshotEvent = event;
167+
beforeScreenshotHint = hint;
168+
return true;
169+
};
170+
171+
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
172+
added: true, isWeb: false);
173+
174+
expect(beforeScreenshotEvent, event);
175+
expect(beforeScreenshotHint, hint);
176+
});
177+
});
94178
}
95179

96180
class Fixture {

0 commit comments

Comments
 (0)