Skip to content

Add beforeScreenshotCallback to SentryFlutterOptions #1805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726))
- Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800))
- 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))
- Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805))

### Dependencies

Expand Down
26 changes: 26 additions & 0 deletions flutter/lib/src/event_processor/screenshot_event_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ class ScreenshotEventProcessor implements EventProcessor {
_hasSentryScreenshotWidget) {
return event;
}
final beforeScreenshot = _options.beforeScreenshot;
if (beforeScreenshot != null) {
try {
final result = beforeScreenshot(event, hint: hint);
bool takeScreenshot;
if (result is Future<bool>) {
takeScreenshot = await result;
} else {
takeScreenshot = result;
}
if (!takeScreenshot) {
return event;
}
} catch (exception, stackTrace) {
_options.logger(
SentryLevel.error,
'The beforeScreenshot callback threw an exception',
exception: exception,
stackTrace: stackTrace,
);
// ignore: invalid_use_of_internal_member
if (_options.automatedTestMode) {
rethrow;
}
}
}

final renderer = _options.rendererWrapper.getRenderer();

Expand Down
23 changes: 10 additions & 13 deletions flutter/lib/src/native/cocoa/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37603,8 +37603,7 @@ class ObjCBlock_bool_ObjCObject_ffiUnsignedLong_bool extends _ObjCBlockBase {
ObjCBlock_bool_ObjCObject_ffiUnsignedLong_bool.fromFunctionPointer(
SentryCocoa lib,
ffi.Pointer<
ffi
.NativeFunction<
ffi.NativeFunction<
ffi.Bool Function(ffi.Pointer<ObjCObject> arg0,
ffi.UnsignedLong arg1, ffi.Pointer<ffi.Bool> arg2)>>
ptr)
Expand Down Expand Up @@ -42032,17 +42031,15 @@ class ObjCBlock_bool_ObjCObject_bool extends _ObjCBlockBase {
ffi.Pointer<ffi.Bool> arg1)>>
ptr)
: this._(
lib
._newBlock1(
_cFuncTrampoline ??= ffi.Pointer.fromFunction<
ffi.Bool Function(
ffi.Pointer<_ObjCBlock> block,
ffi.Pointer<ObjCObject> arg0,
ffi.Pointer<ffi.Bool> arg1)>(
_ObjCBlock_bool_ObjCObject_bool_fnPtrTrampoline,
false)
.cast(),
ptr.cast()),
lib._newBlock1(
_cFuncTrampoline ??= ffi.Pointer.fromFunction<
ffi.Bool Function(
ffi.Pointer<_ObjCBlock> block,
ffi.Pointer<ObjCObject> arg0,
ffi.Pointer<ffi.Bool> arg1)>(
_ObjCBlock_bool_ObjCObject_bool_fnPtrTrampoline, false)
.cast(),
ptr.cast()),
lib);
static ffi.Pointer<ffi.Void>? _cFuncTrampoline;

Expand Down
17 changes: 16 additions & 1 deletion flutter/lib/src/sentry_flutter_options.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:sentry/sentry.dart';
import 'package:flutter/widgets.dart';

import 'binding_wrapper.dart';
import 'renderer/renderer.dart';
import 'screenshot/sentry_screenshot_quality.dart';
import 'event_processor/screenshot_event_processor.dart';

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

/// Sets a callback which is executed before capturing screenshots. Only
/// relevant if `attachScreenshot` is set to true. When false is returned
/// from the function, no screenshot will be attached.
BeforeScreenshotCallback? beforeScreenshot;

/// Enable or disable automatic breadcrumbs for User interactions Using [Listener]
///
/// Requires adding the [SentryUserInteractionWidget] to the widget tree.
Expand Down Expand Up @@ -289,3 +297,10 @@ class SentryFlutterOptions extends SentryOptions {
/// The [navigatorKey] is used to add information of the currently used locale to the contexts.
GlobalKey<NavigatorState>? navigatorKey;
}

/// Callback being executed in [ScreenshotEventProcessor], deciding if a
/// screenshot should be recorded and attached.
typedef BeforeScreenshotCallback = FutureOr<bool> Function(
SentryEvent event, {
Hint? hint,
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late Fixture fixture;

late SentryEvent event;
late Hint hint;

setUp(() {
fixture = Fixture();
});
Expand All @@ -34,8 +37,8 @@ void main() {
textDirection: TextDirection.ltr)));

final throwable = Exception();
final event = SentryEvent(throwable: throwable);
final hint = Hint();
event = SentryEvent(throwable: throwable);
hint = Hint();
await sut.apply(event, hint: hint);

expect(hint.screenshot != null, added);
Expand Down Expand Up @@ -91,6 +94,87 @@ void main() {
await _addScreenshotAttachment(tester, null,
added: true, isWeb: false, expectedMaxWidthOrHeight: widthOrHeight);
});

group('beforeScreenshot', () {
testWidgets('does add screenshot if beforeScreenshot returns true',
(tester) async {
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
return true;
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: true, isWeb: false);
});

testWidgets('does add screenshot if async beforeScreenshot returns true',
(tester) async {
fixture.options.beforeScreenshot =
(SentryEvent event, {Hint? hint}) async {
await Future<void>.delayed(Duration(milliseconds: 1));
return true;
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: true, isWeb: false);
});

testWidgets('does not add screenshot if beforeScreenshot returns false',
(tester) async {
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
return false;
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: false, isWeb: false);
});

testWidgets(
'does not add screenshot if async beforeScreenshot returns false',
(tester) async {
fixture.options.beforeScreenshot =
(SentryEvent event, {Hint? hint}) async {
await Future<void>.delayed(Duration(milliseconds: 1));
return false;
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: false, isWeb: false);
});

testWidgets('does add screenshot if beforeScreenshot throws',
(tester) async {
fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
throw Error();
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: true, isWeb: false);
});

testWidgets('does add screenshot if async beforeScreenshot throws',
(tester) async {
fixture.options.beforeScreenshot =
(SentryEvent event, {Hint? hint}) async {
await Future<void>.delayed(Duration(milliseconds: 1));
throw Error();
};
await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: true, isWeb: false);
});

testWidgets('passes event & hint to beforeScreenshot callback',
(tester) async {
SentryEvent? beforeScreenshotEvent;
Hint? beforeScreenshotHint;

fixture.options.beforeScreenshot = (SentryEvent event, {Hint? hint}) {
beforeScreenshotEvent = event;
beforeScreenshotHint = hint;
return true;
};

await _addScreenshotAttachment(tester, FlutterRenderer.canvasKit,
added: true, isWeb: false);

expect(beforeScreenshotEvent, event);
expect(beforeScreenshotHint, hint);
});
});
}

class Fixture {
Expand Down