Skip to content
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
2 changes: 1 addition & 1 deletion flutter/lib/src/integrations/native_sdk_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class NativeSdkIntegration implements Integration<SentryFlutterOptions> {
}

try {
await _native.init(options);
await _native.init(hub);
options.sdk.addIntegration('nativeSdkIntegration');
} catch (exception, stackTrace) {
options.logger(
Expand Down
39 changes: 22 additions & 17 deletions flutter/lib/src/native/java/sentry_native_java.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:ui';

import 'package:meta/meta.dart';
Expand All @@ -14,16 +13,13 @@ import '../sentry_native_channel.dart';
@internal
class SentryNativeJava extends SentryNativeChannel {
ScreenshotRecorder? _replayRecorder;
late final SentryFlutterOptions _options;
SentryNativeJava(super.options, super.channel);

@override
Future<void> init(SentryFlutterOptions options) async {
Future<void> init(Hub hub) async {
// We only need these when replay is enabled (session or error capture)
// so let's set it up conditionally. This allows Dart to trim the code.
if (options.experimental.replay.isEnabled) {
_options = options;

// We only need the integration when error-replay capture is enabled.
if ((options.experimental.replay.errorSampleRate ?? 0) > 0) {
options.addEventProcessor(ReplayEventProcessor(this));
Expand All @@ -44,7 +40,7 @@ class SentryNativeJava extends SentryNativeChannel {
),
);

Sentry.configureScope((s) {
hub.configureScope((s) {
// ignore: invalid_use_of_internal_member
s.replayId = replayId;
});
Expand All @@ -54,7 +50,7 @@ class SentryNativeJava extends SentryNativeChannel {
await _replayRecorder?.stop();
_replayRecorder = null;

Sentry.configureScope((s) {
hub.configureScope((s) {
// ignore: invalid_use_of_internal_member
s.replayId = null;
});
Expand All @@ -72,7 +68,14 @@ class SentryNativeJava extends SentryNativeChannel {
});
}

return super.init(options);
return super.init(hub);
}

@override
Future<void> close() async {
await _replayRecorder?.stop();
_replayRecorder = null;
return super.close();
}

void _startRecorder(String cacheDir, ScreenshotRecorderConfig config) {
Expand All @@ -89,33 +92,35 @@ class SentryNativeJava extends SentryNativeChannel {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final filePath = "$cacheDir/$timestamp.png";

_options.logger(
options.logger(
SentryLevel.debug,
'Replay: Saving screenshot to $filePath ('
'${image.width}x${image.height} pixels, '
'${imageData.lengthInBytes} bytes)');
await File(filePath).writeAsBytes(imageData.buffer.asUint8List());

try {
await options.fileSystem
.file(filePath)
.writeAsBytes(imageData.buffer.asUint8List(), flush: true);

await channel.invokeMethod(
'addReplayScreenshot',
{'path': filePath, 'timestamp': timestamp},
);
} catch (error, stackTrace) {
_options.logger(
options.logger(
SentryLevel.error,
'Native call `addReplayScreenshot` failed',
exception: error,
stackTrace: stackTrace,
);
// ignore: invalid_use_of_internal_member
if (options.automatedTestMode) {
rethrow;
}
}
}
};

_replayRecorder = ScreenshotRecorder(
config,
callback,
_options,
)..start();
_replayRecorder = ScreenshotRecorder(config, callback, options)..start();
}
}
2 changes: 1 addition & 1 deletion flutter/lib/src/native/sentry_native_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'native_frames.dart';
/// Provide typed methods to access native layer.
@internal
abstract class SentryNativeBinding {
Future<void> init(SentryFlutterOptions options);
Future<void> init(Hub hub);

Future<void> close();

Expand Down
3 changes: 1 addition & 2 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ class SentryNativeChannel
: channel = SentrySafeMethodChannel(channel, options);

@override
Future<void> init(SentryFlutterOptions options) async {
assert(this.options == options);
Future<void> init(Hub hub) async {
return channel.invokeMethod('initNativeSdk', <String, dynamic>{
'dsn': options.dsn,
'debug': options.debug,
Expand Down
8 changes: 7 additions & 1 deletion flutter/lib/src/replay/recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ class ScreenshotRecorder {
final ScreenshotRecorderCallback _callback;
final SentryLogger _logger;
final SentryReplayOptions _options;
final bool rethrowExceptions;
WidgetFilter? _widgetFilter;
late final Scheduler _scheduler;
bool warningLogged = false;

ScreenshotRecorder(this._config, this._callback, SentryFlutterOptions options)
: _logger = options.logger,
_options = options.experimental.replay {
_options = options.experimental.replay,
// ignore: invalid_use_of_internal_member
rethrowExceptions = options.automatedTestMode {
final frameDuration = Duration(milliseconds: 1000 ~/ _config.frameRate);
_scheduler = Scheduler(frameDuration, _capture,
options.bindingUtils.instance!.addPostFrameCallback);
Expand Down Expand Up @@ -121,6 +124,9 @@ class ScreenshotRecorder {
} catch (e, stackTrace) {
_logger(SentryLevel.error, "Replay: failed to capture screenshot.",
exception: e, stackTrace: stackTrace);
if (rethrowExceptions) {
rethrow;
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions flutter/lib/src/sentry_flutter_options.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:async';

import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:meta/meta.dart' as meta;
import 'package:sentry/sentry.dart';
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -329,6 +331,9 @@ class SentryFlutterOptions extends SentryOptions {
/// The [navigatorKey] is used to add information of the currently used locale to the contexts.
GlobalKey<NavigatorState>? navigatorKey;

@meta.internal
FileSystem fileSystem = LocalFileSystem();

/// Configuration of experimental features that may change or be removed
/// without prior notice. Additionally, these features may not be ready for
/// production use yet.
Expand Down
1 change: 1 addition & 0 deletions flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies:
package_info_plus: '>=1.0.0'
meta: ^1.3.0
ffi: ^2.0.0
file: '>=6.1.4'

dev_dependencies:
build_runner: ^2.4.2
Expand Down
5 changes: 3 additions & 2 deletions flutter/test/integrations/init_native_sdk_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:sentry_flutter/src/native/sentry_native_channel.dart';
import 'package:sentry_flutter/src/version.dart';

import '../mocks.dart';
import '../mocks.mocks.dart';

void main() {
late Fixture fixture;
Expand All @@ -25,7 +26,7 @@ void main() {
});
var sut = fixture.getSut(channel);

await sut.init(fixture.options);
await sut.init(MockHub());

channel.setMethodCallHandler(null);

Expand Down Expand Up @@ -115,7 +116,7 @@ void main() {
fixture.options.sdk.addIntegration('foo');
fixture.options.sdk.addPackage('bar', '1');

await sut.init(fixture.options);
await sut.init(MockHub());

channel.setMethodCallHandler(null);

Expand Down
2 changes: 1 addition & 1 deletion flutter/test/integrations/native_sdk_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ void main() {

class _ThrowingMockSentryNative extends MockSentryNativeBinding {
@override
Future<void> init(SentryFlutterOptions? options) async {
Future<void> init(Hub? hub) async {
throw Exception();
}
}
29 changes: 29 additions & 0 deletions flutter/test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,32 @@ final fakeFrameDurations = [
Duration(milliseconds: 40),
Duration(milliseconds: 710),
];

@GenerateMocks([Callbacks])
abstract class Callbacks {
Future<Object?>? methodCallHandler(String method, [dynamic arguments]);
}

class NativeChannelFixture {
late final MethodChannel channel;
late final Future<Object?>? Function(String method, [dynamic arguments])
handler;
static TestDefaultBinaryMessenger get _messenger =>
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;

NativeChannelFixture() {
TestWidgetsFlutterBinding.ensureInitialized();
channel = MethodChannel('test.channel', StandardMethodCodec(), _messenger);
handler = MockCallbacks().methodCallHandler;
_messenger.setMockMethodCallHandler(
channel, (call) => handler(call.method, call.arguments));
}

// Mock this call as if it was invoked by the native side.
Future<ByteData?> invokeFromNative(String method, [dynamic arguments]) async {
final call =
StandardMethodCodec().encodeMethodCall(MethodCall(method, arguments));
return _messenger.handlePlatformMessage(
channel.name, call, (ByteData? data) {});
}
}
Loading