From 03f81187e23a01845d06f3b0719ebfbf48dfc7aa Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 3 Sep 2025 01:25:16 +0200 Subject: [PATCH 01/78] Update --- .../native/java/android_envelope_worker.dart | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/flutter/lib/src/native/java/android_envelope_worker.dart diff --git a/packages/flutter/lib/src/native/java/android_envelope_worker.dart b/packages/flutter/lib/src/native/java/android_envelope_worker.dart new file mode 100644 index 0000000000..b87d8cf92f --- /dev/null +++ b/packages/flutter/lib/src/native/java/android_envelope_worker.dart @@ -0,0 +1,96 @@ +class AndroidEnvelopeWorker extends WorkerIsolate { + AndroidEnvelopeWorker(super.config); + + static Future spawn(WorkerConfig config) async { + // 1) Create a ReceivePort the worker can talk to immediately. + final init = ReceivePort(); + + // 2) Pass BOTH the config and init.sendPort into the isolate. + await Isolate.spawn<(WorkerConfig, SendPort)>( + AndroidEnvelopeWorker.entryPoint, + (config, init.sendPort), + debugName: 'SentryAndroidEnvelopeWorker', + ); + + // 3) First message from worker is its inbox SendPort. + final SendPort workerInbox = await init.first as SendPort; + return workerInbox; + } + + void startMessageLoop() { + final receivePort = ReceivePort(); + + // Handshake: tell host how to send messages to this worker. + hostPort.send(receivePort.sendPort); + + receivePort.listen((message) { + try { + processMessage(message); + } catch (e, st) { + // sendError(e, st); + } + }); + } + + void processMessage(dynamic message) { + IsolateDiagnosticLog.log(SentryLevel.warning, + 'EnvelopeWorker invoked; starting captureEnvelope'); + + if (message is TransferableTypedData) { + final envelopeData = message.materialize().asUint8List(); + _captureEnvelope(envelopeData, false); + } + } + + void _captureEnvelope( + Uint8List envelopeData, bool containsUnhandledException) { + JObject? id; + JByteArray? byteArray; + try { + byteArray = JByteArray.from(envelopeData); + id = native.InternalSentrySdk.captureEnvelope( + byteArray, containsUnhandledException); + + if (id == null) { + IsolateDiagnosticLog.log(SentryLevel.error, + 'Native Android SDK returned null id when capturing envelope'); + } + } catch (exception, stackTrace) { + IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', + exception: exception, stackTrace: stackTrace); + // if (options.automatedTestMode) { + // rethrow; + // } + } finally { + byteArray?.release(); + id?.release(); + } + } + + void send(Object message) => hostPort.send(message); + + static void entryPoint((WorkerConfig, SendPort) args) { + final (config, hostPort) = args; + + final level = config.environment['logLevel'] as SentryLevel; + final debug = config.environment['debug'] as bool; + IsolateDiagnosticLog.configure(debug: debug, level: level); + IsolateDiagnosticLog.log( + SentryLevel.warning, 'AndroidEnvelopeWorker started'); + + // Construct worker with the hostPort we just received. + final worker = AndroidEnvelopeWorker(config); + + // Start loop and complete the handshake by sending our inbox SendPort. + final receivePort = ReceivePort(); + hostPort.send(receivePort.sendPort); // <- completes init.first in spawn() + + // Option A: reuse startMessageLoop’s listener: + receivePort.listen(worker.processMessage); + + // Option B: if you prefer your existing method, you can: + // worker.startMessageLoop(); + // but then remove the duplicate handshake above from startMessageLoop, or + // let startMessageLoop accept the already-created receivePort. + } +} From 6928f3a92a3c43f0b1c64618f7445dc164bdd84a Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 3 Sep 2025 15:03:19 +0200 Subject: [PATCH 02/78] Update --- .../native/java/android_envelope_worker.dart | 107 +++++----- .../src/native/java/sentry_native_java.dart | 31 +-- packages/flutter/lib/src/worker_isolate.dart | 201 ++++++++++++++++++ 3 files changed, 268 insertions(+), 71 deletions(-) create mode 100644 packages/flutter/lib/src/worker_isolate.dart diff --git a/packages/flutter/lib/src/native/java/android_envelope_worker.dart b/packages/flutter/lib/src/native/java/android_envelope_worker.dart index b87d8cf92f..7e2d76e88e 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_worker.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_worker.dart @@ -1,38 +1,69 @@ -class AndroidEnvelopeWorker extends WorkerIsolate { - AndroidEnvelopeWorker(super.config); +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; - static Future spawn(WorkerConfig config) async { - // 1) Create a ReceivePort the worker can talk to immediately. - final init = ReceivePort(); +import 'package:jni/jni.dart'; +import 'package:meta/meta.dart'; - // 2) Pass BOTH the config and init.sendPort into the isolate. - await Isolate.spawn<(WorkerConfig, SendPort)>( - AndroidEnvelopeWorker.entryPoint, - (config, init.sendPort), +import '../../../sentry_flutter.dart'; +import '../../worker_isolate.dart'; +import 'binding.dart' as native; + +/// Host-side proxy for the Android envelope worker isolate. +class AndroidEnvelopeWorker { + AndroidEnvelopeWorker(this._options); + + final SentryFlutterOptions _options; + + WorkerClient? _client; + + @internal // visible for testing/mocking + static AndroidEnvelopeWorker Function(SentryFlutterOptions) factory = + AndroidEnvelopeWorker.new; + + Future start() async { + if (_client != null) return; + final config = WorkerConfig( + debug: _options.debug, + logLevel: _options.diagnosticLevel, debugName: 'SentryAndroidEnvelopeWorker', ); - - // 3) First message from worker is its inbox SendPort. - final SendPort workerInbox = await init.first as SendPort; - return workerInbox; + final (_, port) = await WorkerIsolate.spawn( + config, + AndroidEnvelopeWorkerIsolate.entryPoint, + ); + _client = WorkerClient(port); } - void startMessageLoop() { - final receivePort = ReceivePort(); + Future stop() async { + _close(); + } - // Handshake: tell host how to send messages to this worker. - hostPort.send(receivePort.sendPort); + /// Fire-and-forget send of envelope bytes to the worker. + void captureEnvelope(Uint8List envelopeData) { + final client = _client; + if (client == null) { + _options.log( + SentryLevel.warning, + 'AndroidEnvelopeWorker.captureEnvelope called before start; dropping', + ); + return; + } + client.send(TransferableTypedData.fromList([envelopeData])); + } - receivePort.listen((message) { - try { - processMessage(message); - } catch (e, st) { - // sendError(e, st); - } - }); + void _close() { + _client?.close(); + _client = null; } +} + +/// Worker isolate implementation handling envelope capture via JNI. +class AndroidEnvelopeWorkerIsolate extends WorkerIsolate { + AndroidEnvelopeWorkerIsolate(super.host); - void processMessage(dynamic message) { + @override + FutureOr handleMessage(Object? message) { IsolateDiagnosticLog.log(SentryLevel.warning, 'EnvelopeWorker invoked; starting captureEnvelope'); @@ -67,30 +98,8 @@ class AndroidEnvelopeWorker extends WorkerIsolate { } } - void send(Object message) => hostPort.send(message); - static void entryPoint((WorkerConfig, SendPort) args) { - final (config, hostPort) = args; - - final level = config.environment['logLevel'] as SentryLevel; - final debug = config.environment['debug'] as bool; - IsolateDiagnosticLog.configure(debug: debug, level: level); - IsolateDiagnosticLog.log( - SentryLevel.warning, 'AndroidEnvelopeWorker started'); - - // Construct worker with the hostPort we just received. - final worker = AndroidEnvelopeWorker(config); - - // Start loop and complete the handshake by sending our inbox SendPort. - final receivePort = ReceivePort(); - hostPort.send(receivePort.sendPort); // <- completes init.first in spawn() - - // Option A: reuse startMessageLoop’s listener: - receivePort.listen(worker.processMessage); - - // Option B: if you prefer your existing method, you can: - // worker.startMessageLoop(); - // but then remove the duplicate handshake above from startMessageLoop, or - // let startMessageLoop accept the already-created receivePort. + final (config, host) = args; + WorkerIsolate.bootstrap(config, host, AndroidEnvelopeWorkerIsolate(host)); } } diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 0c57f79179..509a86d66f 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -6,8 +6,10 @@ import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; +import '../../worker_isolate.dart'; import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; +import 'android_envelope_worker.dart'; import 'android_replay_recorder.dart'; import 'binding.dart' as native; @@ -71,34 +73,18 @@ class SentryNativeJava extends SentryNativeChannel { }); } + envelopeWorker = AndroidEnvelopeWorker.factory(options); + await envelopeWorker.start(); + return super.init(hub); } + late AndroidEnvelopeWorker envelopeWorker; + @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - JObject? id; - JByteArray? byteArray; - try { - byteArray = JByteArray.from(envelopeData); - id = native.InternalSentrySdk.captureEnvelope( - byteArray, containsUnhandledException); - - if (id == null) { - options.log(SentryLevel.error, - 'Native Android SDK returned null id when capturing envelope'); - } - } catch (exception, stackTrace) { - options.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, stackTrace: stackTrace); - - if (options.automatedTestMode) { - rethrow; - } - } finally { - byteArray?.release(); - id?.release(); - } + envelopeWorker.captureEnvelope(envelopeData); } @override @@ -189,6 +175,7 @@ class SentryNativeJava extends SentryNativeChannel { @override Future close() async { await _replayRecorder?.stop(); + envelopeWorker.close(); return super.close(); } } diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/worker_isolate.dart new file mode 100644 index 0000000000..db15ab3932 --- /dev/null +++ b/packages/flutter/lib/src/worker_isolate.dart @@ -0,0 +1,201 @@ +import 'dart:developer' as developer; +import 'dart:async'; +import 'dart:isolate'; + +import 'package:meta/meta.dart'; + +import '../sentry_flutter.dart'; + +class WorkerConfig { + final bool debug; + final SentryLevel logLevel; + final String? debugName; + + const WorkerConfig({ + required this.debug, + required this.logLevel, + this.debugName, + }); +} + +class IsolateDiagnosticLog { + IsolateDiagnosticLog._(); + + static late final bool _debug; + static late final SentryLevel _level; + + static void configure({required bool debug, required SentryLevel level}) { + _debug = debug; + _level = level; + } + + static void log( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + if (_isEnabled(level)) { + developer.log( + '[${level.name}] $message', + level: level.toDartLogLevel(), + name: logger ?? 'sentry', + time: DateTime.now(), + error: exception, + stackTrace: stackTrace, + ); + } + } + + static bool _isEnabled(SentryLevel level) { + return _debug && level.ordinal >= _level.ordinal || + level == SentryLevel.fatal; + } +} + +/// Unified V3 worker API combining the robustness of the native replay worker +/// pattern (request/response with correlation IDs) with the minimal +/// WorkerIsolateBase bootstrap/spawn flow. +abstract class WorkerIsolate { + static const String shutdownMessage = 'shutdown'; + + @protected + final SendPort hostPort; + + WorkerIsolate(this.hostPort); + + /// Handle fire-and-forget messages from host → worker. + FutureOr handleMessage(Object? message); + + /// Handle a request expecting a response. Default implementation returns null. + FutureOr handleRequest(Object? payload) => null; + + /// Worker-side bootstrap: configures logging, handshakes, starts loop. + static void bootstrap( + WorkerConfig config, + SendPort hostPort, + WorkerIsolate worker, + ) { + IsolateDiagnosticLog.configure( + debug: config.debug, + level: config.logLevel, + ); + final receivePort = ReceivePort(); + + // Handshake: provide worker's inbox to host. + hostPort.send(receivePort.sendPort); + + receivePort.listen((message) { + if (message == shutdownMessage) { + IsolateDiagnosticLog.log( + SentryLevel.debug, 'Worker V3 received shutdown request'); + try { + receivePort.close(); + } catch (e, st) { + IsolateDiagnosticLog.log( + SentryLevel.error, + 'Worker V3 ReceivePort close error', + exception: e, + stackTrace: st, + ); + } + IsolateDiagnosticLog.log(SentryLevel.debug, 'Worker V3 closed'); + return; + } + + // Minimal RPC pattern: (id, payload, replyTo) + if (message is (int, Object?, SendPort)) { + final (id, payload, replyTo) = message; + Future.sync(() => worker.handleRequest(payload)) + .then((result) => replyTo.send((id, result))) + .catchError((Object error, StackTrace stackTrace) { + // RemoteError is a simple, transferable error container. + replyTo + .send((id, RemoteError(error.toString(), stackTrace.toString()))); + }); + return; + } + + // Fire-and-forget path + try { + worker.handleMessage(message); + } catch (e, st) { + IsolateDiagnosticLog.log( + SentryLevel.error, + 'Worker V3 error while handling message', + exception: e, + stackTrace: st, + ); + } + }); + } + + /// Host-side spawn: returns worker inbox SendPort after handshake + static Future<(Isolate isolate, SendPort workerPort)> spawn( + WorkerConfig cfg, + void Function((WorkerConfig, SendPort)) entryPoint, + ) async { + final init = ReceivePort(); + final isolate = await Isolate.spawn<(WorkerConfig, SendPort)>( + entryPoint, + (cfg, init.sendPort), + debugName: cfg.debugName, + ); + final SendPort workerPort = await init.first as SendPort; + return (isolate, workerPort); + } +} + +/// Host-side helper for workers to perform minimal request/response. +class WorkerClient { + WorkerClient(this._workerPort) { + _responses.listen(_handleResponse); + } + + final SendPort _workerPort; + final ReceivePort _responses = ReceivePort(); + final Map> _pending = {}; + int _idCounter = 0; + bool _closed = false; + + /// Fire-and-forget send to the worker. + void send(Object? message) { + _workerPort.send(message); + } + + /// Send a request to the worker and await a response. + Future request(Object? payload) { + if (_closed) throw StateError('WorkerClientV3 is closed'); + final id = _idCounter++; + final completer = Completer.sync(); + _pending[id] = completer; + _workerPort.send((id, payload, _responses.sendPort)); + return completer.future; + } + + void close() { + if (_closed) return; + _closed = true; + _workerPort.send(WorkerIsolate.shutdownMessage); + if (_pending.isEmpty) { + _responses.close(); + } + } + + void _handleResponse(dynamic message) { + final (int id, Object? response) = message as (int, Object?); + final completer = _pending.remove(id); + if (completer == null) return; + + if (response is RemoteError) { + completer.completeError(response); + } else { + completer.complete(response); + } + + if (_closed && _pending.isEmpty) { + _responses.close(); + } + } +} From a91cbae5ff2db8dbe921814013a4afafb55bc026 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 3 Sep 2025 17:30:19 +0200 Subject: [PATCH 03/78] Update --- .../lib/src/isolate_diagnostic_log.dart | 39 +++ .../native/java/android_envelope_worker.dart | 80 +++--- .../src/native/java/sentry_native_java.dart | 2 +- packages/flutter/lib/src/worker_isolate.dart | 237 ++++++++---------- 4 files changed, 184 insertions(+), 174 deletions(-) create mode 100644 packages/flutter/lib/src/isolate_diagnostic_log.dart diff --git a/packages/flutter/lib/src/isolate_diagnostic_log.dart b/packages/flutter/lib/src/isolate_diagnostic_log.dart new file mode 100644 index 0000000000..96ac1e5e69 --- /dev/null +++ b/packages/flutter/lib/src/isolate_diagnostic_log.dart @@ -0,0 +1,39 @@ +import 'dart:developer' as developer; + +import '../sentry_flutter.dart'; + +class IsolateDiagnosticLog { + IsolateDiagnosticLog._(); + + static late final bool _debug; + static late final SentryLevel _level; + + static void configure({required bool debug, required SentryLevel level}) { + _debug = debug; + _level = level; + } + + static void log( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + if (_isEnabled(level)) { + developer.log( + '[${level.name}] $message', + level: level.toDartLogLevel(), + name: logger ?? 'sentry', + time: DateTime.now(), + error: exception, + stackTrace: stackTrace, + ); + } + } + + static bool _isEnabled(SentryLevel level) { + return _debug && level.ordinal >= _level.ordinal || + level == SentryLevel.fatal; + } +} diff --git a/packages/flutter/lib/src/native/java/android_envelope_worker.dart b/packages/flutter/lib/src/native/java/android_envelope_worker.dart index 7e2d76e88e..65f5d80305 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_worker.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_worker.dart @@ -7,40 +7,39 @@ import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../worker_isolate.dart'; +import '../../isolate_diagnostic_log.dart'; import 'binding.dart' as native; -/// Host-side proxy for the Android envelope worker isolate. -class AndroidEnvelopeWorker { - AndroidEnvelopeWorker(this._options); - +class AndroidEnvelopeWorker implements WorkerHandle { final SentryFlutterOptions _options; + final IsolateConfig _config; + IsolateClient? _client; - WorkerClient? _client; + AndroidEnvelopeWorker(this._options) + : _config = IsolateConfig( + debug: _options.debug, + logLevel: _options.diagnosticLevel, + debugName: 'SentryAndroidEnvelopeWorker', + ); @internal // visible for testing/mocking static AndroidEnvelopeWorker Function(SentryFlutterOptions) factory = AndroidEnvelopeWorker.new; - Future start() async { + @override + FutureOr start() async { if (_client != null) return; - final config = WorkerConfig( - debug: _options.debug, - logLevel: _options.diagnosticLevel, - debugName: 'SentryAndroidEnvelopeWorker', - ); - final (_, port) = await WorkerIsolate.spawn( - config, - AndroidEnvelopeWorkerIsolate.entryPoint, - ); - _client = WorkerClient(port); + _client = await spawnIsolate(_config, _entryPoint); } - Future stop() async { - _close(); + static void _entryPoint((SendPort, IsolateConfig) init) { + final (host, config) = init; + runIsolate(config, host, _AndroidEnvelopeMessageHandler()); } /// Fire-and-forget send of envelope bytes to the worker. - void captureEnvelope(Uint8List envelopeData) { + void captureEnvelope( + Uint8List envelopeData, bool containsUnhandledException) { final client = _client; if (client == null) { _options.log( @@ -49,27 +48,30 @@ class AndroidEnvelopeWorker { ); return; } - client.send(TransferableTypedData.fromList([envelopeData])); + client.send(( + TransferableTypedData.fromList([envelopeData]), + containsUnhandledException + )); } - void _close() { + @override + FutureOr close() { _client?.close(); _client = null; } } -/// Worker isolate implementation handling envelope capture via JNI. -class AndroidEnvelopeWorkerIsolate extends WorkerIsolate { - AndroidEnvelopeWorkerIsolate(super.host); - +class _AndroidEnvelopeMessageHandler implements IsolateMessageHandler { @override - FutureOr handleMessage(Object? message) { - IsolateDiagnosticLog.log(SentryLevel.warning, - 'EnvelopeWorker invoked; starting captureEnvelope'); - - if (message is TransferableTypedData) { - final envelopeData = message.materialize().asUint8List(); - _captureEnvelope(envelopeData, false); + FutureOr onMessage(Object? msg) { + if (msg is (TransferableTypedData, bool)) { + final (transferable, containsUnhandledException) = msg; + final data = transferable.materialize().asUint8List(); + _captureEnvelope(data, containsUnhandledException); + } else { + IsolateDiagnosticLog.log(SentryLevel.warning, + 'Unexpected message type while handling a message: $msg', + logger: 'SentryAndroidEnvelopeWorker'); } } @@ -84,11 +86,15 @@ class AndroidEnvelopeWorkerIsolate extends WorkerIsolate { if (id == null) { IsolateDiagnosticLog.log(SentryLevel.error, - 'Native Android SDK returned null id when capturing envelope'); + 'Native Android SDK returned null id when capturing envelope', + logger: 'SentryAndroidEnvelopeWorker'); } } catch (exception, stackTrace) { IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, stackTrace: stackTrace); + exception: exception, + stackTrace: stackTrace, + logger: 'SentryAndroidEnvelopeWorker'); + // TODO: // if (options.automatedTestMode) { // rethrow; // } @@ -98,8 +104,6 @@ class AndroidEnvelopeWorkerIsolate extends WorkerIsolate { } } - static void entryPoint((WorkerConfig, SendPort) args) { - final (config, host) = args; - WorkerIsolate.bootstrap(config, host, AndroidEnvelopeWorkerIsolate(host)); - } + @override + FutureOr onRequest(Object? payload) => null; // not used for now } diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 509a86d66f..380292d375 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -84,7 +84,7 @@ class SentryNativeJava extends SentryNativeChannel { @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - envelopeWorker.captureEnvelope(envelopeData); + envelopeWorker.captureEnvelope(envelopeData, containsUnhandledException); } @override diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/worker_isolate.dart index db15ab3932..8ad6921ee5 100644 --- a/packages/flutter/lib/src/worker_isolate.dart +++ b/packages/flutter/lib/src/worker_isolate.dart @@ -5,155 +5,39 @@ import 'dart:isolate'; import 'package:meta/meta.dart'; import '../sentry_flutter.dart'; +import 'isolate_diagnostic_log.dart'; -class WorkerConfig { +// ------------------------------------------- +// HOST-SIDE API (runs on the main isolate) +// ------------------------------------------- + +/// Uniform lifecycle for any host-facing worker facade. +abstract class WorkerHandle { + FutureOr start(); + FutureOr close(); +} + +/// Minimal config passed to isolates. Extend as needed. +class IsolateConfig { final bool debug; final SentryLevel logLevel; final String? debugName; - const WorkerConfig({ + const IsolateConfig({ required this.debug, required this.logLevel, this.debugName, }); } -class IsolateDiagnosticLog { - IsolateDiagnosticLog._(); - - static late final bool _debug; - static late final SentryLevel _level; - - static void configure({required bool debug, required SentryLevel level}) { - _debug = debug; - _level = level; - } - - static void log( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - if (_isEnabled(level)) { - developer.log( - '[${level.name}] $message', - level: level.toDartLogLevel(), - name: logger ?? 'sentry', - time: DateTime.now(), - error: exception, - stackTrace: stackTrace, - ); - } - } - - static bool _isEnabled(SentryLevel level) { - return _debug && level.ordinal >= _level.ordinal || - level == SentryLevel.fatal; - } -} - -/// Unified V3 worker API combining the robustness of the native replay worker -/// pattern (request/response with correlation IDs) with the minimal -/// WorkerIsolateBase bootstrap/spawn flow. -abstract class WorkerIsolate { - static const String shutdownMessage = 'shutdown'; - - @protected - final SendPort hostPort; - - WorkerIsolate(this.hostPort); - - /// Handle fire-and-forget messages from host → worker. - FutureOr handleMessage(Object? message); - - /// Handle a request expecting a response. Default implementation returns null. - FutureOr handleRequest(Object? payload) => null; - - /// Worker-side bootstrap: configures logging, handshakes, starts loop. - static void bootstrap( - WorkerConfig config, - SendPort hostPort, - WorkerIsolate worker, - ) { - IsolateDiagnosticLog.configure( - debug: config.debug, - level: config.logLevel, - ); - final receivePort = ReceivePort(); - - // Handshake: provide worker's inbox to host. - hostPort.send(receivePort.sendPort); - - receivePort.listen((message) { - if (message == shutdownMessage) { - IsolateDiagnosticLog.log( - SentryLevel.debug, 'Worker V3 received shutdown request'); - try { - receivePort.close(); - } catch (e, st) { - IsolateDiagnosticLog.log( - SentryLevel.error, - 'Worker V3 ReceivePort close error', - exception: e, - stackTrace: st, - ); - } - IsolateDiagnosticLog.log(SentryLevel.debug, 'Worker V3 closed'); - return; - } - - // Minimal RPC pattern: (id, payload, replyTo) - if (message is (int, Object?, SendPort)) { - final (id, payload, replyTo) = message; - Future.sync(() => worker.handleRequest(payload)) - .then((result) => replyTo.send((id, result))) - .catchError((Object error, StackTrace stackTrace) { - // RemoteError is a simple, transferable error container. - replyTo - .send((id, RemoteError(error.toString(), stackTrace.toString()))); - }); - return; - } - - // Fire-and-forget path - try { - worker.handleMessage(message); - } catch (e, st) { - IsolateDiagnosticLog.log( - SentryLevel.error, - 'Worker V3 error while handling message', - exception: e, - stackTrace: st, - ); - } - }); - } - - /// Host-side spawn: returns worker inbox SendPort after handshake - static Future<(Isolate isolate, SendPort workerPort)> spawn( - WorkerConfig cfg, - void Function((WorkerConfig, SendPort)) entryPoint, - ) async { - final init = ReceivePort(); - final isolate = await Isolate.spawn<(WorkerConfig, SendPort)>( - entryPoint, - (cfg, init.sendPort), - debugName: cfg.debugName, - ); - final SendPort workerPort = await init.first as SendPort; - return (isolate, workerPort); - } -} - /// Host-side helper for workers to perform minimal request/response. -class WorkerClient { - WorkerClient(this._workerPort) { +class IsolateClient { + IsolateClient(this._workerPort) { _responses.listen(_handleResponse); } final SendPort _workerPort; + SendPort get port => _workerPort; final ReceivePort _responses = ReceivePort(); final Map> _pending = {}; int _idCounter = 0; @@ -166,7 +50,7 @@ class WorkerClient { /// Send a request to the worker and await a response. Future request(Object? payload) { - if (_closed) throw StateError('WorkerClientV3 is closed'); + if (_closed) throw StateError('IsolateClient is closed'); final id = _idCounter++; final completer = Completer.sync(); _pending[id] = completer; @@ -176,8 +60,8 @@ class WorkerClient { void close() { if (_closed) return; + _workerPort.send(_Ctl.shutdown); _closed = true; - _workerPort.send(WorkerIsolate.shutdownMessage); if (_pending.isEmpty) { _responses.close(); } @@ -199,3 +83,86 @@ class WorkerClient { } } } + +class _Ctl { + static const shutdown = '_shutdown_'; +} + +/// Isolate entry-point signature. +typedef IsolateEntry = void Function((SendPort, IsolateConfig)); + +/// Spawn an isolate and handshake to obtain its SendPort. +Future spawnIsolate( + IsolateConfig config, + IsolateEntry entry, +) async { + final receivePort = ReceivePort(); + await Isolate.spawn<(SendPort, IsolateConfig)>( + entry, + (receivePort.sendPort, config), + debugName: config.debugName, + ); + final workerPort = await receivePort.first as SendPort; + return IsolateClient(workerPort); +} + +// ------------------------------------------- +// ISOLATE-SIDE API (runs inside the worker isolate) +// ------------------------------------------- + +/// Domain behavior contract implemented INSIDE the worker isolate. +abstract class IsolateMessageHandler { + FutureOr onMessage(Object? message); + FutureOr onRequest(Object? payload) => null; +} + +/// Generic isolate runtime. Reuse for every Sentry worker. +void runIsolate( + IsolateConfig config, + SendPort host, + IsolateMessageHandler logic, +) { + // TODO: we might want to configure this at init overall since we shouldn't need isolate specific log setups + IsolateDiagnosticLog.configure( + debug: config.debug, + level: config.logLevel, + ); + + final inbox = ReceivePort(); + host.send(inbox.sendPort); + + inbox.listen((msg) async { + if (msg == _Ctl.shutdown) { + IsolateDiagnosticLog.log( + SentryLevel.debug, 'Isolate received shutdown request', + logger: config.debugName); + inbox.close(); + IsolateDiagnosticLog.log(SentryLevel.debug, 'Isolate closed.', + logger: config.debugName); + return; + } + + // RPC: (id, payload, replyTo) + if (msg is (int, Object?, SendPort)) { + final (id, payload, replyTo) = msg; + try { + final result = await logic.onRequest(payload); + replyTo.send((id, result)); + } catch (e, st) { + replyTo.send((id, RemoteError(e.toString(), st.toString()))); + } + return; + } + + // Fire-and-forget + try { + await logic.onMessage(msg); + } catch (exception, stackTrace) { + IsolateDiagnosticLog.log( + SentryLevel.error, 'Isolate error while handling message', + exception: exception, + stackTrace: stackTrace, + logger: config.debugName); + } + }); +} From a6bd3ccc542b60570d57185a400a50ff29d0923d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 3 Sep 2025 17:47:52 +0200 Subject: [PATCH 04/78] Update --- .../native/cocoa/cococa_envelope_worker.dart | 101 ++++++++++++++++++ .../src/native/cocoa/sentry_native_cocoa.dart | 23 ++-- .../native/java/android_envelope_worker.dart | 7 +- 3 files changed, 109 insertions(+), 22 deletions(-) create mode 100644 packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart diff --git a/packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart b/packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart new file mode 100644 index 0000000000..91191a14fc --- /dev/null +++ b/packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart @@ -0,0 +1,101 @@ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:jni/jni.dart'; +import 'package:meta/meta.dart'; +import 'package:objective_c/objective_c.dart'; + +import '../../../sentry_flutter.dart'; +import '../../worker_isolate.dart'; +import '../../isolate_diagnostic_log.dart'; +import 'binding.dart' as cocoa; + +class CocoaEnvelopeWorker implements Worker { + final SentryFlutterOptions _options; + final IsolateConfig _config; + IsolateClient? _client; + + CocoaEnvelopeWorker(this._options) + : _config = IsolateConfig( + debug: _options.debug, + logLevel: _options.diagnosticLevel, + debugName: 'SentryCocoaEnvelopeWorker', + ); + + @internal // visible for testing/mocking + static CocoaEnvelopeWorker Function(SentryFlutterOptions) factory = + CocoaEnvelopeWorker.new; + + @override + FutureOr start() async { + if (_client != null) return; + _client = await spawnIsolate(_config, _entryPoint); + } + + static void _entryPoint((SendPort, IsolateConfig) init) { + final (host, config) = init; + runIsolate(config, host, _CocoaEnvelopeMessageHandler()); + } + + /// Fire-and-forget send of envelope bytes to the worker. + void captureEnvelope(Uint8List envelopeData) { + final client = _client; + if (client == null) { + _options.log( + SentryLevel.warning, + 'CocoaEnvelopeWorker.captureEnvelope called before start; dropping', + ); + return; + } + client.send(TransferableTypedData.fromList([envelopeData])); + } + + @override + FutureOr close() { + _client?.close(); + _client = null; + } +} + +class _CocoaEnvelopeMessageHandler extends IsolateMessageHandler { + @override + FutureOr onMessage(Object? msg) { + if (msg is TransferableTypedData) { + final data = msg.materialize().asUint8List(); + _captureEnvelope(data); + } else { + IsolateDiagnosticLog.log(SentryLevel.warning, + 'Unexpected message type while handling a message: $msg', + logger: 'SentryCocoaEnvelopeWorker'); + } + } + + void _captureEnvelope(Uint8List envelopeData) { + JObject? id; + JByteArray? byteArray; + try { + final nsData = envelopeData.toNSData(); + final envelope = cocoa.PrivateSentrySDKOnly.envelopeWithData(nsData); + if (envelope != null) { + cocoa.PrivateSentrySDKOnly.captureEnvelope(envelope); + } else { + IsolateDiagnosticLog.log(SentryLevel.error, + 'Native Cocoa SDK returned null when capturing envelope', + logger: 'SentryCocoaEnvelopeWorker'); + } + } catch (exception, stackTrace) { + IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', + exception: exception, + stackTrace: stackTrace, + logger: 'SentryCocoaEnvelopeWorker'); + // TODO: + // if (options.automatedTestMode) { + // rethrow; + // } + } finally { + byteArray?.release(); + id?.release(); + } + } +} diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 7145f39f71..4bd66e81ab 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -9,10 +9,12 @@ import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; import 'binding.dart' as cocoa; import 'cocoa_replay_recorder.dart'; +import 'cococa_envelope_worker.dart'; @internal class SentryNativeCocoa extends SentryNativeChannel { CocoaReplayRecorder? _replayRecorder; + CocoaEnvelopeWorker? _envelopeWorker; SentryId? _replayId; SentryNativeCocoa(super.options); @@ -49,29 +51,16 @@ class SentryNativeCocoa extends SentryNativeChannel { }); } + _envelopeWorker = CocoaEnvelopeWorker(options); + _envelopeWorker?.start(); + return super.init(hub); } @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - try { - final nsData = envelopeData.toNSData(); - final envelope = cocoa.PrivateSentrySDKOnly.envelopeWithData(nsData); - if (envelope != null) { - cocoa.PrivateSentrySDKOnly.captureEnvelope(envelope); - } else { - options.log( - SentryLevel.error, 'Failed to capture envelope: envelope is null'); - } - } catch (exception, stackTrace) { - options.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, stackTrace: stackTrace); - - if (options.automatedTestMode) { - rethrow; - } - } + _envelopeWorker?.captureEnvelope(envelopeData); } @override diff --git a/packages/flutter/lib/src/native/java/android_envelope_worker.dart b/packages/flutter/lib/src/native/java/android_envelope_worker.dart index 65f5d80305..f19f338539 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_worker.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_worker.dart @@ -10,7 +10,7 @@ import '../../worker_isolate.dart'; import '../../isolate_diagnostic_log.dart'; import 'binding.dart' as native; -class AndroidEnvelopeWorker implements WorkerHandle { +class AndroidEnvelopeWorker implements Worker { final SentryFlutterOptions _options; final IsolateConfig _config; IsolateClient? _client; @@ -61,7 +61,7 @@ class AndroidEnvelopeWorker implements WorkerHandle { } } -class _AndroidEnvelopeMessageHandler implements IsolateMessageHandler { +class _AndroidEnvelopeMessageHandler extends IsolateMessageHandler { @override FutureOr onMessage(Object? msg) { if (msg is (TransferableTypedData, bool)) { @@ -103,7 +103,4 @@ class _AndroidEnvelopeMessageHandler implements IsolateMessageHandler { id?.release(); } } - - @override - FutureOr onRequest(Object? payload) => null; // not used for now } From b9269c7dd28ec4b6faea963c07ffcc2451ebabd9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 00:01:49 +0200 Subject: [PATCH 05/78] Update --- .../integrations/native_sdk_integration.dart | 7 ++ ...worker.dart => cocoa_envelope_sender.dart} | 61 +++++++--------- .../src/native/cocoa/sentry_native_cocoa.dart | 10 +-- ...rker.dart => android_envelope_sender.dart} | 51 ++++++------- .../src/native/java/sentry_native_java.dart | 11 +-- packages/flutter/lib/src/worker_isolate.dart | 73 +++++++++---------- 6 files changed, 105 insertions(+), 108 deletions(-) rename packages/flutter/lib/src/native/cocoa/{cococa_envelope_worker.dart => cocoa_envelope_sender.dart} (59%) rename packages/flutter/lib/src/native/java/{android_envelope_worker.dart => android_envelope_sender.dart} (68%) diff --git a/packages/flutter/lib/src/integrations/native_sdk_integration.dart b/packages/flutter/lib/src/integrations/native_sdk_integration.dart index 76c91eda6e..14164c6617 100644 --- a/packages/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/packages/flutter/lib/src/integrations/native_sdk_integration.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; +import '../isolate_diagnostic_log.dart'; import '../native/sentry_native_binding.dart'; import '../sentry_flutter_options.dart'; @@ -25,6 +26,12 @@ class NativeSdkIntegration implements Integration { return; } + // Configure static Isolate logger before spawning isolates + IsolateDiagnosticLog.configure( + debug: options.debug, + level: options.diagnosticLevel, + ); + try { await _native.init(hub); options.sdk.addIntegration('nativeSdkIntegration'); diff --git a/packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart similarity index 59% rename from packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart rename to packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index 91191a14fc..33e48f9662 100644 --- a/packages/flutter/lib/src/native/cocoa/cococa_envelope_worker.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:isolate'; import 'dart:typed_data'; -import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; @@ -11,54 +10,55 @@ import '../../worker_isolate.dart'; import '../../isolate_diagnostic_log.dart'; import 'binding.dart' as cocoa; -class CocoaEnvelopeWorker implements Worker { +class CocoaEnvelopeSender implements WorkerHost { final SentryFlutterOptions _options; - final IsolateConfig _config; - IsolateClient? _client; + final WorkerConfig _config; + Worker? _worker; - CocoaEnvelopeWorker(this._options) - : _config = IsolateConfig( - debug: _options.debug, - logLevel: _options.diagnosticLevel, - debugName: 'SentryCocoaEnvelopeWorker', + static final String name = 'SentryCocoaEnvelopeSender'; + + CocoaEnvelopeSender(this._options) + : _config = WorkerConfig( + debugName: name, ); @internal // visible for testing/mocking - static CocoaEnvelopeWorker Function(SentryFlutterOptions) factory = - CocoaEnvelopeWorker.new; + static CocoaEnvelopeSender Function(SentryFlutterOptions) factory = + CocoaEnvelopeSender.new; @override FutureOr start() async { - if (_client != null) return; - _client = await spawnIsolate(_config, _entryPoint); + if (_worker != null) return; + _worker = await spawnWorker(_config, _entryPoint); } - static void _entryPoint((SendPort, IsolateConfig) init) { - final (host, config) = init; - runIsolate(config, host, _CocoaEnvelopeMessageHandler()); + @override + FutureOr close() { + _worker?.close(); + _worker = null; } /// Fire-and-forget send of envelope bytes to the worker. void captureEnvelope(Uint8List envelopeData) { - final client = _client; + final client = _worker; if (client == null) { _options.log( SentryLevel.warning, - 'CocoaEnvelopeWorker.captureEnvelope called before start; dropping', + 'captureEnvelope called before start; dropping', + logger: name, ); return; } client.send(TransferableTypedData.fromList([envelopeData])); } - @override - FutureOr close() { - _client?.close(); - _client = null; + static void _entryPoint((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _CocoaEnvelopeHandler()); } } -class _CocoaEnvelopeMessageHandler extends IsolateMessageHandler { +class _CocoaEnvelopeHandler extends WorkerHandler { @override FutureOr onMessage(Object? msg) { if (msg is TransferableTypedData) { @@ -67,13 +67,11 @@ class _CocoaEnvelopeMessageHandler extends IsolateMessageHandler { } else { IsolateDiagnosticLog.log(SentryLevel.warning, 'Unexpected message type while handling a message: $msg', - logger: 'SentryCocoaEnvelopeWorker'); + logger: CocoaEnvelopeSender.name); } } void _captureEnvelope(Uint8List envelopeData) { - JObject? id; - JByteArray? byteArray; try { final nsData = envelopeData.toNSData(); final envelope = cocoa.PrivateSentrySDKOnly.envelopeWithData(nsData); @@ -82,20 +80,13 @@ class _CocoaEnvelopeMessageHandler extends IsolateMessageHandler { } else { IsolateDiagnosticLog.log(SentryLevel.error, 'Native Cocoa SDK returned null when capturing envelope', - logger: 'SentryCocoaEnvelopeWorker'); + logger: CocoaEnvelopeSender.name); } } catch (exception, stackTrace) { IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', exception: exception, stackTrace: stackTrace, - logger: 'SentryCocoaEnvelopeWorker'); - // TODO: - // if (options.automatedTestMode) { - // rethrow; - // } - } finally { - byteArray?.release(); - id?.release(); + logger: CocoaEnvelopeSender.name); } } } diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 4bd66e81ab..6707817f2d 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -9,12 +9,12 @@ import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; import 'binding.dart' as cocoa; import 'cocoa_replay_recorder.dart'; -import 'cococa_envelope_worker.dart'; +import 'cocoa_envelope_sender.dart'; @internal class SentryNativeCocoa extends SentryNativeChannel { CocoaReplayRecorder? _replayRecorder; - CocoaEnvelopeWorker? _envelopeWorker; + CocoaEnvelopeSender? _envelopeSender; SentryId? _replayId; SentryNativeCocoa(super.options); @@ -51,8 +51,8 @@ class SentryNativeCocoa extends SentryNativeChannel { }); } - _envelopeWorker = CocoaEnvelopeWorker(options); - _envelopeWorker?.start(); + _envelopeSender = CocoaEnvelopeSender(options); + await _envelopeSender?.start(); return super.init(hub); } @@ -60,7 +60,7 @@ class SentryNativeCocoa extends SentryNativeChannel { @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - _envelopeWorker?.captureEnvelope(envelopeData); + _envelopeSender?.captureEnvelope(envelopeData); } @override diff --git a/packages/flutter/lib/src/native/java/android_envelope_worker.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart similarity index 68% rename from packages/flutter/lib/src/native/java/android_envelope_worker.dart rename to packages/flutter/lib/src/native/java/android_envelope_sender.dart index f19f338539..71171badba 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_worker.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -10,41 +10,43 @@ import '../../worker_isolate.dart'; import '../../isolate_diagnostic_log.dart'; import 'binding.dart' as native; -class AndroidEnvelopeWorker implements Worker { +class AndroidEnvelopeSender implements WorkerHost { final SentryFlutterOptions _options; - final IsolateConfig _config; - IsolateClient? _client; + final WorkerConfig _config; + Worker? _worker; - AndroidEnvelopeWorker(this._options) - : _config = IsolateConfig( - debug: _options.debug, - logLevel: _options.diagnosticLevel, - debugName: 'SentryAndroidEnvelopeWorker', + static final String name = 'SentryAndroidEnvelopeSender'; + + AndroidEnvelopeSender(this._options) + : _config = WorkerConfig( + debugName: name, ); @internal // visible for testing/mocking - static AndroidEnvelopeWorker Function(SentryFlutterOptions) factory = - AndroidEnvelopeWorker.new; + static AndroidEnvelopeSender Function(SentryFlutterOptions) factory = + AndroidEnvelopeSender.new; @override FutureOr start() async { - if (_client != null) return; - _client = await spawnIsolate(_config, _entryPoint); + if (_worker != null) return; + _worker = await spawnWorker(_config, _entryPoint); } - static void _entryPoint((SendPort, IsolateConfig) init) { - final (host, config) = init; - runIsolate(config, host, _AndroidEnvelopeMessageHandler()); + @override + FutureOr close() { + _worker?.close(); + _worker = null; } /// Fire-and-forget send of envelope bytes to the worker. void captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - final client = _client; + final client = _worker; if (client == null) { _options.log( SentryLevel.warning, - 'AndroidEnvelopeWorker.captureEnvelope called before start; dropping', + 'captureEnvelope called before worker started; dropping', + logger: name, ); return; } @@ -54,14 +56,13 @@ class AndroidEnvelopeWorker implements Worker { )); } - @override - FutureOr close() { - _client?.close(); - _client = null; + static void _entryPoint((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _AndroidEnvelopeHandler()); } } -class _AndroidEnvelopeMessageHandler extends IsolateMessageHandler { +class _AndroidEnvelopeHandler extends WorkerHandler { @override FutureOr onMessage(Object? msg) { if (msg is (TransferableTypedData, bool)) { @@ -71,7 +72,7 @@ class _AndroidEnvelopeMessageHandler extends IsolateMessageHandler { } else { IsolateDiagnosticLog.log(SentryLevel.warning, 'Unexpected message type while handling a message: $msg', - logger: 'SentryAndroidEnvelopeWorker'); + logger: AndroidEnvelopeSender.name); } } @@ -87,13 +88,13 @@ class _AndroidEnvelopeMessageHandler extends IsolateMessageHandler { if (id == null) { IsolateDiagnosticLog.log(SentryLevel.error, 'Native Android SDK returned null id when capturing envelope', - logger: 'SentryAndroidEnvelopeWorker'); + logger: AndroidEnvelopeSender.name); } } catch (exception, stackTrace) { IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', exception: exception, stackTrace: stackTrace, - logger: 'SentryAndroidEnvelopeWorker'); + logger: AndroidEnvelopeSender.name); // TODO: // if (options.automatedTestMode) { // rethrow; diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 380292d375..a6df55c0af 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -6,16 +6,17 @@ import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; -import '../../worker_isolate.dart'; import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; -import 'android_envelope_worker.dart'; +import 'android_envelope_sender.dart'; import 'android_replay_recorder.dart'; import 'binding.dart' as native; @internal class SentryNativeJava extends SentryNativeChannel { AndroidReplayRecorder? _replayRecorder; + AndroidEnvelopeSender? _envelopeSender; + SentryNativeJava(super.options); @override @@ -73,13 +74,13 @@ class SentryNativeJava extends SentryNativeChannel { }); } - envelopeWorker = AndroidEnvelopeWorker.factory(options); - await envelopeWorker.start(); + _envelopeSender = AndroidEnvelopeSender.factory(options); + await _envelopeSender?.start(); return super.init(hub); } - late AndroidEnvelopeWorker envelopeWorker; + late AndroidEnvelopeSender envelopeWorker; @override FutureOr captureEnvelope( diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/worker_isolate.dart index 8ad6921ee5..d087d94efa 100644 --- a/packages/flutter/lib/src/worker_isolate.dart +++ b/packages/flutter/lib/src/worker_isolate.dart @@ -1,9 +1,6 @@ -import 'dart:developer' as developer; import 'dart:async'; import 'dart:isolate'; -import 'package:meta/meta.dart'; - import '../sentry_flutter.dart'; import 'isolate_diagnostic_log.dart'; @@ -11,28 +8,27 @@ import 'isolate_diagnostic_log.dart'; // HOST-SIDE API (runs on the main isolate) // ------------------------------------------- -/// Uniform lifecycle for any host-facing worker facade. -abstract class WorkerHandle { +/// Host-side lifecycle interface for a worker isolate. +/// +/// Responsible for spawning the worker isolate, sending messages, +/// and shutting it down. It does not define the worker logic. +abstract class WorkerHost { FutureOr start(); FutureOr close(); } /// Minimal config passed to isolates. Extend as needed. -class IsolateConfig { - final bool debug; - final SentryLevel logLevel; +class WorkerConfig { final String? debugName; - const IsolateConfig({ - required this.debug, - required this.logLevel, - this.debugName, + const WorkerConfig({ + required this.debugName, }); } /// Host-side helper for workers to perform minimal request/response. -class IsolateClient { - IsolateClient(this._workerPort) { +class Worker { + Worker(this._workerPort) { _responses.listen(_handleResponse); } @@ -50,7 +46,7 @@ class IsolateClient { /// Send a request to the worker and await a response. Future request(Object? payload) { - if (_closed) throw StateError('IsolateClient is closed'); + if (_closed) throw StateError('WorkerClient is closed'); final id = _idCounter++; final completer = Completer.sync(); _pending[id] = completer; @@ -88,46 +84,47 @@ class _Ctl { static const shutdown = '_shutdown_'; } -/// Isolate entry-point signature. -typedef IsolateEntry = void Function((SendPort, IsolateConfig)); +/// Worker (isolate) entry-point signature. +typedef WorkerEntry = void Function((SendPort, WorkerConfig)); -/// Spawn an isolate and handshake to obtain its SendPort. -Future spawnIsolate( - IsolateConfig config, - IsolateEntry entry, +/// Spawn a worker isolate and handshake to obtain its SendPort. +Future spawnWorker( + WorkerConfig config, + WorkerEntry entry, ) async { final receivePort = ReceivePort(); - await Isolate.spawn<(SendPort, IsolateConfig)>( + await Isolate.spawn<(SendPort, WorkerConfig)>( entry, (receivePort.sendPort, config), debugName: config.debugName, ); final workerPort = await receivePort.first as SendPort; - return IsolateClient(workerPort); + return Worker(workerPort); } // ------------------------------------------- // ISOLATE-SIDE API (runs inside the worker isolate) // ------------------------------------------- -/// Domain behavior contract implemented INSIDE the worker isolate. -abstract class IsolateMessageHandler { +/// Message/request handler that runs inside the worker isolate. +/// +/// This does not represent the isolate lifecycle; it only defines how +/// the worker processes incoming messages and optional request/response. +abstract class WorkerHandler { + /// Handle fire-and-forget messages sent from the host. FutureOr onMessage(Object? message); - FutureOr onRequest(Object? payload) => null; + + /// Handle request/response payloads sent from the host. + /// Return value is sent back to the host. Default: no-op. + FutureOr onRequest(Object? payload) => {}; } -/// Generic isolate runtime. Reuse for every Sentry worker. -void runIsolate( - IsolateConfig config, +/// Generic worker runtime. Reuse for every Sentry worker. +void runWorker( + WorkerConfig config, SendPort host, - IsolateMessageHandler logic, + WorkerHandler handler, ) { - // TODO: we might want to configure this at init overall since we shouldn't need isolate specific log setups - IsolateDiagnosticLog.configure( - debug: config.debug, - level: config.logLevel, - ); - final inbox = ReceivePort(); host.send(inbox.sendPort); @@ -146,7 +143,7 @@ void runIsolate( if (msg is (int, Object?, SendPort)) { final (id, payload, replyTo) = msg; try { - final result = await logic.onRequest(payload); + final result = await handler.onRequest(payload); replyTo.send((id, result)); } catch (e, st) { replyTo.send((id, RemoteError(e.toString(), st.toString()))); @@ -156,7 +153,7 @@ void runIsolate( // Fire-and-forget try { - await logic.onMessage(msg); + await handler.onMessage(msg); } catch (exception, stackTrace) { IsolateDiagnosticLog.log( SentryLevel.error, 'Isolate error while handling message', From 6ef9960d313ff7994679ae9fc92e53745faedbc3 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 11:47:31 +0200 Subject: [PATCH 06/78] Update --- packages/flutter/lib/src/worker_isolate.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/worker_isolate.dart index d087d94efa..3f9a2ec3e3 100644 --- a/packages/flutter/lib/src/worker_isolate.dart +++ b/packages/flutter/lib/src/worker_isolate.dart @@ -10,8 +10,8 @@ import 'isolate_diagnostic_log.dart'; /// Host-side lifecycle interface for a worker isolate. /// -/// Responsible for spawning the worker isolate, sending messages, -/// and shutting it down. It does not define the worker logic. +/// Responsible for spawning the worker isolate, and shutting it down. +/// It does not define the worker logic. abstract class WorkerHost { FutureOr start(); FutureOr close(); From a43f2e1d03367f40496eed9740fd20aa94bdc334 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 12:45:09 +0200 Subject: [PATCH 07/78] Configure diagnostic log --- .../lib/src/integrations/native_sdk_integration.dart | 6 ------ packages/flutter/lib/src/worker_isolate.dart | 9 +++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/integrations/native_sdk_integration.dart b/packages/flutter/lib/src/integrations/native_sdk_integration.dart index 14164c6617..9cdb8f65d7 100644 --- a/packages/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/packages/flutter/lib/src/integrations/native_sdk_integration.dart @@ -26,12 +26,6 @@ class NativeSdkIntegration implements Integration { return; } - // Configure static Isolate logger before spawning isolates - IsolateDiagnosticLog.configure( - debug: options.debug, - level: options.diagnosticLevel, - ); - try { await _native.init(hub); options.sdk.addIntegration('nativeSdkIntegration'); diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/worker_isolate.dart index 3f9a2ec3e3..2c15e0f425 100644 --- a/packages/flutter/lib/src/worker_isolate.dart +++ b/packages/flutter/lib/src/worker_isolate.dart @@ -19,9 +19,13 @@ abstract class WorkerHost { /// Minimal config passed to isolates. Extend as needed. class WorkerConfig { + final bool debug; + final SentryLevel diagnosticLevel; final String? debugName; const WorkerConfig({ + required this.debug, + required this.diagnosticLevel, required this.debugName, }); } @@ -125,6 +129,11 @@ void runWorker( SendPort host, WorkerHandler handler, ) { + IsolateDiagnosticLog.configure( + debug: config.debug, + level: config.diagnosticLevel, + ); + final inbox = ReceivePort(); host.send(inbox.sendPort); From e334269ece7adbf36efdf228b3bdd4d25544a478 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 13:28:07 +0200 Subject: [PATCH 08/78] Update log messages --- .../integrations/native_sdk_integration.dart | 2 +- .../integrations/thread_info_integration.dart | 2 +- .../lib/src/{ => isolate}/isolate_helper.dart | 0 .../lib/src/isolate/isolate_logger.dart | 66 +++++++++++++++++++ .../isolate_worker.dart} | 23 +++---- .../lib/src/isolate_diagnostic_log.dart | 39 ----------- .../native/cocoa/cocoa_envelope_sender.dart | 26 +++----- .../native/java/android_envelope_sender.dart | 26 +++----- 8 files changed, 97 insertions(+), 87 deletions(-) rename packages/flutter/lib/src/{ => isolate}/isolate_helper.dart (100%) create mode 100644 packages/flutter/lib/src/isolate/isolate_logger.dart rename packages/flutter/lib/src/{worker_isolate.dart => isolate/isolate_worker.dart} (87%) delete mode 100644 packages/flutter/lib/src/isolate_diagnostic_log.dart diff --git a/packages/flutter/lib/src/integrations/native_sdk_integration.dart b/packages/flutter/lib/src/integrations/native_sdk_integration.dart index 9cdb8f65d7..edb17e0c8b 100644 --- a/packages/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/packages/flutter/lib/src/integrations/native_sdk_integration.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; -import '../isolate_diagnostic_log.dart'; +import '../isolate_logger.dart'; import '../native/sentry_native_binding.dart'; import '../sentry_flutter_options.dart'; diff --git a/packages/flutter/lib/src/integrations/thread_info_integration.dart b/packages/flutter/lib/src/integrations/thread_info_integration.dart index 94ad83bb8c..a647b1b10e 100644 --- a/packages/flutter/lib/src/integrations/thread_info_integration.dart +++ b/packages/flutter/lib/src/integrations/thread_info_integration.dart @@ -3,7 +3,7 @@ import 'package:meta/meta.dart'; import '../../sentry_flutter.dart'; -import '../isolate_helper.dart'; +import '../isolate/isolate_helper.dart'; /// Integration for adding thread information to spans. /// diff --git a/packages/flutter/lib/src/isolate_helper.dart b/packages/flutter/lib/src/isolate/isolate_helper.dart similarity index 100% rename from packages/flutter/lib/src/isolate_helper.dart rename to packages/flutter/lib/src/isolate/isolate_helper.dart diff --git a/packages/flutter/lib/src/isolate/isolate_logger.dart b/packages/flutter/lib/src/isolate/isolate_logger.dart new file mode 100644 index 0000000000..9abf4e6ac2 --- /dev/null +++ b/packages/flutter/lib/src/isolate/isolate_logger.dart @@ -0,0 +1,66 @@ +import 'dart:developer' as developer; + +import '../../sentry_flutter.dart'; + +/// Isolate-local logger that writes diagnostic messages to `dart:developer.log`. +/// +/// Intended for worker/background isolates where a `SentryOptions` instance +/// or hub may not be available. Because Dart statics are isolate-local, +/// you must call [configure] once per isolate before using [log]. +class IsolateLogger { + IsolateLogger._(); + + static late final bool _debug; + static late final SentryLevel _level; + static late final String _loggerName; + static bool _isConfigured = false; + + /// Configures this logger for the current isolate. + /// + /// Must be called once per isolate before invoking [log]. + /// + /// - [debug]: when false, suppresses all logs except [SentryLevel.fatal]. + /// - [level]: minimum severity threshold (inclusive) when [debug] is true. + /// - [loggerName]: logger name for the call sites + static void configure( + {required bool debug, + required SentryLevel level, + required String loggerName}) { + _debug = debug; + _level = level; + _loggerName = loggerName; + _isConfigured = true; + } + + /// Emits a log entry if enabled for this isolate. + /// + /// Messages are forwarded to [developer.log]. The provided [level] is + /// mapped via [SentryLevel.toDartLogLevel] to a `developer.log` numeric level. + /// If logging is disabled or [level] is below the configured threshold, + /// nothing is emitted. [SentryLevel.fatal] is always emitted. + static void log( + SentryLevel level, + String message, { + String? logger, + Object? exception, + StackTrace? stackTrace, + }) { + assert( + _isConfigured, 'IsolateLogger.configure must be called before logging'); + if (_isEnabled(level)) { + developer.log( + '[${level.name}] $message', + level: level.toDartLogLevel(), + name: logger ?? _loggerName, + time: DateTime.now(), + error: exception, + stackTrace: stackTrace, + ); + } + } + + static bool _isEnabled(SentryLevel level) { + return _debug && level.ordinal >= _level.ordinal || + level == SentryLevel.fatal; + } +} diff --git a/packages/flutter/lib/src/worker_isolate.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart similarity index 87% rename from packages/flutter/lib/src/worker_isolate.dart rename to packages/flutter/lib/src/isolate/isolate_worker.dart index 2c15e0f425..92a924e55c 100644 --- a/packages/flutter/lib/src/worker_isolate.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'dart:isolate'; -import '../sentry_flutter.dart'; -import 'isolate_diagnostic_log.dart'; +import '../../sentry_flutter.dart'; +import 'isolate_logger.dart'; // ------------------------------------------- // HOST-SIDE API (runs on the main isolate) @@ -50,7 +50,7 @@ class Worker { /// Send a request to the worker and await a response. Future request(Object? payload) { - if (_closed) throw StateError('WorkerClient is closed'); + if (_closed) throw StateError('Worker is closed'); final id = _idCounter++; final completer = Completer.sync(); _pending[id] = completer; @@ -129,9 +129,10 @@ void runWorker( SendPort host, WorkerHandler handler, ) { - IsolateDiagnosticLog.configure( + IsolateLogger.configure( debug: config.debug, level: config.diagnosticLevel, + loggerName: config.debugName ?? 'SentryIsolateWorker', ); final inbox = ReceivePort(); @@ -139,12 +140,9 @@ void runWorker( inbox.listen((msg) async { if (msg == _Ctl.shutdown) { - IsolateDiagnosticLog.log( - SentryLevel.debug, 'Isolate received shutdown request', - logger: config.debugName); + IsolateLogger.log(SentryLevel.debug, 'Isolate received shutdown'); inbox.close(); - IsolateDiagnosticLog.log(SentryLevel.debug, 'Isolate closed.', - logger: config.debugName); + IsolateLogger.log(SentryLevel.debug, 'Isolate closed'); return; } @@ -164,11 +162,8 @@ void runWorker( try { await handler.onMessage(msg); } catch (exception, stackTrace) { - IsolateDiagnosticLog.log( - SentryLevel.error, 'Isolate error while handling message', - exception: exception, - stackTrace: stackTrace, - logger: config.debugName); + IsolateLogger.log(SentryLevel.error, 'Isolate failed to handle message', + exception: exception, stackTrace: stackTrace); } }); } diff --git a/packages/flutter/lib/src/isolate_diagnostic_log.dart b/packages/flutter/lib/src/isolate_diagnostic_log.dart deleted file mode 100644 index 96ac1e5e69..0000000000 --- a/packages/flutter/lib/src/isolate_diagnostic_log.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:developer' as developer; - -import '../sentry_flutter.dart'; - -class IsolateDiagnosticLog { - IsolateDiagnosticLog._(); - - static late final bool _debug; - static late final SentryLevel _level; - - static void configure({required bool debug, required SentryLevel level}) { - _debug = debug; - _level = level; - } - - static void log( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - if (_isEnabled(level)) { - developer.log( - '[${level.name}] $message', - level: level.toDartLogLevel(), - name: logger ?? 'sentry', - time: DateTime.now(), - error: exception, - stackTrace: stackTrace, - ); - } - } - - static bool _isEnabled(SentryLevel level) { - return _debug && level.ordinal >= _level.ordinal || - level == SentryLevel.fatal; - } -} diff --git a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index 33e48f9662..4f6de62582 100644 --- a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -6,8 +6,8 @@ import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; import '../../../sentry_flutter.dart'; -import '../../worker_isolate.dart'; -import '../../isolate_diagnostic_log.dart'; +import '../../isolate/isolate_worker.dart'; +import '../../isolate/isolate_logger.dart'; import 'binding.dart' as cocoa; class CocoaEnvelopeSender implements WorkerHost { @@ -15,11 +15,11 @@ class CocoaEnvelopeSender implements WorkerHost { final WorkerConfig _config; Worker? _worker; - static final String name = 'SentryCocoaEnvelopeSender'; - CocoaEnvelopeSender(this._options) : _config = WorkerConfig( - debugName: name, + debugName: 'SentryCocoaEnvelopeSender', + debug: _options.debug, + diagnosticLevel: _options.diagnosticLevel, ); @internal // visible for testing/mocking @@ -45,7 +45,6 @@ class CocoaEnvelopeSender implements WorkerHost { _options.log( SentryLevel.warning, 'captureEnvelope called before start; dropping', - logger: name, ); return; } @@ -65,9 +64,7 @@ class _CocoaEnvelopeHandler extends WorkerHandler { final data = msg.materialize().asUint8List(); _captureEnvelope(data); } else { - IsolateDiagnosticLog.log(SentryLevel.warning, - 'Unexpected message type while handling a message: $msg', - logger: CocoaEnvelopeSender.name); + IsolateLogger.log(SentryLevel.warning, 'Unexpected message type: $msg'); } } @@ -78,15 +75,12 @@ class _CocoaEnvelopeHandler extends WorkerHandler { if (envelope != null) { cocoa.PrivateSentrySDKOnly.captureEnvelope(envelope); } else { - IsolateDiagnosticLog.log(SentryLevel.error, - 'Native Cocoa SDK returned null when capturing envelope', - logger: CocoaEnvelopeSender.name); + IsolateLogger.log(SentryLevel.error, + 'Native Cocoa SDK returned null when capturing envelope'); } } catch (exception, stackTrace) { - IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, - stackTrace: stackTrace, - logger: CocoaEnvelopeSender.name); + IsolateLogger.log(SentryLevel.error, 'Failed to capture envelope', + exception: exception, stackTrace: stackTrace); } } } diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index 71171badba..ff1b1b9cc8 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -6,8 +6,8 @@ import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; -import '../../worker_isolate.dart'; -import '../../isolate_diagnostic_log.dart'; +import '../../isolate/isolate_worker.dart'; +import '../../isolate/isolate_logger.dart'; import 'binding.dart' as native; class AndroidEnvelopeSender implements WorkerHost { @@ -15,11 +15,11 @@ class AndroidEnvelopeSender implements WorkerHost { final WorkerConfig _config; Worker? _worker; - static final String name = 'SentryAndroidEnvelopeSender'; - AndroidEnvelopeSender(this._options) : _config = WorkerConfig( - debugName: name, + debugName: 'SentryAndroidEnvelopeSender', + debug: _options.debug, + diagnosticLevel: _options.diagnosticLevel, ); @internal // visible for testing/mocking @@ -46,7 +46,6 @@ class AndroidEnvelopeSender implements WorkerHost { _options.log( SentryLevel.warning, 'captureEnvelope called before worker started; dropping', - logger: name, ); return; } @@ -70,9 +69,7 @@ class _AndroidEnvelopeHandler extends WorkerHandler { final data = transferable.materialize().asUint8List(); _captureEnvelope(data, containsUnhandledException); } else { - IsolateDiagnosticLog.log(SentryLevel.warning, - 'Unexpected message type while handling a message: $msg', - logger: AndroidEnvelopeSender.name); + IsolateLogger.log(SentryLevel.warning, 'Unexpected message type: $msg'); } } @@ -86,15 +83,12 @@ class _AndroidEnvelopeHandler extends WorkerHandler { byteArray, containsUnhandledException); if (id == null) { - IsolateDiagnosticLog.log(SentryLevel.error, - 'Native Android SDK returned null id when capturing envelope', - logger: AndroidEnvelopeSender.name); + IsolateLogger.log(SentryLevel.error, + 'Native Android SDK returned null when capturing envelope'); } } catch (exception, stackTrace) { - IsolateDiagnosticLog.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, - stackTrace: stackTrace, - logger: AndroidEnvelopeSender.name); + IsolateLogger.log(SentryLevel.error, 'Failed to capture envelope', + exception: exception, stackTrace: stackTrace); // TODO: // if (options.automatedTestMode) { // rethrow; From aa728e71c027f6bb8c993e2ea6d6f083cdfdaa7d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 13:43:09 +0200 Subject: [PATCH 09/78] Update --- .../lib/src/isolate/isolate_worker.dart | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 92a924e55c..70d1760e69 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -4,19 +4,12 @@ import 'dart:isolate'; import '../../sentry_flutter.dart'; import 'isolate_logger.dart'; +const _shutdownCommand = '_shutdown_'; + // ------------------------------------------- // HOST-SIDE API (runs on the main isolate) // ------------------------------------------- -/// Host-side lifecycle interface for a worker isolate. -/// -/// Responsible for spawning the worker isolate, and shutting it down. -/// It does not define the worker logic. -abstract class WorkerHost { - FutureOr start(); - FutureOr close(); -} - /// Minimal config passed to isolates. Extend as needed. class WorkerConfig { final bool debug; @@ -30,6 +23,15 @@ class WorkerConfig { }); } +/// Host-side lifecycle interface for a worker isolate. +/// +/// Responsible for spawning the worker isolate, and shutting it down. +/// It does not define the worker logic. +abstract class WorkerHost { + FutureOr start(); + FutureOr close(); +} + /// Host-side helper for workers to perform minimal request/response. class Worker { Worker(this._workerPort) { @@ -60,7 +62,7 @@ class Worker { void close() { if (_closed) return; - _workerPort.send(_Ctl.shutdown); + _workerPort.send(_shutdownCommand); _closed = true; if (_pending.isEmpty) { _responses.close(); @@ -84,10 +86,6 @@ class Worker { } } -class _Ctl { - static const shutdown = '_shutdown_'; -} - /// Worker (isolate) entry-point signature. typedef WorkerEntry = void Function((SendPort, WorkerConfig)); @@ -123,7 +121,11 @@ abstract class WorkerHandler { FutureOr onRequest(Object? payload) => {}; } -/// Generic worker runtime. Reuse for every Sentry worker. +/// Runs the Sentry worker loop inside a background isolate. +/// +/// Call this only from the worker isolate entry-point spawned via +/// [spawnWorker]. It configures logging, handshakes with the host, and routes +/// messages void runWorker( WorkerConfig config, SendPort host, @@ -139,7 +141,7 @@ void runWorker( host.send(inbox.sendPort); inbox.listen((msg) async { - if (msg == _Ctl.shutdown) { + if (msg == _shutdownCommand) { IsolateLogger.log(SentryLevel.debug, 'Isolate received shutdown'); inbox.close(); IsolateLogger.log(SentryLevel.debug, 'Isolate closed'); From 45cc8c30e99bd4685589694d9992b8f56b155f70 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 13:43:22 +0200 Subject: [PATCH 10/78] Update --- .../flutter/lib/src/integrations/native_sdk_integration.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/lib/src/integrations/native_sdk_integration.dart b/packages/flutter/lib/src/integrations/native_sdk_integration.dart index edb17e0c8b..76c91eda6e 100644 --- a/packages/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/packages/flutter/lib/src/integrations/native_sdk_integration.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:sentry/sentry.dart'; -import '../isolate_logger.dart'; import '../native/sentry_native_binding.dart'; import '../sentry_flutter_options.dart'; From a603960cc2b420f600eb69b864599f6026444f76 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 15:16:00 +0200 Subject: [PATCH 11/78] Update --- packages/flutter/lib/src/isolate/isolate_worker.dart | 4 ++-- .../lib/src/native/cocoa/cocoa_envelope_sender.dart | 10 +++++++--- .../lib/src/native/java/android_envelope_sender.dart | 10 +++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 70d1760e69..5ad2900b4e 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -14,7 +14,7 @@ const _shutdownCommand = '_shutdown_'; class WorkerConfig { final bool debug; final SentryLevel diagnosticLevel; - final String? debugName; + final String debugName; const WorkerConfig({ required this.debug, @@ -134,7 +134,7 @@ void runWorker( IsolateLogger.configure( debug: config.debug, level: config.diagnosticLevel, - loggerName: config.debugName ?? 'SentryIsolateWorker', + loggerName: config.debugName, ); final inbox = ReceivePort(); diff --git a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index 4f6de62582..be70515ab5 100644 --- a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -10,17 +10,21 @@ import '../../isolate/isolate_worker.dart'; import '../../isolate/isolate_logger.dart'; import 'binding.dart' as cocoa; +typedef SpawnWorkerFn = Future Function(WorkerConfig, WorkerEntry); + class CocoaEnvelopeSender implements WorkerHost { final SentryFlutterOptions _options; final WorkerConfig _config; + final SpawnWorkerFn _spawn; Worker? _worker; - CocoaEnvelopeSender(this._options) + CocoaEnvelopeSender(this._options, {SpawnWorkerFn? spawn}) : _config = WorkerConfig( debugName: 'SentryCocoaEnvelopeSender', debug: _options.debug, diagnosticLevel: _options.diagnosticLevel, - ); + ), + _spawn = spawn ?? spawnWorker; @internal // visible for testing/mocking static CocoaEnvelopeSender Function(SentryFlutterOptions) factory = @@ -29,7 +33,7 @@ class CocoaEnvelopeSender implements WorkerHost { @override FutureOr start() async { if (_worker != null) return; - _worker = await spawnWorker(_config, _entryPoint); + _worker = await _spawn(_config, _entryPoint); } @override diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index ff1b1b9cc8..e77c398311 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -10,17 +10,21 @@ import '../../isolate/isolate_worker.dart'; import '../../isolate/isolate_logger.dart'; import 'binding.dart' as native; +typedef SpawnWorkerFn = Future Function(WorkerConfig, WorkerEntry); + class AndroidEnvelopeSender implements WorkerHost { final SentryFlutterOptions _options; final WorkerConfig _config; + final SpawnWorkerFn _spawn; Worker? _worker; - AndroidEnvelopeSender(this._options) + AndroidEnvelopeSender(this._options, {SpawnWorkerFn? spawn}) : _config = WorkerConfig( debugName: 'SentryAndroidEnvelopeSender', debug: _options.debug, diagnosticLevel: _options.diagnosticLevel, - ); + ), + _spawn = spawn ?? spawnWorker; @internal // visible for testing/mocking static AndroidEnvelopeSender Function(SentryFlutterOptions) factory = @@ -29,7 +33,7 @@ class AndroidEnvelopeSender implements WorkerHost { @override FutureOr start() async { if (_worker != null) return; - _worker = await spawnWorker(_config, _entryPoint); + _worker = await _spawn(_config, _entryPoint); } @override From 147da011587df77e6644302e77fe715cbd96930b Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 15:20:52 +0200 Subject: [PATCH 12/78] Update --- packages/flutter/lib/src/native/java/sentry_native_java.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index a6df55c0af..df84f63978 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -80,12 +80,10 @@ class SentryNativeJava extends SentryNativeChannel { return super.init(hub); } - late AndroidEnvelopeSender envelopeWorker; - @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { - envelopeWorker.captureEnvelope(envelopeData, containsUnhandledException); + _envelopeSender?.captureEnvelope(envelopeData, containsUnhandledException); } @override From 2b11149a0239bf3506796d6a943d7e3eec920392 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 15:21:29 +0200 Subject: [PATCH 13/78] Update --- packages/flutter/lib/src/native/java/sentry_native_java.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index df84f63978..58fb8657ce 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -174,7 +174,7 @@ class SentryNativeJava extends SentryNativeChannel { @override Future close() async { await _replayRecorder?.stop(); - envelopeWorker.close(); + await _envelopeSender?.close(); return super.close(); } } From 83259527ff83f22a4107a0a16a01c1e91bb145bc Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 15:27:55 +0200 Subject: [PATCH 14/78] Update --- .../test/isolate/isolate_logger_test.dart | 52 +++++ .../test/isolate/isolate_worker_test.dart | 203 ++++++++++++++++++ .../native/android_envelope_sender_test.dart | 191 ++++++++++++++++ .../native/cocoa_envelope_sender_test.dart | 188 ++++++++++++++++ 4 files changed, 634 insertions(+) create mode 100644 packages/flutter/test/isolate/isolate_logger_test.dart create mode 100644 packages/flutter/test/isolate/isolate_worker_test.dart create mode 100644 packages/flutter/test/native/android_envelope_sender_test.dart create mode 100644 packages/flutter/test/native/cocoa_envelope_sender_test.dart diff --git a/packages/flutter/test/isolate/isolate_logger_test.dart b/packages/flutter/test/isolate/isolate_logger_test.dart new file mode 100644 index 0000000000..5e804bdf5c --- /dev/null +++ b/packages/flutter/test/isolate/isolate_logger_test.dart @@ -0,0 +1,52 @@ +@TestOn('vm') +library; + +import 'dart:isolate'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/isolate/isolate_logger.dart'; + +void _entryUnconfigured(SendPort sendPort) { + try { + IsolateLogger.log(SentryLevel.info, 'x'); + sendPort.send('no-error'); + } catch (e) { + sendPort.send(e.runtimeType.toString()); + } +} + +void main() { + test('configure required before log (debug builds)', () async { + final rp = ReceivePort(); + await Isolate.spawn(_entryUnconfigured, rp.sendPort, + debugName: 'LoggerUnconfigured'); + final result = await rp.first; + rp.close(); + + // In debug mode, assert triggers AssertionError before any late fields are read. + expect(result, 'AssertionError'); + }); + + test('fatal logs even when debug=false', () { + IsolateLogger.configure( + debug: false, + level: SentryLevel.error, + loggerName: 't', + ); + expect(() => IsolateLogger.log(SentryLevel.fatal, 'fatal ok'), + returnsNormally); + }); + + test('threshold gating (no-throw at info below warning)', () { + IsolateLogger.configure( + debug: true, + level: SentryLevel.warning, + loggerName: 't', + ); + expect( + () => IsolateLogger.log(SentryLevel.info, 'info ok'), returnsNormally); + expect(() => IsolateLogger.log(SentryLevel.warning, 'warn ok'), + returnsNormally); + }); +} diff --git a/packages/flutter/test/isolate/isolate_worker_test.dart b/packages/flutter/test/isolate/isolate_worker_test.dart new file mode 100644 index 0000000000..be5784a4a2 --- /dev/null +++ b/packages/flutter/test/isolate/isolate_worker_test.dart @@ -0,0 +1,203 @@ +@TestOn('vm') +library; + +import 'dart:async'; +import 'dart:isolate'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; + +class _EchoHandler extends WorkerHandler { + @override + Future onMessage(Object? message) async { + if (message is (SendPort, Object?)) { + message.$1.send(message.$2); + } + } + + @override + Future onRequest(Object? payload) async => payload; +} + +class _ErrorHandler extends WorkerHandler { + @override + Future onMessage(Object? message) async {} + + @override + Future onRequest(Object? payload) async { + throw Exception('boom'); + } +} + +class _DelayHandler extends WorkerHandler { + @override + Future onMessage(Object? message) async {} + + @override + Future onRequest(Object? payload) async { + final milliseconds = payload as int; + await Future.delayed(Duration(milliseconds: milliseconds)); + return 'd:$milliseconds'; + } +} + +class _DebugNameHandler extends WorkerHandler { + @override + Future onMessage(Object? message) async {} + + @override + Future onRequest(Object? payload) async { + return Isolate.current.debugName; + } +} + +void _entryEcho((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _EchoHandler()); +} + +void _entryError((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _ErrorHandler()); +} + +void _entryDelay((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _DelayHandler()); +} + +void _entryDebugName((SendPort, WorkerConfig) init) { + final (host, config) = init; + runWorker(config, host, _DebugNameHandler()); +} + +void main() { + group('Worker isolate', () { + test('request/response echoes', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'EchoWorker', + ), + _entryEcho, + ); + try { + final result = await worker.request('ping'); + expect(result, 'ping'); + } finally { + worker.close(); + } + }); + + test('fire-and-forget can ack via SendPort', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'AckWorker', + ), + _entryEcho, + ); + try { + final rp = ReceivePort(); + worker.send((rp.sendPort, 'ok')); + expect(await rp.first, 'ok'); + rp.close(); + } finally { + worker.close(); + } + }); + + test('request errors propagate as RemoteError', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'ErrorWorker', + ), + _entryError, + ); + try { + expect(() => worker.request('any'), throwsA(isA())); + } finally { + worker.close(); + } + }); + + test('concurrent requests are correlated', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'DelayWorker', + ), + _entryDelay, + ); + try { + final futures = >[ + worker.request(50), + worker.request(10), + worker.request(30), + ]; + final results = await Future.wait(futures); + expect(results, ['d:50', 'd:10', 'd:30']); + } finally { + worker.close(); + } + }); + + test('close rejects new requests; in-flight completes', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'CloseWorker', + ), + _entryDelay, + ); + try { + final inFlight = worker.request(30); + worker.close(); + expect(() => worker.request(1), throwsA(isA())); + expect(await inFlight, 'd:30'); + } finally { + // idempotent + worker.close(); + } + }); + + test('send after close is a no-op and does not throw', () async { + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: 'NoThrowSendAfterCloseWorker', + ), + _entryEcho, + ); + worker.close(); + // Fire-and-forget send should be safe and not throw even after close. + expect(() => worker.send('ignored'), returnsNormally); + }); + + test('debugName propagates to worker isolate', () async { + const debugName = 'DebugNameWorker'; + final worker = await spawnWorker( + const WorkerConfig( + debug: true, + diagnosticLevel: SentryLevel.debug, + debugName: debugName, + ), + _entryDebugName, + ); + try { + final result = await worker.request(null); + expect(result, debugName); + } finally { + worker.close(); + } + }); + }); +} diff --git a/packages/flutter/test/native/android_envelope_sender_test.dart b/packages/flutter/test/native/android_envelope_sender_test.dart new file mode 100644 index 0000000000..d1ba80e282 --- /dev/null +++ b/packages/flutter/test/native/android_envelope_sender_test.dart @@ -0,0 +1,191 @@ +@TestOn('vm') +// ignore_for_file: invalid_use_of_internal_member +library; + +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/native/java/android_envelope_sender.dart'; +import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; + +void main() { + group('AndroidEnvelopeSender host behavior', () { + test('warns and drops when not started', () { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = AndroidEnvelopeSender(options); + sender.captureEnvelope(Uint8List.fromList([1, 2, 3]), false); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains( + 'captureEnvelope called before worker started; dropping')), + isTrue, + ); + }); + + test('close is a no-op when not started', () { + final options = SentryFlutterOptions(); + final sender = AndroidEnvelopeSender(options); + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('warns and drops after close', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = AndroidEnvelopeSender(options); + await sender.start(); + sender.close(); + + sender.captureEnvelope(Uint8List.fromList([9]), false); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains( + 'captureEnvelope called before worker started; dropping')), + isTrue, + ); + }); + + test('start is a no-op when already started', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + var spawnCount = 0; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + spawnCount++; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + + await sender.start(); + await sender.start(); + expect(spawnCount, 1); + + sender.close(); + spawnCount = 0; + + await sender.start(); + expect(spawnCount, 1); + + // Close twice should be safe. + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('delivers tuple to worker after start', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + final payload = Uint8List.fromList([4, 5, 6]); + sender.captureEnvelope(payload, true); + + final msg = await inboxes.last.first; + expect(msg, isA<(TransferableTypedData, bool)>()); + final (transferable, containsUnhandled) = + msg as (TransferableTypedData, bool); + expect(containsUnhandled, true); + final data = transferable.materialize().asUint8List(); + expect(data, [4, 5, 6]); + + sender.close(); + }); + + test('uses expected WorkerConfig', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + WorkerConfig? seenConfig; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + seenConfig = config; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + expect(seenConfig, isNotNull); + expect(seenConfig!.debugName, 'SentryAndroidEnvelopeSender'); + expect(seenConfig!.debug, options.debug); + expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); + + sender.close(); + }); + + test('sends are delivered sequentially with flags', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + sender.captureEnvelope(Uint8List.fromList([10]), true); + sender.captureEnvelope(Uint8List.fromList([11]), false); + + final inbox = inboxes.last; + final msgs = await inbox.take(2).toList(); + final msg1 = msgs[0]; + final msg2 = msgs[1]; + + expect(msg1, isA<(TransferableTypedData, bool)>()); + expect(msg2, isA<(TransferableTypedData, bool)>()); + + final (t1, f1) = msg1 as (TransferableTypedData, bool); + final (t2, f2) = msg2 as (TransferableTypedData, bool); + expect(f1, true); + expect(f2, false); + final data1 = t1.materialize().asUint8List(); + final data2 = t2.materialize().asUint8List(); + expect(data1, [10]); + expect(data2, [11]); + + sender.close(); + }); + }); +} diff --git a/packages/flutter/test/native/cocoa_envelope_sender_test.dart b/packages/flutter/test/native/cocoa_envelope_sender_test.dart new file mode 100644 index 0000000000..37aed72847 --- /dev/null +++ b/packages/flutter/test/native/cocoa_envelope_sender_test.dart @@ -0,0 +1,188 @@ +@TestOn('vm') +// ignore_for_file: invalid_use_of_internal_member +library; + +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/native/cocoa/cocoa_envelope_sender.dart'; +import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; + +void main() { + group('CocoaEnvelopeSender host behavior', () { + test('warns and drops when not started', () { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = CocoaEnvelopeSender(options); + sender.captureEnvelope(Uint8List.fromList([1, 2, 3])); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains('captureEnvelope called before start; dropping')), + isTrue, + ); + }); + + test('close is a no-op when not started', () { + final options = SentryFlutterOptions(); + final sender = CocoaEnvelopeSender(options); + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('start is a no-op when already started', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + var spawnCount = 0; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + spawnCount++; + final inbox = ReceivePort(); + late final StreamSubscription sub; + sub = inbox.listen((msg) async { + if (msg == '_shutdown_') { + await sub.cancel(); + inbox.close(); + } + }); + return Worker(inbox.sendPort); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + + await sender.start(); + await sender.start(); + expect(spawnCount, 1); + + sender.close(); + spawnCount = 0; + + await sender.start(); + expect(spawnCount, 1); + + // Close twice should be safe. + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('warns and drops after close', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = CocoaEnvelopeSender(options); + await sender.start(); + sender.close(); + + sender.captureEnvelope(Uint8List.fromList([9])); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains('captureEnvelope called before start; dropping')), + isTrue, + ); + }); + + test('sends are delivered sequentially', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + sender.captureEnvelope(Uint8List.fromList([10])); + sender.captureEnvelope(Uint8List.fromList([11])); + + final inbox = inboxes.last; + final msgs = await inbox.take(2).toList(); + final msg1 = msgs[0]; + final msg2 = msgs[1]; + + expect(msg1, isA()); + expect(msg2, isA()); + + final data1 = (msg1 as TransferableTypedData).materialize().asUint8List(); + final data2 = (msg2 as TransferableTypedData).materialize().asUint8List(); + expect(data1, [10]); + expect(data2, [11]); + + sender.close(); + }); + + test('delivers to worker after start', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + final payload = Uint8List.fromList([1, 2, 3]); + sender.captureEnvelope(payload); + + final msg = await inboxes.last.first; + expect(msg, isA()); + final data = (msg as TransferableTypedData).materialize().asUint8List(); + expect(data, [1, 2, 3]); + + sender.close(); + }); + + test('uses expected WorkerConfig', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + WorkerConfig? seenConfig; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + seenConfig = config; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + return Worker(inbox.sendPort); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + expect(seenConfig, isNotNull); + expect(seenConfig!.debugName, 'SentryCocoaEnvelopeSender'); + expect(seenConfig!.debug, options.debug); + expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); + + sender.close(); + }); + }); +} From 3dbe75120cff66962ba9565b814b7e630366c6ab Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 15:56:48 +0200 Subject: [PATCH 15/78] Update --- .../lib/src/isolate/isolate_worker.dart | 34 ++++++++++++------- .../native/android_envelope_sender_test.dart | 12 ++++--- .../native/cocoa_envelope_sender_test.dart | 12 ++++--- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 5ad2900b4e..31ca49cec9 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -33,14 +33,15 @@ abstract class WorkerHost { } /// Host-side helper for workers to perform minimal request/response. +/// Adapted from https://dart.dev/language/isolates#robust-ports-example class Worker { - Worker(this._workerPort) { + Worker(this._workerPort, this._responses) { _responses.listen(_handleResponse); } final SendPort _workerPort; SendPort get port => _workerPort; - final ReceivePort _responses = ReceivePort(); + final ReceivePort _responses; final Map> _pending = {}; int _idCounter = 0; bool _closed = false; @@ -56,7 +57,7 @@ class Worker { final id = _idCounter++; final completer = Completer.sync(); _pending[id] = completer; - _workerPort.send((id, payload, _responses.sendPort)); + _workerPort.send((id, payload)); return completer.future; } @@ -94,14 +95,23 @@ Future spawnWorker( WorkerConfig config, WorkerEntry entry, ) async { - final receivePort = ReceivePort(); + final initPort = RawReceivePort(); + final connection = Completer<(ReceivePort, SendPort)>.sync(); + initPort.handler = (SendPort commandPort) { + connection.complete(( + ReceivePort.fromRawReceivePort(initPort), + commandPort, + )); + }; + await Isolate.spawn<(SendPort, WorkerConfig)>( entry, - (receivePort.sendPort, config), + (initPort.sendPort, config), debugName: config.debugName, ); - final workerPort = await receivePort.first as SendPort; - return Worker(workerPort); + + final (ReceivePort receivePort, SendPort sendPort) = await connection.future; + return Worker(sendPort, receivePort); } // ------------------------------------------- @@ -148,14 +158,14 @@ void runWorker( return; } - // RPC: (id, payload, replyTo) - if (msg is (int, Object?, SendPort)) { - final (id, payload, replyTo) = msg; + // RPC: (id, payload) + if (msg is (int, Object?)) { + final (id, payload) = msg; try { final result = await handler.onRequest(payload); - replyTo.send((id, result)); + host.send((id, result)); } catch (e, st) { - replyTo.send((id, RemoteError(e.toString(), st.toString()))); + host.send((id, RemoteError(e.toString(), st.toString()))); } return; } diff --git a/packages/flutter/test/native/android_envelope_sender_test.dart b/packages/flutter/test/native/android_envelope_sender_test.dart index d1ba80e282..dcd157c3e6 100644 --- a/packages/flutter/test/native/android_envelope_sender_test.dart +++ b/packages/flutter/test/native/android_envelope_sender_test.dart @@ -75,7 +75,8 @@ void main() { spawnCount++; final inbox = ReceivePort(); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); @@ -105,7 +106,8 @@ void main() { final inbox = ReceivePort(); inboxes.add(inbox); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); @@ -135,7 +137,8 @@ void main() { seenConfig = config; final inbox = ReceivePort(); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); @@ -159,7 +162,8 @@ void main() { final inbox = ReceivePort(); inboxes.add(inbox); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); diff --git a/packages/flutter/test/native/cocoa_envelope_sender_test.dart b/packages/flutter/test/native/cocoa_envelope_sender_test.dart index 37aed72847..31c18373c3 100644 --- a/packages/flutter/test/native/cocoa_envelope_sender_test.dart +++ b/packages/flutter/test/native/cocoa_envelope_sender_test.dart @@ -56,7 +56,8 @@ void main() { inbox.close(); } }); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); @@ -109,7 +110,8 @@ void main() { final inbox = ReceivePort(); inboxes.add(inbox); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); @@ -144,7 +146,8 @@ void main() { final inbox = ReceivePort(); inboxes.add(inbox); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); @@ -171,7 +174,8 @@ void main() { seenConfig = config; final inbox = ReceivePort(); addTearDown(() => inbox.close()); - return Worker(inbox.sendPort); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); } final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); From 71ba593d163e856e30819ed8dc9bb181069e4ef7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 16:01:55 +0200 Subject: [PATCH 16/78] Update --- packages/flutter/lib/src/isolate/isolate_worker.dart | 9 --------- .../lib/src/native/cocoa/cocoa_envelope_sender.dart | 4 +--- .../lib/src/native/java/android_envelope_sender.dart | 4 +--- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 31ca49cec9..d76f4b6b33 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -23,15 +23,6 @@ class WorkerConfig { }); } -/// Host-side lifecycle interface for a worker isolate. -/// -/// Responsible for spawning the worker isolate, and shutting it down. -/// It does not define the worker logic. -abstract class WorkerHost { - FutureOr start(); - FutureOr close(); -} - /// Host-side helper for workers to perform minimal request/response. /// Adapted from https://dart.dev/language/isolates#robust-ports-example class Worker { diff --git a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index be70515ab5..a371518235 100644 --- a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -12,7 +12,7 @@ import 'binding.dart' as cocoa; typedef SpawnWorkerFn = Future Function(WorkerConfig, WorkerEntry); -class CocoaEnvelopeSender implements WorkerHost { +class CocoaEnvelopeSender { final SentryFlutterOptions _options; final WorkerConfig _config; final SpawnWorkerFn _spawn; @@ -30,13 +30,11 @@ class CocoaEnvelopeSender implements WorkerHost { static CocoaEnvelopeSender Function(SentryFlutterOptions) factory = CocoaEnvelopeSender.new; - @override FutureOr start() async { if (_worker != null) return; _worker = await _spawn(_config, _entryPoint); } - @override FutureOr close() { _worker?.close(); _worker = null; diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index e77c398311..05be049268 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -12,7 +12,7 @@ import 'binding.dart' as native; typedef SpawnWorkerFn = Future Function(WorkerConfig, WorkerEntry); -class AndroidEnvelopeSender implements WorkerHost { +class AndroidEnvelopeSender { final SentryFlutterOptions _options; final WorkerConfig _config; final SpawnWorkerFn _spawn; @@ -30,13 +30,11 @@ class AndroidEnvelopeSender implements WorkerHost { static AndroidEnvelopeSender Function(SentryFlutterOptions) factory = AndroidEnvelopeSender.new; - @override FutureOr start() async { if (_worker != null) return; _worker = await _spawn(_config, _entryPoint); } - @override FutureOr close() { _worker?.close(); _worker = null; From fe7f6dfccd60e50ad01b4508acb8d34911502a80 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 4 Sep 2025 16:04:52 +0200 Subject: [PATCH 17/78] Update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a567ca68f6..df6db27aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Enhancements + +- Offload `captureEnvelope` to background isolate for iOS and Android ([#3232](https://github.com/getsentry/sentry-dart/pull/3232)) + ## 9.7.0-beta.2 ### Features From 39a951e49fcbe53ef4785897637156a730637d08 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 11:08:19 +0200 Subject: [PATCH 18/78] Update --- packages/flutter/lib/src/isolate/isolate_logger.dart | 4 ++-- packages/flutter/lib/src/isolate/isolate_worker.dart | 4 +--- .../flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart | 2 +- .../flutter/lib/src/native/java/android_envelope_sender.dart | 2 +- packages/flutter/test/isolate/isolate_worker_test.dart | 1 - 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_logger.dart b/packages/flutter/lib/src/isolate/isolate_logger.dart index 9abf4e6ac2..5fe97dff86 100644 --- a/packages/flutter/lib/src/isolate/isolate_logger.dart +++ b/packages/flutter/lib/src/isolate/isolate_logger.dart @@ -2,7 +2,7 @@ import 'dart:developer' as developer; import '../../sentry_flutter.dart'; -/// Isolate-local logger that writes diagnostic messages to `dart:developer.log`. +/// Static logger for Isolates that writes diagnostic messages to `dart:developer.log`. /// /// Intended for worker/background isolates where a `SentryOptions` instance /// or hub may not be available. Because Dart statics are isolate-local, @@ -32,7 +32,7 @@ class IsolateLogger { _isConfigured = true; } - /// Emits a log entry if enabled for this isolate. + /// Emits a log entry if enabled. /// /// Messages are forwarded to [developer.log]. The provided [level] is /// mapped via [SentryLevel.toDartLogLevel] to a `developer.log` numeric level. diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index d76f4b6b33..434e4b232e 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -10,7 +10,7 @@ const _shutdownCommand = '_shutdown_'; // HOST-SIDE API (runs on the main isolate) // ------------------------------------------- -/// Minimal config passed to isolates. Extend as needed. +/// Minimal config passed to isolates - extend as needed. class WorkerConfig { final bool debug; final SentryLevel diagnosticLevel; @@ -149,7 +149,6 @@ void runWorker( return; } - // RPC: (id, payload) if (msg is (int, Object?)) { final (id, payload) = msg; try { @@ -161,7 +160,6 @@ void runWorker( return; } - // Fire-and-forget try { await handler.onMessage(msg); } catch (exception, stackTrace) { diff --git a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index a371518235..72f3eb74e6 100644 --- a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -26,7 +26,7 @@ class CocoaEnvelopeSender { ), _spawn = spawn ?? spawnWorker; - @internal // visible for testing/mocking + @internal static CocoaEnvelopeSender Function(SentryFlutterOptions) factory = CocoaEnvelopeSender.new; diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index 05be049268..59d53a3960 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -26,7 +26,7 @@ class AndroidEnvelopeSender { ), _spawn = spawn ?? spawnWorker; - @internal // visible for testing/mocking + @internal static AndroidEnvelopeSender Function(SentryFlutterOptions) factory = AndroidEnvelopeSender.new; diff --git a/packages/flutter/test/isolate/isolate_worker_test.dart b/packages/flutter/test/isolate/isolate_worker_test.dart index be5784a4a2..f1b6d630fb 100644 --- a/packages/flutter/test/isolate/isolate_worker_test.dart +++ b/packages/flutter/test/isolate/isolate_worker_test.dart @@ -1,7 +1,6 @@ @TestOn('vm') library; -import 'dart:async'; import 'dart:isolate'; import 'package:flutter_test/flutter_test.dart'; From 884642f5bd105bbaa36870fa1febdb7a085d1bed Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 11:31:31 +0200 Subject: [PATCH 19/78] Fix test --- packages/flutter/test/sentry_native_channel_test.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index c0a49c0aa4..f0ab1bceb6 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -194,6 +194,7 @@ void main() { }); test('startProfiler', () { + sut.startProfiler(SentryId.newId()); final matcher = _nativeUnavailableMatcher( mockPlatform, androidUnsupported: true, @@ -238,13 +239,8 @@ void main() { when(channel.invokeMethod('captureEnvelope', any)) .thenAnswer((_) async => {}); - final matcher = _nativeUnavailableMatcher( - mockPlatform, - includeLookupSymbol: true, - ); - final data = Uint8List.fromList([1, 2, 3]); - expect(() => sut.captureEnvelope(data, false), matcher); + sut.captureEnvelope(data, false); verifyZeroInteractions(channel); }, @@ -267,7 +263,7 @@ void main() { mockPlatform, includeLookupSymbol: true, ); - + sut.loadDebugImages(SentryStackTrace(frames: [])); expect( () => sut.loadDebugImages(SentryStackTrace(frames: [])), matcher); From f5f5401069b3eef5de55d10dfbd79f8e061022b4 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 11:40:39 +0200 Subject: [PATCH 20/78] Update --- .../lib/src/isolate/isolate_logger.dart | 21 +++++++++++++++--- .../test/isolate/isolate_logger_test.dart | 22 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_logger.dart b/packages/flutter/lib/src/isolate/isolate_logger.dart index 5fe97dff86..bb8a8b7fe0 100644 --- a/packages/flutter/lib/src/isolate/isolate_logger.dart +++ b/packages/flutter/lib/src/isolate/isolate_logger.dart @@ -1,5 +1,7 @@ import 'dart:developer' as developer; +import 'package:meta/meta.dart'; + import '../../sentry_flutter.dart'; /// Static logger for Isolates that writes diagnostic messages to `dart:developer.log`. @@ -10,14 +12,15 @@ import '../../sentry_flutter.dart'; class IsolateLogger { IsolateLogger._(); - static late final bool _debug; - static late final SentryLevel _level; - static late final String _loggerName; + static late bool _debug; + static late SentryLevel _level; + static late String _loggerName; static bool _isConfigured = false; /// Configures this logger for the current isolate. /// /// Must be called once per isolate before invoking [log]. + /// Throws [StateError] if called more than once without calling [reset] first. /// /// - [debug]: when false, suppresses all logs except [SentryLevel.fatal]. /// - [level]: minimum severity threshold (inclusive) when [debug] is true. @@ -26,12 +29,24 @@ class IsolateLogger { {required bool debug, required SentryLevel level, required String loggerName}) { + if (_isConfigured) { + throw StateError( + 'IsolateLogger.configure has already been called. It can only be configured once per isolate.'); + } _debug = debug; _level = level; _loggerName = loggerName; _isConfigured = true; } + /// Resets the logger state to allow reconfiguration. + /// + /// This is intended for testing purposes only. + @visibleForTesting + static void reset() { + _isConfigured = false; + } + /// Emits a log entry if enabled. /// /// Messages are forwarded to [developer.log]. The provided [level] is diff --git a/packages/flutter/test/isolate/isolate_logger_test.dart b/packages/flutter/test/isolate/isolate_logger_test.dart index 5e804bdf5c..5d6fddfc96 100644 --- a/packages/flutter/test/isolate/isolate_logger_test.dart +++ b/packages/flutter/test/isolate/isolate_logger_test.dart @@ -17,6 +17,10 @@ void _entryUnconfigured(SendPort sendPort) { } void main() { + setUp(() { + IsolateLogger.reset(); + }); + test('configure required before log (debug builds)', () async { final rp = ReceivePort(); await Isolate.spawn(_entryUnconfigured, rp.sendPort, @@ -24,8 +28,7 @@ void main() { final result = await rp.first; rp.close(); - // In debug mode, assert triggers AssertionError before any late fields are read. - expect(result, 'AssertionError'); + expect(result, '_AssertionError'); }); test('fatal logs even when debug=false', () { @@ -49,4 +52,19 @@ void main() { expect(() => IsolateLogger.log(SentryLevel.warning, 'warn ok'), returnsNormally); }); + + test('prevents reconfiguration without reset', () { + IsolateLogger.configure( + debug: true, + level: SentryLevel.info, + loggerName: 't', + ); + expect( + () => IsolateLogger.configure( + debug: false, + level: SentryLevel.error, + loggerName: 't2', + ), + throwsStateError); + }); } From de232c6ef9958eee4bcb65c8e5935f6fb52cfe6c Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 11:42:42 +0200 Subject: [PATCH 21/78] Update --- .../flutter/test/integrations/thread_info_integration_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/test/integrations/thread_info_integration_test.dart b/packages/flutter/test/integrations/thread_info_integration_test.dart index 3615e84814..75e4a8595b 100644 --- a/packages/flutter/test/integrations/thread_info_integration_test.dart +++ b/packages/flutter/test/integrations/thread_info_integration_test.dart @@ -4,8 +4,8 @@ library; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/src/integrations/thread_info_integration.dart'; -import 'package:sentry_flutter/src/isolate_helper.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/isolate/isolate_helper.dart'; import '../mocks.mocks.dart'; From 69d51119dc220ce4479a59ee79cf477416099240 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 12:01:22 +0200 Subject: [PATCH 22/78] Update --- packages/flutter/test/native/android_envelope_sender_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/test/native/android_envelope_sender_test.dart b/packages/flutter/test/native/android_envelope_sender_test.dart index dcd157c3e6..64067db2bb 100644 --- a/packages/flutter/test/native/android_envelope_sender_test.dart +++ b/packages/flutter/test/native/android_envelope_sender_test.dart @@ -2,7 +2,6 @@ // ignore_for_file: invalid_use_of_internal_member library; -import 'dart:async'; import 'dart:isolate'; import 'dart:typed_data'; From 62bb12bbf0c42aa56cd31d1fd43c9878394b6b02 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 12:13:27 +0200 Subject: [PATCH 23/78] Add automatedTestMode option --- .../flutter/lib/src/isolate/isolate_worker.dart | 2 ++ .../src/native/cocoa/cocoa_envelope_sender.dart | 10 +++++++++- .../src/native/java/android_envelope_sender.dart | 14 +++++++++----- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 434e4b232e..bcf3bdbb67 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -15,11 +15,13 @@ class WorkerConfig { final bool debug; final SentryLevel diagnosticLevel; final String debugName; + final bool automatedTestMode; const WorkerConfig({ required this.debug, required this.diagnosticLevel, required this.debugName, + this.automatedTestMode = false, }); } diff --git a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart index 72f3eb74e6..57f47325c7 100644 --- a/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart +++ b/packages/flutter/lib/src/native/cocoa/cocoa_envelope_sender.dart @@ -23,6 +23,7 @@ class CocoaEnvelopeSender { debugName: 'SentryCocoaEnvelopeSender', debug: _options.debug, diagnosticLevel: _options.diagnosticLevel, + automatedTestMode: _options.automatedTestMode, ), _spawn = spawn ?? spawnWorker; @@ -55,11 +56,15 @@ class CocoaEnvelopeSender { static void _entryPoint((SendPort, WorkerConfig) init) { final (host, config) = init; - runWorker(config, host, _CocoaEnvelopeHandler()); + runWorker(config, host, _CocoaEnvelopeHandler(config)); } } class _CocoaEnvelopeHandler extends WorkerHandler { + final WorkerConfig _config; + + _CocoaEnvelopeHandler(this._config); + @override FutureOr onMessage(Object? msg) { if (msg is TransferableTypedData) { @@ -83,6 +88,9 @@ class _CocoaEnvelopeHandler extends WorkerHandler { } catch (exception, stackTrace) { IsolateLogger.log(SentryLevel.error, 'Failed to capture envelope', exception: exception, stackTrace: stackTrace); + if (_config.automatedTestMode) { + rethrow; + } } } } diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index 59d53a3960..e3855e6f45 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -23,6 +23,7 @@ class AndroidEnvelopeSender { debugName: 'SentryAndroidEnvelopeSender', debug: _options.debug, diagnosticLevel: _options.diagnosticLevel, + automatedTestMode: _options.automatedTestMode, ), _spawn = spawn ?? spawnWorker; @@ -59,11 +60,15 @@ class AndroidEnvelopeSender { static void _entryPoint((SendPort, WorkerConfig) init) { final (host, config) = init; - runWorker(config, host, _AndroidEnvelopeHandler()); + runWorker(config, host, _AndroidEnvelopeHandler(config)); } } class _AndroidEnvelopeHandler extends WorkerHandler { + final WorkerConfig _config; + + _AndroidEnvelopeHandler(this._config); + @override FutureOr onMessage(Object? msg) { if (msg is (TransferableTypedData, bool)) { @@ -91,10 +96,9 @@ class _AndroidEnvelopeHandler extends WorkerHandler { } catch (exception, stackTrace) { IsolateLogger.log(SentryLevel.error, 'Failed to capture envelope', exception: exception, stackTrace: stackTrace); - // TODO: - // if (options.automatedTestMode) { - // rethrow; - // } + if (_config.automatedTestMode) { + rethrow; + } } finally { byteArray?.release(); id?.release(); From 53c603611e73e55c0b5c86a78d40be8d1bce8c3e Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 14:30:34 +0200 Subject: [PATCH 24/78] Update --- packages/flutter/example/pubspec_overrides.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/example/pubspec_overrides.yaml b/packages/flutter/example/pubspec_overrides.yaml index 8f6b711d3b..7dafca339e 100644 --- a/packages/flutter/example/pubspec_overrides.yaml +++ b/packages/flutter/example/pubspec_overrides.yaml @@ -21,4 +21,3 @@ dependency_overrides: isar_flutter_libs: git: url: https://github.com/MrLittleWhite/isar_flutter_libs.git - From 06ee227757bebda500cd3c3f34f68f760ed52250 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 14:59:28 +0200 Subject: [PATCH 25/78] Fix web tests --- .../native/android_envelope_sender_test.dart | 196 +----------------- .../android_envelope_sender_test_real.dart | 194 +++++++++++++++++ .../android_envelope_sender_test_web.dart | 10 + .../native/cocoa_envelope_sender_test.dart | 194 +---------------- .../cocoa_envelope_sender_test_real.dart | 192 +++++++++++++++++ .../cocoa_envelope_sender_test_web.dart | 10 + 6 files changed, 410 insertions(+), 386 deletions(-) create mode 100644 packages/flutter/test/native/android_envelope_sender_test_real.dart create mode 100644 packages/flutter/test/native/android_envelope_sender_test_web.dart create mode 100644 packages/flutter/test/native/cocoa_envelope_sender_test_real.dart create mode 100644 packages/flutter/test/native/cocoa_envelope_sender_test_web.dart diff --git a/packages/flutter/test/native/android_envelope_sender_test.dart b/packages/flutter/test/native/android_envelope_sender_test.dart index 64067db2bb..9779018acb 100644 --- a/packages/flutter/test/native/android_envelope_sender_test.dart +++ b/packages/flutter/test/native/android_envelope_sender_test.dart @@ -1,194 +1,2 @@ -@TestOn('vm') -// ignore_for_file: invalid_use_of_internal_member -library; - -import 'dart:isolate'; -import 'dart:typed_data'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/native/java/android_envelope_sender.dart'; -import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; - -void main() { - group('AndroidEnvelopeSender host behavior', () { - test('warns and drops when not started', () { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - final logs = <(SentryLevel, String)>[]; - options.log = (level, message, {logger, exception, stackTrace}) { - logs.add((level, message)); - }; - - final sender = AndroidEnvelopeSender(options); - sender.captureEnvelope(Uint8List.fromList([1, 2, 3]), false); - - expect( - logs.any((e) => - e.$1 == SentryLevel.warning && - e.$2.contains( - 'captureEnvelope called before worker started; dropping')), - isTrue, - ); - }); - - test('close is a no-op when not started', () { - final options = SentryFlutterOptions(); - final sender = AndroidEnvelopeSender(options); - expect(() => sender.close(), returnsNormally); - expect(() => sender.close(), returnsNormally); - }); - - test('warns and drops after close', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - final logs = <(SentryLevel, String)>[]; - options.log = (level, message, {logger, exception, stackTrace}) { - logs.add((level, message)); - }; - - final sender = AndroidEnvelopeSender(options); - await sender.start(); - sender.close(); - - sender.captureEnvelope(Uint8List.fromList([9]), false); - - expect( - logs.any((e) => - e.$1 == SentryLevel.warning && - e.$2.contains( - 'captureEnvelope called before worker started; dropping')), - isTrue, - ); - }); - - test('start is a no-op when already started', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - var spawnCount = 0; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - spawnCount++; - final inbox = ReceivePort(); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); - - await sender.start(); - await sender.start(); - expect(spawnCount, 1); - - sender.close(); - spawnCount = 0; - - await sender.start(); - expect(spawnCount, 1); - - // Close twice should be safe. - expect(() => sender.close(), returnsNormally); - expect(() => sender.close(), returnsNormally); - }); - - test('delivers tuple to worker after start', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - final inboxes = []; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - final inbox = ReceivePort(); - inboxes.add(inbox); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - final payload = Uint8List.fromList([4, 5, 6]); - sender.captureEnvelope(payload, true); - - final msg = await inboxes.last.first; - expect(msg, isA<(TransferableTypedData, bool)>()); - final (transferable, containsUnhandled) = - msg as (TransferableTypedData, bool); - expect(containsUnhandled, true); - final data = transferable.materialize().asUint8List(); - expect(data, [4, 5, 6]); - - sender.close(); - }); - - test('uses expected WorkerConfig', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - WorkerConfig? seenConfig; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - seenConfig = config; - final inbox = ReceivePort(); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - expect(seenConfig, isNotNull); - expect(seenConfig!.debugName, 'SentryAndroidEnvelopeSender'); - expect(seenConfig!.debug, options.debug); - expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); - - sender.close(); - }); - - test('sends are delivered sequentially with flags', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - final inboxes = []; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - final inbox = ReceivePort(); - inboxes.add(inbox); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - sender.captureEnvelope(Uint8List.fromList([10]), true); - sender.captureEnvelope(Uint8List.fromList([11]), false); - - final inbox = inboxes.last; - final msgs = await inbox.take(2).toList(); - final msg1 = msgs[0]; - final msg2 = msgs[1]; - - expect(msg1, isA<(TransferableTypedData, bool)>()); - expect(msg2, isA<(TransferableTypedData, bool)>()); - - final (t1, f1) = msg1 as (TransferableTypedData, bool); - final (t2, f2) = msg2 as (TransferableTypedData, bool); - expect(f1, true); - expect(f2, false); - final data1 = t1.materialize().asUint8List(); - final data2 = t2.materialize().asUint8List(); - expect(data1, [10]); - expect(data2, [11]); - - sender.close(); - }); - }); -} +export 'android_envelope_sender_test_real.dart' + if (dart.library.js_interop) 'android_envelope_sender_test_web.dart'; diff --git a/packages/flutter/test/native/android_envelope_sender_test_real.dart b/packages/flutter/test/native/android_envelope_sender_test_real.dart new file mode 100644 index 0000000000..64067db2bb --- /dev/null +++ b/packages/flutter/test/native/android_envelope_sender_test_real.dart @@ -0,0 +1,194 @@ +@TestOn('vm') +// ignore_for_file: invalid_use_of_internal_member +library; + +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/native/java/android_envelope_sender.dart'; +import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; + +void main() { + group('AndroidEnvelopeSender host behavior', () { + test('warns and drops when not started', () { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = AndroidEnvelopeSender(options); + sender.captureEnvelope(Uint8List.fromList([1, 2, 3]), false); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains( + 'captureEnvelope called before worker started; dropping')), + isTrue, + ); + }); + + test('close is a no-op when not started', () { + final options = SentryFlutterOptions(); + final sender = AndroidEnvelopeSender(options); + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('warns and drops after close', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = AndroidEnvelopeSender(options); + await sender.start(); + sender.close(); + + sender.captureEnvelope(Uint8List.fromList([9]), false); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains( + 'captureEnvelope called before worker started; dropping')), + isTrue, + ); + }); + + test('start is a no-op when already started', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + var spawnCount = 0; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + spawnCount++; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + + await sender.start(); + await sender.start(); + expect(spawnCount, 1); + + sender.close(); + spawnCount = 0; + + await sender.start(); + expect(spawnCount, 1); + + // Close twice should be safe. + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('delivers tuple to worker after start', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + final payload = Uint8List.fromList([4, 5, 6]); + sender.captureEnvelope(payload, true); + + final msg = await inboxes.last.first; + expect(msg, isA<(TransferableTypedData, bool)>()); + final (transferable, containsUnhandled) = + msg as (TransferableTypedData, bool); + expect(containsUnhandled, true); + final data = transferable.materialize().asUint8List(); + expect(data, [4, 5, 6]); + + sender.close(); + }); + + test('uses expected WorkerConfig', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + WorkerConfig? seenConfig; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + seenConfig = config; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + expect(seenConfig, isNotNull); + expect(seenConfig!.debugName, 'SentryAndroidEnvelopeSender'); + expect(seenConfig!.debug, options.debug); + expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); + + sender.close(); + }); + + test('sends are delivered sequentially with flags', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = AndroidEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + sender.captureEnvelope(Uint8List.fromList([10]), true); + sender.captureEnvelope(Uint8List.fromList([11]), false); + + final inbox = inboxes.last; + final msgs = await inbox.take(2).toList(); + final msg1 = msgs[0]; + final msg2 = msgs[1]; + + expect(msg1, isA<(TransferableTypedData, bool)>()); + expect(msg2, isA<(TransferableTypedData, bool)>()); + + final (t1, f1) = msg1 as (TransferableTypedData, bool); + final (t2, f2) = msg2 as (TransferableTypedData, bool); + expect(f1, true); + expect(f2, false); + final data1 = t1.materialize().asUint8List(); + final data2 = t2.materialize().asUint8List(); + expect(data1, [10]); + expect(data2, [11]); + + sender.close(); + }); + }); +} diff --git a/packages/flutter/test/native/android_envelope_sender_test_web.dart b/packages/flutter/test/native/android_envelope_sender_test_web.dart new file mode 100644 index 0000000000..6b061ee80a --- /dev/null +++ b/packages/flutter/test/native/android_envelope_sender_test_web.dart @@ -0,0 +1,10 @@ +// Stub for web - these tests only run on VM +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Android envelope sender tests are not supported on web', () { + // This test file exists only to satisfy the compiler when running web tests. + // The actual tests in android_envelope_sender_test_real.dart are only + // executed on VM platforms. + }); +} diff --git a/packages/flutter/test/native/cocoa_envelope_sender_test.dart b/packages/flutter/test/native/cocoa_envelope_sender_test.dart index 31c18373c3..15590a436d 100644 --- a/packages/flutter/test/native/cocoa_envelope_sender_test.dart +++ b/packages/flutter/test/native/cocoa_envelope_sender_test.dart @@ -1,192 +1,2 @@ -@TestOn('vm') -// ignore_for_file: invalid_use_of_internal_member -library; - -import 'dart:async'; -import 'dart:isolate'; -import 'dart:typed_data'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/native/cocoa/cocoa_envelope_sender.dart'; -import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; - -void main() { - group('CocoaEnvelopeSender host behavior', () { - test('warns and drops when not started', () { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - final logs = <(SentryLevel, String)>[]; - options.log = (level, message, {logger, exception, stackTrace}) { - logs.add((level, message)); - }; - - final sender = CocoaEnvelopeSender(options); - sender.captureEnvelope(Uint8List.fromList([1, 2, 3])); - - expect( - logs.any((e) => - e.$1 == SentryLevel.warning && - e.$2.contains('captureEnvelope called before start; dropping')), - isTrue, - ); - }); - - test('close is a no-op when not started', () { - final options = SentryFlutterOptions(); - final sender = CocoaEnvelopeSender(options); - expect(() => sender.close(), returnsNormally); - expect(() => sender.close(), returnsNormally); - }); - - test('start is a no-op when already started', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - var spawnCount = 0; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - spawnCount++; - final inbox = ReceivePort(); - late final StreamSubscription sub; - sub = inbox.listen((msg) async { - if (msg == '_shutdown_') { - await sub.cancel(); - inbox.close(); - } - }); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); - - await sender.start(); - await sender.start(); - expect(spawnCount, 1); - - sender.close(); - spawnCount = 0; - - await sender.start(); - expect(spawnCount, 1); - - // Close twice should be safe. - expect(() => sender.close(), returnsNormally); - expect(() => sender.close(), returnsNormally); - }); - - test('warns and drops after close', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - final logs = <(SentryLevel, String)>[]; - options.log = (level, message, {logger, exception, stackTrace}) { - logs.add((level, message)); - }; - - final sender = CocoaEnvelopeSender(options); - await sender.start(); - sender.close(); - - sender.captureEnvelope(Uint8List.fromList([9])); - - expect( - logs.any((e) => - e.$1 == SentryLevel.warning && - e.$2.contains('captureEnvelope called before start; dropping')), - isTrue, - ); - }); - - test('sends are delivered sequentially', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - final inboxes = []; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - final inbox = ReceivePort(); - inboxes.add(inbox); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - sender.captureEnvelope(Uint8List.fromList([10])); - sender.captureEnvelope(Uint8List.fromList([11])); - - final inbox = inboxes.last; - final msgs = await inbox.take(2).toList(); - final msg1 = msgs[0]; - final msg2 = msgs[1]; - - expect(msg1, isA()); - expect(msg2, isA()); - - final data1 = (msg1 as TransferableTypedData).materialize().asUint8List(); - final data2 = (msg2 as TransferableTypedData).materialize().asUint8List(); - expect(data1, [10]); - expect(data2, [11]); - - sender.close(); - }); - - test('delivers to worker after start', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - final inboxes = []; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - final inbox = ReceivePort(); - inboxes.add(inbox); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - final payload = Uint8List.fromList([1, 2, 3]); - sender.captureEnvelope(payload); - - final msg = await inboxes.last.first; - expect(msg, isA()); - final data = (msg as TransferableTypedData).materialize().asUint8List(); - expect(data, [1, 2, 3]); - - sender.close(); - }); - - test('uses expected WorkerConfig', () async { - final options = SentryFlutterOptions(); - options.debug = true; - options.diagnosticLevel = SentryLevel.debug; - - WorkerConfig? seenConfig; - Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { - seenConfig = config; - final inbox = ReceivePort(); - addTearDown(() => inbox.close()); - final replies = ReceivePort(); - return Worker(inbox.sendPort, replies); - } - - final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); - await sender.start(); - - expect(seenConfig, isNotNull); - expect(seenConfig!.debugName, 'SentryCocoaEnvelopeSender'); - expect(seenConfig!.debug, options.debug); - expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); - - sender.close(); - }); - }); -} +export 'cocoa_envelope_sender_test_real.dart' + if (dart.library.js_interop) 'cocoa_envelope_sender_test_web.dart'; diff --git a/packages/flutter/test/native/cocoa_envelope_sender_test_real.dart b/packages/flutter/test/native/cocoa_envelope_sender_test_real.dart new file mode 100644 index 0000000000..31c18373c3 --- /dev/null +++ b/packages/flutter/test/native/cocoa_envelope_sender_test_real.dart @@ -0,0 +1,192 @@ +@TestOn('vm') +// ignore_for_file: invalid_use_of_internal_member +library; + +import 'dart:async'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/native/cocoa/cocoa_envelope_sender.dart'; +import 'package:sentry_flutter/src/isolate/isolate_worker.dart'; + +void main() { + group('CocoaEnvelopeSender host behavior', () { + test('warns and drops when not started', () { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = CocoaEnvelopeSender(options); + sender.captureEnvelope(Uint8List.fromList([1, 2, 3])); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains('captureEnvelope called before start; dropping')), + isTrue, + ); + }); + + test('close is a no-op when not started', () { + final options = SentryFlutterOptions(); + final sender = CocoaEnvelopeSender(options); + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('start is a no-op when already started', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + var spawnCount = 0; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + spawnCount++; + final inbox = ReceivePort(); + late final StreamSubscription sub; + sub = inbox.listen((msg) async { + if (msg == '_shutdown_') { + await sub.cancel(); + inbox.close(); + } + }); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + + await sender.start(); + await sender.start(); + expect(spawnCount, 1); + + sender.close(); + spawnCount = 0; + + await sender.start(); + expect(spawnCount, 1); + + // Close twice should be safe. + expect(() => sender.close(), returnsNormally); + expect(() => sender.close(), returnsNormally); + }); + + test('warns and drops after close', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + final logs = <(SentryLevel, String)>[]; + options.log = (level, message, {logger, exception, stackTrace}) { + logs.add((level, message)); + }; + + final sender = CocoaEnvelopeSender(options); + await sender.start(); + sender.close(); + + sender.captureEnvelope(Uint8List.fromList([9])); + + expect( + logs.any((e) => + e.$1 == SentryLevel.warning && + e.$2.contains('captureEnvelope called before start; dropping')), + isTrue, + ); + }); + + test('sends are delivered sequentially', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + sender.captureEnvelope(Uint8List.fromList([10])); + sender.captureEnvelope(Uint8List.fromList([11])); + + final inbox = inboxes.last; + final msgs = await inbox.take(2).toList(); + final msg1 = msgs[0]; + final msg2 = msgs[1]; + + expect(msg1, isA()); + expect(msg2, isA()); + + final data1 = (msg1 as TransferableTypedData).materialize().asUint8List(); + final data2 = (msg2 as TransferableTypedData).materialize().asUint8List(); + expect(data1, [10]); + expect(data2, [11]); + + sender.close(); + }); + + test('delivers to worker after start', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + final inboxes = []; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + final inbox = ReceivePort(); + inboxes.add(inbox); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + final payload = Uint8List.fromList([1, 2, 3]); + sender.captureEnvelope(payload); + + final msg = await inboxes.last.first; + expect(msg, isA()); + final data = (msg as TransferableTypedData).materialize().asUint8List(); + expect(data, [1, 2, 3]); + + sender.close(); + }); + + test('uses expected WorkerConfig', () async { + final options = SentryFlutterOptions(); + options.debug = true; + options.diagnosticLevel = SentryLevel.debug; + + WorkerConfig? seenConfig; + Future fakeSpawn(WorkerConfig config, WorkerEntry entry) async { + seenConfig = config; + final inbox = ReceivePort(); + addTearDown(() => inbox.close()); + final replies = ReceivePort(); + return Worker(inbox.sendPort, replies); + } + + final sender = CocoaEnvelopeSender(options, spawn: fakeSpawn); + await sender.start(); + + expect(seenConfig, isNotNull); + expect(seenConfig!.debugName, 'SentryCocoaEnvelopeSender'); + expect(seenConfig!.debug, options.debug); + expect(seenConfig!.diagnosticLevel, options.diagnosticLevel); + + sender.close(); + }); + }); +} diff --git a/packages/flutter/test/native/cocoa_envelope_sender_test_web.dart b/packages/flutter/test/native/cocoa_envelope_sender_test_web.dart new file mode 100644 index 0000000000..be0d7c08f0 --- /dev/null +++ b/packages/flutter/test/native/cocoa_envelope_sender_test_web.dart @@ -0,0 +1,10 @@ +// Stub for web - these tests only run on VM +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Cocoa envelope sender tests are not supported on web', () { + // This test file exists only to satisfy the compiler when running web tests. + // The actual tests in cocoa_envelope_sender_test_real.dart are only + // executed on VM platforms. + }); +} From 10d9419548d0b160a0facef6cb9455dcbcca0cf5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 15:02:02 +0200 Subject: [PATCH 26/78] Update --- packages/flutter/test/sentry_native_channel_test.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index f0ab1bceb6..1a5c7dd476 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -194,7 +194,6 @@ void main() { }); test('startProfiler', () { - sut.startProfiler(SentryId.newId()); final matcher = _nativeUnavailableMatcher( mockPlatform, androidUnsupported: true, @@ -263,7 +262,7 @@ void main() { mockPlatform, includeLookupSymbol: true, ); - sut.loadDebugImages(SentryStackTrace(frames: [])); + expect( () => sut.loadDebugImages(SentryStackTrace(frames: [])), matcher); From e6771bb1d28bed655ca75f584138387fd8740b9d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 16:45:56 +0200 Subject: [PATCH 27/78] Update --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa24a7d360..e8238842ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,8 +79,6 @@ - Replay: continue processing if encountering `InheritedWidget` ([#3200](https://github.com/getsentry/sentry-dart/pull/3200)) - Prevents false debug warnings when using [provider](https://pub.dev/packages/provider) for example which extensively uses `InheritedWidget` ->>> main - ## 9.7.0-beta.2 ### Features From e2ae6a3d481b6c16860b34bbcffd08fa0fcaf130 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 7 Oct 2025 16:48:28 +0200 Subject: [PATCH 28/78] Add close --- .../flutter/lib/src/native/cocoa/sentry_native_cocoa.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 6707817f2d..0beb6e26b1 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -57,6 +57,12 @@ class SentryNativeCocoa extends SentryNativeChannel { return super.init(hub); } + @override + Future close() async { + await _envelopeSender?.close(); + return super.close(); + } + @override FutureOr captureEnvelope( Uint8List envelopeData, bool containsUnhandledException) { From ae9b24c8e5b9ed1bed382cc4fdd1ebf4423ab953 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 8 Oct 2025 11:07:32 +0200 Subject: [PATCH 29/78] Review --- .../lib/src/isolate/isolate_worker.dart | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index bcf3bdbb67..25b3fcb362 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -45,19 +45,19 @@ class Worker { } /// Send a request to the worker and await a response. - Future request(Object? payload) { + Future request(Object? payload) async { if (_closed) throw StateError('Worker is closed'); final id = _idCounter++; final completer = Completer.sync(); _pending[id] = completer; _workerPort.send((id, payload)); - return completer.future; + return await completer.future; } void close() { if (_closed) return; - _workerPort.send(_shutdownCommand); _closed = true; + _workerPort.send(_shutdownCommand); if (_pending.isEmpty) { _responses.close(); } @@ -97,11 +97,16 @@ Future spawnWorker( )); }; - await Isolate.spawn<(SendPort, WorkerConfig)>( - entry, - (initPort.sendPort, config), - debugName: config.debugName, - ); + try { + await Isolate.spawn<(SendPort, WorkerConfig)>( + entry, + (initPort.sendPort, config), + debugName: config.debugName, + ); + } on Object { + initPort.close(); + rethrow; + } final (ReceivePort receivePort, SendPort sendPort) = await connection.future; return Worker(sendPort, receivePort); From 4b440d01f8aa721b53b520ca9d62f83120399c99 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 8 Oct 2025 12:59:27 +0200 Subject: [PATCH 30/78] Review --- packages/flutter/lib/src/isolate/isolate_logger.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/isolate/isolate_logger.dart b/packages/flutter/lib/src/isolate/isolate_logger.dart index bb8a8b7fe0..2b6d8c3667 100644 --- a/packages/flutter/lib/src/isolate/isolate_logger.dart +++ b/packages/flutter/lib/src/isolate/isolate_logger.dart @@ -75,7 +75,7 @@ class IsolateLogger { } static bool _isEnabled(SentryLevel level) { - return _debug && level.ordinal >= _level.ordinal || + return (_debug && level.ordinal >= _level.ordinal) || level == SentryLevel.fatal; } } From 91b029831279b84f40e3df1115b45475e5d56fe5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 8 Oct 2025 14:38:06 +0200 Subject: [PATCH 31/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 53 ++++++------- .../sentry_flutter/SentryFlutterPlugin.swift | 74 +++++++++---------- .../sentry_flutter_objc/SentryFlutterPlugin.h | 1 + .../flutter/lib/src/native/cocoa/binding.dart | 11 +++ .../src/native/cocoa/sentry_native_cocoa.dart | 12 +++ .../flutter/lib/src/native/java/binding.dart | 50 +++++++++++++ .../src/native/java/sentry_native_java.dart | 7 ++ .../lib/src/native/sentry_native_channel.dart | 7 +- 8 files changed, 149 insertions(+), 66 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 2d24d12f5d..e4a8b38fbc 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -43,7 +43,6 @@ class SentryFlutterPlugin : private lateinit var context: Context private lateinit var sentryFlutter: SentryFlutter - private var activity: WeakReference? = null private var pluginRegistrationTime: Long? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { @@ -75,7 +74,6 @@ class SentryFlutterPlugin : "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) "removeTag" -> removeTag(call.argument("key"), result) - "displayRefreshRate" -> displayRefreshRate(result) "nativeCrash" -> crash() "setReplayConfig" -> setReplayConfig(call, result) "captureReplay" -> captureReplay(result) @@ -217,29 +215,6 @@ class SentryFlutterPlugin : } } - private fun displayRefreshRate(result: Result) { - var refreshRate: Int? = null - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val display = activity?.get()?.display - if (display != null) { - refreshRate = display.refreshRate.toInt() - } - } else { - val display = - activity - ?.get() - ?.window - ?.windowManager - ?.defaultDisplay - if (display != null) { - refreshRate = display.refreshRate.toInt() - } - } - - result.success(refreshRate) - } - private fun TimeSpan.addToMap(map: MutableMap) { if (startTimestamp == null) return @@ -381,12 +356,40 @@ class SentryFlutterPlugin : @SuppressLint("StaticFieldLeak") private var applicationContext: Context? = null + @SuppressLint("StaticFieldLeak") + private var activity: WeakReference? = null + private const val NATIVE_CRASH_WAIT_TIME = 500L @Suppress("unused") // Used by native/jni bindings @JvmStatic fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay + @Suppress("unused") // Used by native/jni bindings + @JvmStatic + fun getDisplayRefreshRate(): Int? { + var refreshRate: Int? = null + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val display = activity?.get()?.display + if (display != null) { + refreshRate = display.refreshRate.toInt() + } + } else { + val display = + activity + ?.get() + ?.window + ?.windowManager + ?.defaultDisplay + if (display != null) { + refreshRate = display.refreshRate.toInt() + } + } + + return refreshRate + } + @JvmStatic fun getApplicationContext(): Context? = applicationContext diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 044e3d4757..31c160cc5e 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -133,9 +133,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { collectProfile(call, result) #endif - case "displayRefreshRate": - displayRefreshRate(result) - case "pauseAppHangTracking": pauseAppHangTracking(result) @@ -514,42 +511,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { result(nil) } - #if os(iOS) - // Taken from the Flutter engine: - // https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm#L150 - private func displayRefreshRate(_ result: @escaping FlutterResult) { - let displayLink = CADisplayLink(target: self, selector: #selector(onDisplayLink(_:))) - displayLink.add(to: .main, forMode: .common) - displayLink.isPaused = true - - let preferredFPS = displayLink.preferredFramesPerSecond - displayLink.invalidate() - - if preferredFPS != 0 { - result(preferredFPS) - return - } - - if #available(iOS 13.0, *) { - guard let windowScene = UIApplication.shared.windows.first?.windowScene else { - result(nil) - return - } - result(windowScene.screen.maximumFramesPerSecond) - } else { - result(UIScreen.main.maximumFramesPerSecond) - } - } - - @objc private func onDisplayLink(_ displayLink: CADisplayLink) { - // No-op - } - #elseif os(macOS) - private func displayRefreshRate(_ result: @escaping FlutterResult) { - result(nil) - } - #endif - private func pauseAppHangTracking(_ result: @escaping FlutterResult) { SentrySDK.pauseAppHangTracking() result("") @@ -569,6 +530,41 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { // Group of methods exposed to the Objective-C runtime via `@objc`. // // Purpose: Called from the Flutter plugin's native bridge (FFI) - bindings are created from SentryFlutterPlugin.h + + #if os(iOS) + // Taken from the Flutter engine: + // https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm#L150 + @objc public class func getDisplayRefreshRate() -> NSNumber? { + let displayLink = CADisplayLink(target: self, selector: #selector(onDisplayLinkStatic(_:))) + displayLink.add(to: .main, forMode: .common) + displayLink.isPaused = true + + let preferredFPS = displayLink.preferredFramesPerSecond + displayLink.invalidate() + + if preferredFPS != 0 { + return NSNumber(value: preferredFPS) + } + + if #available(iOS 13.0, *) { + guard let windowScene = UIApplication.shared.windows.first?.windowScene else { + return nil + } + return NSNumber(value: windowScene.screen.maximumFramesPerSecond) + } else { + return NSNumber(value: UIScreen.main.maximumFramesPerSecond) + } + } + + @objc private class func onDisplayLinkStatic(_ displayLink: CADisplayLink) { + // No-op + } + #elseif os(macOS) + @objc public class func getDisplayRefreshRate() -> NSNumber? { + return nil + } + #endif + @objc(loadDebugImagesAsBytes:) public class func loadDebugImagesAsBytes(instructionAddresses: Set) -> NSData? { var debugImages: [DebugMeta] = [] diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index a18f29ed10..ace6992371 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -4,6 +4,7 @@ #import #else @interface SentryFlutterPlugin : NSObject ++ (nullable NSNumber *)getDisplayRefreshRate; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; @end diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index dfa2b80f88..b5d952c5a5 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1121,6 +1121,8 @@ class SentryId$1 extends objc.NSObject { } late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); +late final _sel_getDisplayRefreshRate = + objc.registerName("getDisplayRefreshRate"); late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); @@ -1146,6 +1148,15 @@ class SentryFlutterPlugin extends objc.NSObject { obj.ref.pointer, _sel_isKindOfClass_, _class_SentryFlutterPlugin); } + /// getDisplayRefreshRate + static objc.NSNumber? getDisplayRefreshRate() { + final _ret = _objc_msgSend_151sglz( + _class_SentryFlutterPlugin, _sel_getDisplayRefreshRate); + return _ret.address == 0 + ? null + : objc.NSNumber.castFromPointer(_ret, retain: true, release: true); + } + /// loadContextsAsBytes static objc.NSData? loadContextsAsBytes() { final _ret = _objc_msgSend_151sglz( diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 0beb6e26b1..89a92af7e4 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ffi'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; @@ -153,4 +154,15 @@ class SentryNativeCocoa extends SentryNativeChannel { return startTime; }, ); + + @override + int? displayRefreshRate() => tryCatchSync( + 'displayRefreshRate', + () { + final refreshRate = cocoa.SentryFlutterPlugin.getDisplayRefreshRate(); + if (refreshRate == null) return null; + + return refreshRate.intValue; + }, + ); } diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index 6eb935200c..8c263dc772 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1305,6 +1305,31 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } + static final _id_getDisplayRefreshRate = _class.instanceMethodId( + r'getDisplayRefreshRate', + r'()Ljava/lang/Integer;', + ); + + static final _getDisplayRefreshRate = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final java.lang.Integer getDisplayRefreshRate()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JInteger? getDisplayRefreshRate() { + return _getDisplayRefreshRate( + reference.pointer, _id_getDisplayRefreshRate as jni$_.JMethodIDPtr) + .object(const jni$_.JIntegerNullableType()); + } + static final _id_getApplicationContext = _class.instanceMethodId( r'getApplicationContext', r'()Landroid/content/Context;', @@ -1765,6 +1790,31 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } + static final _id_getDisplayRefreshRate = _class.staticMethodId( + r'getDisplayRefreshRate', + r'()Ljava/lang/Integer;', + ); + + static final _getDisplayRefreshRate = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public final java.lang.Integer getDisplayRefreshRate()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JInteger? getDisplayRefreshRate() { + return _getDisplayRefreshRate(_class.reference.pointer, + _id_getDisplayRefreshRate as jni$_.JMethodIDPtr) + .object(const jni$_.JIntegerNullableType()); + } + static final _id_getApplicationContext = _class.staticMethodId( r'getApplicationContext', r'()Landroid/content/Context;', diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 58fb8657ce..05a2d66148 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -171,6 +171,13 @@ class SentryNativeJava extends SentryNativeChannel { return null; } + @override + int? displayRefreshRate() { + return native.SentryFlutterPlugin.Companion + .getDisplayRefreshRate() + ?.intValue(); + } + @override Future close() async { await _replayRecorder?.stop(); diff --git a/packages/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart index 20d72dd317..c4266fa0cb 100644 --- a/packages/flutter/lib/src/native/sentry_native_channel.dart +++ b/packages/flutter/lib/src/native/sentry_native_channel.dart @@ -223,8 +223,11 @@ class SentryNativeChannel } @override - Future displayRefreshRate() => - channel.invokeMethod('displayRefreshRate'); + FutureOr displayRefreshRate() { + assert(false, + 'displayRefreshRate should not be used through method channels.'); + return null; + } @override Future pauseAppHangTracking() => From 2cd6dd8b7bdf02a30bde927d602b2c610ca55a24 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 8 Oct 2025 15:05:14 +0200 Subject: [PATCH 32/78] Update --- packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 89a92af7e4..70ee6bf3fa 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:ffi'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; From 2f1840d0a62e37c08b73b1e967c5f64095fb2662 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 8 Oct 2025 15:23:10 +0200 Subject: [PATCH 33/78] Update --- .../kotlin/io/sentry/flutter/SentryFlutter.kt | 1 + .../io/sentry/flutter/SentryFlutterPlugin.kt | 165 +++++++++--------- .../flutter/lib/src/native/java/binding.dart | 104 +++++++++++ .../src/native/java/sentry_native_java.dart | 29 +++ .../lib/src/native/sentry_native_channel.dart | 8 +- 5 files changed, 222 insertions(+), 85 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt index 19eec63247..a1a7ff5c3a 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt @@ -108,6 +108,7 @@ class SentryFlutter { data.getIfNotNull("enableAutoPerformanceTracing") { enableAutoPerformanceTracing -> if (enableAutoPerformanceTracing) { autoPerformanceTracingEnabled = true + SentryFlutterPlugin.setAutoPerformanceTracingEnabled(true) } } diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index e4a8b38fbc..47f1a3d171 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -43,10 +43,8 @@ class SentryFlutterPlugin : private lateinit var context: Context private lateinit var sentryFlutter: SentryFlutter - private var pluginRegistrationTime: Long? = null - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - pluginRegistrationTime = System.currentTimeMillis() + Companion.pluginRegistrationTime = System.currentTimeMillis() context = flutterPluginBinding.applicationContext applicationContext = context @@ -64,7 +62,6 @@ class SentryFlutterPlugin : when (call.method) { "initNativeSdk" -> initNativeSdk(call, result) "closeNativeSdk" -> closeNativeSdk(result) - "fetchNativeAppStart" -> fetchNativeAppStart(result) "setContexts" -> setContexts(call.argument("key"), call.argument("value"), result) "removeContexts" -> removeContexts(call.argument("key"), result) "setUser" -> setUser(call.argument("user"), result) @@ -149,83 +146,6 @@ class SentryFlutterPlugin : } } - private fun fetchNativeAppStart(result: Result) { - if (!sentryFlutter.autoPerformanceTracingEnabled) { - result.success(null) - return - } - - val appStartMetrics = AppStartMetrics.getInstance() - - if (!appStartMetrics.isAppLaunchedInForeground || - appStartMetrics.appStartTimeSpan.durationMs > APP_START_MAX_DURATION_MS - ) { - Log.w( - "Sentry", - "Invalid app start data: app not launched in foreground or app start took too long (>60s)", - ) - result.success(null) - return - } - - val appStartTimeSpan = appStartMetrics.appStartTimeSpan - val appStartTime = appStartTimeSpan.startTimestamp - val isColdStart = appStartMetrics.appStartType == AppStartMetrics.AppStartType.COLD - - if (appStartTime == null) { - Log.w("Sentry", "App start won't be sent due to missing appStartTime") - result.success(null) - } else { - val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) - val item = - - mutableMapOf( - "pluginRegistrationTime" to pluginRegistrationTime, - "appStartTime" to appStartTimeMillis, - "isColdStart" to isColdStart, - ) - - val androidNativeSpans = mutableMapOf() - - val processInitSpan = - TimeSpan().apply { - description = "Process Initialization" - setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) - setStartedAt(appStartTimeSpan.startUptimeMs) - setStoppedAt(appStartMetrics.classLoadedUptimeMs) - } - processInitSpan.addToMap(androidNativeSpans) - - val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan - applicationOnCreateSpan.addToMap(androidNativeSpans) - - val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans - contentProviderSpans.forEach { span -> - span.addToMap(androidNativeSpans) - } - - appStartMetrics.activityLifecycleTimeSpans.forEach { span -> - span.onCreate.addToMap(androidNativeSpans) - span.onStart.addToMap(androidNativeSpans) - } - - item["nativeSpanTimes"] = androidNativeSpans - - result.success(item) - } - } - - private fun TimeSpan.addToMap(map: MutableMap) { - if (startTimestamp == null) return - - description?.let { description -> - map[description] = - mapOf( - "startTimestampMsSinceEpoch" to startTimestampMs, - "stopTimestampMsSinceEpoch" to projectedStopTimestampMs, - ) - } - } private fun setContexts( key: String?, value: Any?, @@ -359,6 +279,9 @@ class SentryFlutterPlugin : @SuppressLint("StaticFieldLeak") private var activity: WeakReference? = null + private var pluginRegistrationTime: Long? = null + private var autoPerformanceTracingEnabled: Boolean = false + private const val NATIVE_CRASH_WAIT_TIME = 500L @Suppress("unused") // Used by native/jni bindings @@ -390,9 +313,89 @@ class SentryFlutterPlugin : return refreshRate } + @Suppress("unused") // Used by native/jni bindings + @JvmStatic + fun fetchNativeAppStartAsBytes(): ByteArray? { + if (!autoPerformanceTracingEnabled) { + return null + } + + val appStartMetrics = AppStartMetrics.getInstance() + + if (!appStartMetrics.isAppLaunchedInForeground || + appStartMetrics.appStartTimeSpan.durationMs > APP_START_MAX_DURATION_MS + ) { + Log.w( + "Sentry", + "Invalid app start data: app not launched in foreground or app start took too long (>60s)", + ) + return null + } + + val appStartTimeSpan = appStartMetrics.appStartTimeSpan + val appStartTime = appStartTimeSpan.startTimestamp + val isColdStart = appStartMetrics.appStartType == AppStartMetrics.AppStartType.COLD + + if (appStartTime == null) { + Log.w("Sentry", "App start won't be sent due to missing appStartTime") + return null + } + + val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) + val item = mutableMapOf( + "pluginRegistrationTime" to pluginRegistrationTime, + "appStartTime" to appStartTimeMillis, + "isColdStart" to isColdStart, + ) + + val androidNativeSpans = mutableMapOf() + + val processInitSpan = TimeSpan().apply { + description = "Process Initialization" + setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) + setStartedAt(appStartTimeSpan.startUptimeMs) + setStoppedAt(appStartMetrics.classLoadedUptimeMs) + } + addTimeSpanToMap(processInitSpan, androidNativeSpans) + + val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan + addTimeSpanToMap(applicationOnCreateSpan, androidNativeSpans) + + val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans + contentProviderSpans.forEach { span -> + addTimeSpanToMap(span, androidNativeSpans) + } + + appStartMetrics.activityLifecycleTimeSpans.forEach { span -> + addTimeSpanToMap(span.onCreate, androidNativeSpans) + addTimeSpanToMap(span.onStart, androidNativeSpans) + } + + item["nativeSpanTimes"] = androidNativeSpans + + val json = JSONObject(item).toString() + return json.toByteArray(Charsets.UTF_8) + } + + private fun addTimeSpanToMap(span: TimeSpan, map: MutableMap) { + if (span.startTimestamp == null) return + + span.description?.let { description -> + map[description] = mapOf( + "startTimestampMsSinceEpoch" to span.startTimestampMs, + "stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs, + ) + } + } + @JvmStatic fun getApplicationContext(): Context? = applicationContext + @JvmStatic + fun setAutoPerformanceTracingEnabled(enabled: Boolean) { + autoPerformanceTracingEnabled = enabled + } + @Suppress("unused") // Used by native/jni bindings @JvmStatic fun loadContextsAsBytes(): ByteArray? { diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index 8c263dc772..aebd47c81c 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1330,6 +1330,32 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const jni$_.JIntegerNullableType()); } + static final _id_fetchNativeAppStartAsBytes = _class.instanceMethodId( + r'fetchNativeAppStartAsBytes', + r'()[B', + ); + + static final _fetchNativeAppStartAsBytes = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final byte[] fetchNativeAppStartAsBytes()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JByteArray? fetchNativeAppStartAsBytes() { + return _fetchNativeAppStartAsBytes(reference.pointer, + _id_fetchNativeAppStartAsBytes as jni$_.JMethodIDPtr) + .object(const jni$_.JByteArrayNullableType()); + } + static final _id_getApplicationContext = _class.instanceMethodId( r'getApplicationContext', r'()Landroid/content/Context;', @@ -1355,6 +1381,32 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const jni$_.JObjectNullableType()); } + static final _id_setAutoPerformanceTracingEnabled = _class.instanceMethodId( + r'setAutoPerformanceTracingEnabled', + r'(Z)V', + ); + + static final _setAutoPerformanceTracingEnabled = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.VarArgs<(jni$_.Int32,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `public final void setAutoPerformanceTracingEnabled(boolean z)` + void setAutoPerformanceTracingEnabled( + bool z, + ) { + _setAutoPerformanceTracingEnabled( + reference.pointer, + _id_setAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr, + z ? 1 : 0) + .check(); + } + static final _id_loadContextsAsBytes = _class.instanceMethodId( r'loadContextsAsBytes', r'()[B', @@ -1815,6 +1867,32 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const jni$_.JIntegerNullableType()); } + static final _id_fetchNativeAppStartAsBytes = _class.staticMethodId( + r'fetchNativeAppStartAsBytes', + r'()[B', + ); + + static final _fetchNativeAppStartAsBytes = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public final byte[] fetchNativeAppStartAsBytes()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JByteArray? fetchNativeAppStartAsBytes() { + return _fetchNativeAppStartAsBytes(_class.reference.pointer, + _id_fetchNativeAppStartAsBytes as jni$_.JMethodIDPtr) + .object(const jni$_.JByteArrayNullableType()); + } + static final _id_getApplicationContext = _class.staticMethodId( r'getApplicationContext', r'()Landroid/content/Context;', @@ -1840,6 +1918,32 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const jni$_.JObjectNullableType()); } + static final _id_setAutoPerformanceTracingEnabled = _class.staticMethodId( + r'setAutoPerformanceTracingEnabled', + r'(Z)V', + ); + + static final _setAutoPerformanceTracingEnabled = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.VarArgs<(jni$_.Int32,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `static public final void setAutoPerformanceTracingEnabled(boolean z)` + static void setAutoPerformanceTracingEnabled( + bool z, + ) { + _setAutoPerformanceTracingEnabled( + _class.reference.pointer, + _id_setAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr, + z ? 1 : 0) + .check(); + } + static final _id_loadContextsAsBytes = _class.staticMethodId( r'loadContextsAsBytes', r'()[B', diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 05a2d66148..8a5655a40e 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; +import '../native_app_start.dart'; import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; import 'android_envelope_sender.dart'; @@ -178,6 +179,34 @@ class SentryNativeJava extends SentryNativeChannel { ?.intValue(); } + @override + NativeAppStart? fetchNativeAppStart() { + JByteArray? appStartUtf8JsonBytes; + + try { + appStartUtf8JsonBytes = + native.SentryFlutterPlugin.Companion.fetchNativeAppStartAsBytes(); + if (appStartUtf8JsonBytes == null) return null; + + final byteRange = + appStartUtf8JsonBytes.getRange(0, appStartUtf8JsonBytes.length); + final bytes = Uint8List.view( + byteRange.buffer, byteRange.offsetInBytes, byteRange.length); + final appStartMap = decodeUtf8JsonMap(bytes); + return NativeAppStart.fromJson(appStartMap); + } catch (exception, stackTrace) { + options.log(SentryLevel.error, 'JNI: Failed to fetch native app start', + exception: exception, stackTrace: stackTrace); + if (options.automatedTestMode) { + rethrow; + } + } finally { + appStartUtf8JsonBytes?.release(); + } + + return null; + } + @override Future close() async { await _replayRecorder?.stop(); diff --git a/packages/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart index c4266fa0cb..384bdcaca7 100644 --- a/packages/flutter/lib/src/native/sentry_native_channel.dart +++ b/packages/flutter/lib/src/native/sentry_native_channel.dart @@ -91,10 +91,10 @@ class SentryNativeChannel Future close() async => channel.invokeMethod('closeNativeSdk'); @override - Future fetchNativeAppStart() async { - final json = - await channel.invokeMapMethod('fetchNativeAppStart'); - return (json != null) ? NativeAppStart.fromJson(json) : null; + FutureOr fetchNativeAppStart() async { + assert(false, + 'fetchNativeAppStart should not be used through method channels.'); + return null; } @override From 775021383ca165c14d2b46129ac7e32fe3ae9670 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:19:54 +0200 Subject: [PATCH 34/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 38 ++--- .../sentry_flutter/SentryFlutterPlugin.swift | 136 ++++++++---------- .../sentry_flutter_objc/SentryFlutterPlugin.h | 1 + .../flutter/lib/src/native/cocoa/binding.dart | 11 ++ .../src/native/cocoa/sentry_native_cocoa.dart | 14 ++ .../test/sentry_native_channel_test.dart | 45 +++--- 6 files changed, 123 insertions(+), 122 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 47f1a3d171..97116c6a02 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -342,20 +342,22 @@ class SentryFlutterPlugin : } val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) - val item = mutableMapOf( - "pluginRegistrationTime" to pluginRegistrationTime, - "appStartTime" to appStartTimeMillis, - "isColdStart" to isColdStart, - ) + val item = + mutableMapOf( + "pluginRegistrationTime" to pluginRegistrationTime, + "appStartTime" to appStartTimeMillis, + "isColdStart" to isColdStart, + ) val androidNativeSpans = mutableMapOf() - val processInitSpan = TimeSpan().apply { - description = "Process Initialization" - setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) - setStartedAt(appStartTimeSpan.startUptimeMs) - setStoppedAt(appStartMetrics.classLoadedUptimeMs) - } + val processInitSpan = + TimeSpan().apply { + description = "Process Initialization" + setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) + setStartedAt(appStartTimeSpan.startUptimeMs) + setStoppedAt(appStartMetrics.classLoadedUptimeMs) + } addTimeSpanToMap(processInitSpan, androidNativeSpans) val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan @@ -377,14 +379,18 @@ class SentryFlutterPlugin : return json.toByteArray(Charsets.UTF_8) } - private fun addTimeSpanToMap(span: TimeSpan, map: MutableMap) { + private fun addTimeSpanToMap( + span: TimeSpan, + map: MutableMap, + ) { if (span.startTimestamp == null) return span.description?.let { description -> - map[description] = mapOf( - "startTimestampMsSinceEpoch" to span.startTimestampMs, - "stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs, - ) + map[description] = + mapOf( + "startTimestampMsSinceEpoch" to span.startTimestampMs, + "stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs, + ) } } diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 31c160cc5e..6629269211 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -76,9 +76,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { case "closeNativeSdk": closeNativeSdk(call, result: result) - case "fetchNativeAppStart": - fetchNativeAppStart(result: result) - case "setContexts": let arguments = call.arguments as? [String: Any?] let key = arguments?["key"] as? String @@ -291,83 +288,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { return !name.isEmpty } - struct TimeSpan { - var startTimestampMsSinceEpoch: NSNumber - var stopTimestampMsSinceEpoch: NSNumber - var description: String - - func addToMap(_ map: inout [String: Any]) { - map[description] = [ - "startTimestampMsSinceEpoch": startTimestampMsSinceEpoch, - "stopTimestampMsSinceEpoch": stopTimestampMsSinceEpoch - ] - } - } - - private func fetchNativeAppStart(result: @escaping FlutterResult) { - #if os(iOS) || os(tvOS) - guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else { - print("warning: appStartMeasurement is null") - result(nil) - return - } - - var nativeSpanTimes: [String: Any] = [:] - - let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds() - let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds() - let moduleInitializationTimeMs = - appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds() - let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds() - - if !appStartMeasurement.isPreWarmed { - let preRuntimeInitDescription = "Pre Runtime Init" - let preRuntimeInitSpan = TimeSpan( - startTimestampMsSinceEpoch: NSNumber(value: appStartTimeMs), - stopTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs), - description: preRuntimeInitDescription - ) - preRuntimeInitSpan.addToMap(&nativeSpanTimes) - - let moduleInitializationDescription = "Runtime init to Pre Main initializers" - let moduleInitializationSpan = TimeSpan( - startTimestampMsSinceEpoch: NSNumber(value: runtimeInitTimeMs), - stopTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs), - description: moduleInitializationDescription - ) - moduleInitializationSpan.addToMap(&nativeSpanTimes) - } - - let uiKitInitDescription = "UIKit init" - let uiKitInitSpan = TimeSpan( - startTimestampMsSinceEpoch: NSNumber(value: moduleInitializationTimeMs), - stopTimestampMsSinceEpoch: NSNumber(value: sdkStartTimeMs), - description: uiKitInitDescription - ) - uiKitInitSpan.addToMap(&nativeSpanTimes) - - // Info: We don't have access to didFinishLaunchingTimestamp, - // On HybridSDKs, the Cocoa SDK misses the didFinishLaunchNotification and the - // didBecomeVisibleNotification. Therefore, we can't set the - // didFinishLaunchingTimestamp - - let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000 - let isColdStart = appStartMeasurement.type == .cold - - let item: [String: Any] = [ - "pluginRegistrationTime": SentryFlutterPlugin.pluginRegistrationTime, - "appStartTime": appStartTime, - "isColdStart": isColdStart, - "nativeSpanTimes": nativeSpanTimes - ] - - result(item) - #else - print("note: appStartMeasurement not available on this platform") - result(nil) - #endif - } - private func setContexts(key: String?, value: Any?, result: @escaping FlutterResult) { guard let key = key else { result("") @@ -565,6 +485,62 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { } #endif + @objc public class func fetchNativeAppStartAsBytes() -> NSData? { + #if os(iOS) || os(tvOS) + guard let appStartMeasurement = PrivateSentrySDKOnly.appStartMeasurement else { + return nil + } + + var nativeSpanTimes: [String: Any] = [:] + + let appStartTimeMs = appStartMeasurement.appStartTimestamp.timeIntervalSince1970.toMilliseconds() + let runtimeInitTimeMs = appStartMeasurement.runtimeInitTimestamp.timeIntervalSince1970.toMilliseconds() + let moduleInitializationTimeMs = + appStartMeasurement.moduleInitializationTimestamp.timeIntervalSince1970.toMilliseconds() + let sdkStartTimeMs = appStartMeasurement.sdkStartTimestamp.timeIntervalSince1970.toMilliseconds() + + if !appStartMeasurement.isPreWarmed { + let preRuntimeInitDescription = "Pre Runtime Init" + let preRuntimeInitSpan: [String: Any] = [ + "startTimestampMsSinceEpoch": NSNumber(value: appStartTimeMs), + "stopTimestampMsSinceEpoch": NSNumber(value: runtimeInitTimeMs) + ] + nativeSpanTimes[preRuntimeInitDescription] = preRuntimeInitSpan + + let moduleInitializationDescription = "Runtime init to Pre Main initializers" + let moduleInitializationSpan: [String: Any] = [ + "startTimestampMsSinceEpoch": NSNumber(value: runtimeInitTimeMs), + "stopTimestampMsSinceEpoch": NSNumber(value: moduleInitializationTimeMs) + ] + nativeSpanTimes[moduleInitializationDescription] = moduleInitializationSpan + } + + let uiKitInitDescription = "UIKit init" + let uiKitInitSpan: [String: Any] = [ + "startTimestampMsSinceEpoch": NSNumber(value: moduleInitializationTimeMs), + "stopTimestampMsSinceEpoch": NSNumber(value: sdkStartTimeMs) + ] + nativeSpanTimes[uiKitInitDescription] = uiKitInitSpan + + let appStartTime = appStartMeasurement.appStartTimestamp.timeIntervalSince1970 * 1000 + let isColdStart = appStartMeasurement.type == .cold + + let item: [String: Any] = [ + "pluginRegistrationTime": pluginRegistrationTime, + "appStartTime": appStartTime, + "isColdStart": isColdStart, + "nativeSpanTimes": nativeSpanTimes + ] + + if let data = try? JSONSerialization.data(withJSONObject: item, options: []) { + return data as NSData + } + return nil + #else + return nil + #endif + } + @objc(loadDebugImagesAsBytes:) public class func loadDebugImagesAsBytes(instructionAddresses: Set) -> NSData? { var debugImages: [DebugMeta] = [] diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index ace6992371..6f2e25eb54 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -5,6 +5,7 @@ #else @interface SentryFlutterPlugin : NSObject + (nullable NSNumber *)getDisplayRefreshRate; ++ (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; @end diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index b5d952c5a5..903062aa10 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1123,6 +1123,8 @@ class SentryId$1 extends objc.NSObject { late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); late final _sel_getDisplayRefreshRate = objc.registerName("getDisplayRefreshRate"); +late final _sel_fetchNativeAppStartAsBytes = + objc.registerName("fetchNativeAppStartAsBytes"); late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); @@ -1157,6 +1159,15 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSNumber.castFromPointer(_ret, retain: true, release: true); } + /// fetchNativeAppStartAsBytes + static objc.NSData? fetchNativeAppStartAsBytes() { + final _ret = _objc_msgSend_151sglz( + _class_SentryFlutterPlugin, _sel_fetchNativeAppStartAsBytes); + return _ret.address == 0 + ? null + : objc.NSData.castFromPointer(_ret, retain: true, release: true); + } + /// loadContextsAsBytes static objc.NSData? loadContextsAsBytes() { final _ret = _objc_msgSend_151sglz( diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 70ee6bf3fa..0a8dc2ee4a 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -5,6 +5,7 @@ import 'package:objective_c/objective_c.dart'; import '../../../sentry_flutter.dart'; import '../../replay/replay_config.dart'; +import '../native_app_start.dart'; import '../sentry_native_channel.dart'; import '../utils/utf8_json.dart'; import 'binding.dart' as cocoa; @@ -164,4 +165,17 @@ class SentryNativeCocoa extends SentryNativeChannel { return refreshRate.intValue; }, ); + + @override + NativeAppStart? fetchNativeAppStart() => tryCatchSync( + 'fetchNativeAppStart', + () { + final appStartUtf8JsonBytes = + cocoa.SentryFlutterPlugin.fetchNativeAppStartAsBytes(); + if (appStartUtf8JsonBytes == null) return null; + + final json = decodeUtf8JsonMap(appStartUtf8JsonBytes.toList()); + return NativeAppStart.fromJson(json); + }, + ); } diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index 1a5c7dd476..f35d68ea8b 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -38,34 +38,15 @@ void main() { // TODO move other methods here, e.g. init_native_sdk_test.dart test('fetchNativeAppStart', () async { - when(channel.invokeMethod('fetchNativeAppStart')) - .thenAnswer((_) async => { - 'pluginRegistrationTime': 1, - 'appStartTime': 0.1, - 'isColdStart': true, - // ignore: inference_failure_on_collection_literal - 'nativeSpanTimes': {}, - }); - - final actual = await sut.fetchNativeAppStart(); - - expect(actual?.appStartTime, 0.1); - expect(actual?.isColdStart, true); - }); - - test('invalid fetchNativeAppStart returns null', () async { - when(channel.invokeMethod('fetchNativeAppStart')) - .thenAnswer((_) async => { - 'pluginRegistrationTime': 'invalid', - 'appStartTime': 'invalid', - 'isColdStart': 'invalid', - // ignore: inference_failure_on_collection_literal - 'nativeSpanTimes': 'invalid', - }); + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); - final actual = await sut.fetchNativeAppStart(); + expect(() => sut.fetchNativeAppStart(), matcher); - expect(actual, isNull); + verifyZeroInteractions(channel); }); test('setUser', () async { @@ -269,6 +250,18 @@ void main() { verifyZeroInteractions(channel); }); + test('displayRefreshRate', () async { + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); + + expect(() => sut.displayRefreshRate(), matcher); + + verifyZeroInteractions(channel); + }); + test('pauseAppHangTracking', () async { when(channel.invokeMethod('pauseAppHangTracking')) .thenAnswer((_) => Future.value()); From cd554e45f712d0c58e488503670a5ce1aec1fa7d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:22:19 +0200 Subject: [PATCH 35/78] Update --- .../kotlin/io/sentry/flutter/SentryFlutter.kt | 1 - .../io/sentry/flutter/SentryFlutterPlugin.kt | 5 -- .../flutter/lib/src/native/java/binding.dart | 52 ------------------- 3 files changed, 58 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt index a1a7ff5c3a..19eec63247 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt @@ -108,7 +108,6 @@ class SentryFlutter { data.getIfNotNull("enableAutoPerformanceTracing") { enableAutoPerformanceTracing -> if (enableAutoPerformanceTracing) { autoPerformanceTracingEnabled = true - SentryFlutterPlugin.setAutoPerformanceTracingEnabled(true) } } diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 97116c6a02..af431efb94 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -397,11 +397,6 @@ class SentryFlutterPlugin : @JvmStatic fun getApplicationContext(): Context? = applicationContext - @JvmStatic - fun setAutoPerformanceTracingEnabled(enabled: Boolean) { - autoPerformanceTracingEnabled = enabled - } - @Suppress("unused") // Used by native/jni bindings @JvmStatic fun loadContextsAsBytes(): ByteArray? { diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index aebd47c81c..feae89319b 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1381,32 +1381,6 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const jni$_.JObjectNullableType()); } - static final _id_setAutoPerformanceTracingEnabled = _class.instanceMethodId( - r'setAutoPerformanceTracingEnabled', - r'(Z)V', - ); - - static final _setAutoPerformanceTracingEnabled = - jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function(jni$_.Pointer, - jni$_.JMethodIDPtr, jni$_.VarArgs<(jni$_.Int32,)>)>>( - 'globalEnv_CallVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); - - /// from: `public final void setAutoPerformanceTracingEnabled(boolean z)` - void setAutoPerformanceTracingEnabled( - bool z, - ) { - _setAutoPerformanceTracingEnabled( - reference.pointer, - _id_setAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr, - z ? 1 : 0) - .check(); - } - static final _id_loadContextsAsBytes = _class.instanceMethodId( r'loadContextsAsBytes', r'()[B', @@ -1918,32 +1892,6 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const jni$_.JObjectNullableType()); } - static final _id_setAutoPerformanceTracingEnabled = _class.staticMethodId( - r'setAutoPerformanceTracingEnabled', - r'(Z)V', - ); - - static final _setAutoPerformanceTracingEnabled = - jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function(jni$_.Pointer, - jni$_.JMethodIDPtr, jni$_.VarArgs<(jni$_.Int32,)>)>>( - 'globalEnv_CallStaticVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); - - /// from: `static public final void setAutoPerformanceTracingEnabled(boolean z)` - static void setAutoPerformanceTracingEnabled( - bool z, - ) { - _setAutoPerformanceTracingEnabled( - _class.reference.pointer, - _id_setAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr, - z ? 1 : 0) - .check(); - } - static final _id_loadContextsAsBytes = _class.staticMethodId( r'loadContextsAsBytes', r'()[B', From 9708168987bceddf8e56142cec21bc556d89bcc2 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:28:51 +0200 Subject: [PATCH 36/78] Update --- .../src/native/java/sentry_native_java.dart | 26 +++++++------------ .../lib/src/native/sentry_native_invoker.dart | 5 +++- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 8a5655a40e..b79d8b9db4 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -173,38 +173,30 @@ class SentryNativeJava extends SentryNativeChannel { } @override - int? displayRefreshRate() { - return native.SentryFlutterPlugin.Companion - .getDisplayRefreshRate() - ?.intValue(); - } + int? displayRefreshRate() => tryCatchSync('displayRefreshRate', () { + return native.SentryFlutterPlugin.Companion + .getDisplayRefreshRate() + ?.intValue(); + }); @override NativeAppStart? fetchNativeAppStart() { JByteArray? appStartUtf8JsonBytes; - try { + return tryCatchSync('fetchNativeAppStart', () { appStartUtf8JsonBytes = native.SentryFlutterPlugin.Companion.fetchNativeAppStartAsBytes(); if (appStartUtf8JsonBytes == null) return null; final byteRange = - appStartUtf8JsonBytes.getRange(0, appStartUtf8JsonBytes.length); + appStartUtf8JsonBytes!.getRange(0, appStartUtf8JsonBytes!.length); final bytes = Uint8List.view( byteRange.buffer, byteRange.offsetInBytes, byteRange.length); final appStartMap = decodeUtf8JsonMap(bytes); return NativeAppStart.fromJson(appStartMap); - } catch (exception, stackTrace) { - options.log(SentryLevel.error, 'JNI: Failed to fetch native app start', - exception: exception, stackTrace: stackTrace); - if (options.automatedTestMode) { - rethrow; - } - } finally { + }, finallyFn: () { appStartUtf8JsonBytes?.release(); - } - - return null; + }); } @override diff --git a/packages/flutter/lib/src/native/sentry_native_invoker.dart b/packages/flutter/lib/src/native/sentry_native_invoker.dart index 6b20aff03d..31b1c187b6 100644 --- a/packages/flutter/lib/src/native/sentry_native_invoker.dart +++ b/packages/flutter/lib/src/native/sentry_native_invoker.dart @@ -22,7 +22,8 @@ mixin SentryNativeSafeInvoker { } } - T? tryCatchSync(String nativeMethodName, T? Function() fn) { + T? tryCatchSync(String nativeMethodName, T? Function() fn, + {void Function()? finallyFn}) { try { return fn(); } catch (error, stackTrace) { @@ -31,6 +32,8 @@ mixin SentryNativeSafeInvoker { rethrow; } return null; + } finally { + finallyFn?.call(); } } From 99b5584213c43fd2e53d6e52f756d88b6bb1d8e8 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:33:10 +0200 Subject: [PATCH 37/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index af431efb94..57fb29e1c0 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -269,6 +269,7 @@ class SentryFlutterPlugin : result.success("") } + @Suppress("TooManyFunctions") companion object { @SuppressLint("StaticFieldLeak") private var replay: ReplayIntegration? = null @@ -313,7 +314,7 @@ class SentryFlutterPlugin : return refreshRate } - @Suppress("unused") // Used by native/jni bindings + @Suppress("unused", "ReturnCount") // Used by native/jni bindings @JvmStatic fun fetchNativeAppStartAsBytes(): ByteArray? { if (!autoPerformanceTracingEnabled) { From eead0ce3e49f6375da50d8a4394454d12e906bb7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:42:47 +0200 Subject: [PATCH 38/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 57fb29e1c0..da5a113850 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -44,7 +44,7 @@ class SentryFlutterPlugin : private lateinit var sentryFlutter: SentryFlutter override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - Companion.pluginRegistrationTime = System.currentTimeMillis() + pluginRegistrationTime = System.currentTimeMillis() context = flutterPluginBinding.applicationContext applicationContext = context @@ -281,7 +281,6 @@ class SentryFlutterPlugin : private var activity: WeakReference? = null private var pluginRegistrationTime: Long? = null - private var autoPerformanceTracingEnabled: Boolean = false private const val NATIVE_CRASH_WAIT_TIME = 500L From 780a59c2f8f83b890c5df0fe9bcac9ad67ad5a9b Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:48:29 +0200 Subject: [PATCH 39/78] Update --- .../integration_test/integration_test.dart | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/packages/flutter/example/integration_test/integration_test.dart b/packages/flutter/example/integration_test/integration_test.dart index 4fa3fc4153..c5d24b352b 100644 --- a/packages/flutter/example/integration_test/integration_test.dart +++ b/packages/flutter/example/integration_test/integration_test.dart @@ -599,6 +599,60 @@ void main() { expect(debugImageByStacktrace.first.imageAddr, expectedImage.imageAddr); }); + testWidgets('fetchNativeAppStart returns app start data', (tester) async { + await restoreFlutterOnErrorAfter(() async { + await setupSentryAndApp(tester); + }); + + if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + // fetchNativeAppStart should return data on mobile platforms + final appStart = await SentryFlutter.native?.fetchNativeAppStart(); + + expect(appStart, isNotNull, reason: 'App start data should be available'); + + if (appStart != null) { + expect(appStart.appStartTime, greaterThan(0), + reason: 'App start time should be positive'); + expect(appStart.pluginRegistrationTime, greaterThan(0), + reason: 'Plugin registration time should be positive'); + expect(appStart.isColdStart, isA(), + reason: 'isColdStart should be a boolean'); + expect(appStart.nativeSpanTimes, isA(), + reason: 'Native span times should be a map'); + } + } else { + // On other platforms, it should return null + final appStart = await SentryFlutter.native?.fetchNativeAppStart(); + expect(appStart, isNull, + reason: 'App start should be null on non-mobile platforms'); + } + }); + + testWidgets('displayRefreshRate returns valid refresh rate', (tester) async { + await restoreFlutterOnErrorAfter(() async { + await setupSentryAndApp(tester); + }); + + if (Platform.isAndroid || Platform.isIOS) { + final refreshRate = await SentryFlutter.native?.displayRefreshRate(); + + // Refresh rate should be available on mobile platforms + expect(refreshRate, isNotNull, + reason: 'Display refresh rate should be available'); + + if (refreshRate != null) { + expect(refreshRate, greaterThan(0), + reason: 'Refresh rate should be positive'); + expect(refreshRate, lessThanOrEqualTo(1000), + reason: 'Refresh rate should be reasonable (<=1000Hz)'); + } + } else { + final refreshRate = await SentryFlutter.native?.displayRefreshRate(); + expect(refreshRate, isNull, + reason: 'Refresh rate should be null or positive on other platforms'); + } + }); + group('e2e', () { var output = find.byKey(const Key('output')); late Fixture fixture; From 143414f3688dfd1561bd3aef40ccab89bbe6f9e3 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 11:54:59 +0200 Subject: [PATCH 40/78] Update --- packages/flutter/example/integration_test/integration_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/example/integration_test/integration_test.dart b/packages/flutter/example/integration_test/integration_test.dart index c5d24b352b..82b2c0e703 100644 --- a/packages/flutter/example/integration_test/integration_test.dart +++ b/packages/flutter/example/integration_test/integration_test.dart @@ -604,7 +604,7 @@ void main() { await setupSentryAndApp(tester); }); - if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + if (Platform.isAndroid || Platform.isIOS) { // fetchNativeAppStart should return data on mobile platforms final appStart = await SentryFlutter.native?.fetchNativeAppStart(); From 60f96741c7a179e72c3f52f040f0626520e0ce60 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 13:03:08 +0200 Subject: [PATCH 41/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutter.kt | 4 +--- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt index 19eec63247..1df72b60b2 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt @@ -21,8 +21,6 @@ class SentryFlutter { internal const val NATIVE_SDK = "sentry.native.android.flutter" } - var autoPerformanceTracingEnabled = false - fun updateOptions( options: SentryAndroidOptions, data: Map, @@ -107,7 +105,7 @@ class SentryFlutter { data.getIfNotNull("enableAutoPerformanceTracing") { enableAutoPerformanceTracing -> if (enableAutoPerformanceTracing) { - autoPerformanceTracingEnabled = true + SentryFlutterPlugin.setAutoPerformanceTracingEnabled(true) } } diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index da5a113850..09e8d34122 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -281,6 +281,7 @@ class SentryFlutterPlugin : private var activity: WeakReference? = null private var pluginRegistrationTime: Long? = null + private var autoPerformanceTracingEnabled: Boolean = false private const val NATIVE_CRASH_WAIT_TIME = 500L @@ -439,6 +440,10 @@ class SentryFlutterPlugin : return json.toByteArray(Charsets.UTF_8) } + internal fun setAutoPerformanceTracingEnabled(enabled: Boolean) { + autoPerformanceTracingEnabled = enabled + } + private fun List?.serialize() = this?.map { it.serialize() } private fun DebugImage.serialize() = From 3eeda60919d43bf4db93af8fb393eff456ec41d7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 13:27:22 +0200 Subject: [PATCH 42/78] Fix tests --- packages/flutter/lib/src/native/native_app_start.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/native/native_app_start.dart b/packages/flutter/lib/src/native/native_app_start.dart index 71f0c1eb58..c4c4efb3f9 100644 --- a/packages/flutter/lib/src/native/native_app_start.dart +++ b/packages/flutter/lib/src/native/native_app_start.dart @@ -9,7 +9,7 @@ class NativeAppStart { required this.isColdStart, required this.nativeSpanTimes}); - double appStartTime; + int appStartTime; int pluginRegistrationTime; bool isColdStart; Map nativeSpanTimes; @@ -20,7 +20,7 @@ class NativeAppStart { final isColdStart = json['isColdStart']; final nativeSpanTimes = json['nativeSpanTimes']; - if (appStartTime is! double || + if (appStartTime is! int || pluginRegistrationTime is! int || isColdStart is! bool || nativeSpanTimes is! Map) { From a40112f0bc462fddcd6ed767c321a48f11aaa6fe Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 13:50:28 +0200 Subject: [PATCH 43/78] Update --- .../src/integrations/native_app_start_handler.dart | 4 ++-- .../flutter/lib/src/native/native_app_start.dart | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/integrations/native_app_start_handler.dart b/packages/flutter/lib/src/integrations/native_app_start_handler.dart index 4eda35c0e7..79ee45d91d 100644 --- a/packages/flutter/lib/src/integrations/native_app_start_handler.dart +++ b/packages/flutter/lib/src/integrations/native_app_start_handler.dart @@ -80,8 +80,8 @@ class NativeAppStartHandler { return null; } - final appStartDateTime = DateTime.fromMillisecondsSinceEpoch( - nativeAppStart.appStartTime.toInt()); + final appStartDateTime = + DateTime.fromMillisecondsSinceEpoch(nativeAppStart.appStartTime); final pluginRegistrationDateTime = DateTime.fromMillisecondsSinceEpoch( nativeAppStart.pluginRegistrationTime); diff --git a/packages/flutter/lib/src/native/native_app_start.dart b/packages/flutter/lib/src/native/native_app_start.dart index c4c4efb3f9..dc56381b68 100644 --- a/packages/flutter/lib/src/native/native_app_start.dart +++ b/packages/flutter/lib/src/native/native_app_start.dart @@ -15,12 +15,22 @@ class NativeAppStart { Map nativeSpanTimes; static NativeAppStart? fromJson(Map json) { - final appStartTime = json['appStartTime']; + final appStartTimeValue = json['appStartTime']; final pluginRegistrationTime = json['pluginRegistrationTime']; final isColdStart = json['isColdStart']; final nativeSpanTimes = json['nativeSpanTimes']; - if (appStartTime is! int || + // Convert appStartTime to int (iOS returns double, Android returns int) + final int? appStartTime; + if (appStartTimeValue is int) { + appStartTime = appStartTimeValue; + } else if (appStartTimeValue is double) { + appStartTime = appStartTimeValue.toInt(); + } else { + appStartTime = null; + } + + if (appStartTime == null || pluginRegistrationTime is! int || isColdStart is! bool || nativeSpanTimes is! Map) { From 83f8ea4a0a95559d64b8b382546166d83c6d89da Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 14:20:12 +0200 Subject: [PATCH 44/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutter.kt | 2 +- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 8 +++----- .../test/kotlin/io/sentry/flutter/SentryFlutterTest.kt | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt index 1df72b60b2..e0e35b9ccc 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt @@ -105,7 +105,7 @@ class SentryFlutter { data.getIfNotNull("enableAutoPerformanceTracing") { enableAutoPerformanceTracing -> if (enableAutoPerformanceTracing) { - SentryFlutterPlugin.setAutoPerformanceTracingEnabled(true) + SentryFlutterPlugin.autoPerformanceTracingEnabled = true } } diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 09e8d34122..a62a58fa38 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -281,7 +281,9 @@ class SentryFlutterPlugin : private var activity: WeakReference? = null private var pluginRegistrationTime: Long? = null - private var autoPerformanceTracingEnabled: Boolean = false + + var autoPerformanceTracingEnabled: Boolean = false + internal set private const val NATIVE_CRASH_WAIT_TIME = 500L @@ -440,10 +442,6 @@ class SentryFlutterPlugin : return json.toByteArray(Charsets.UTF_8) } - internal fun setAutoPerformanceTracingEnabled(enabled: Boolean) { - autoPerformanceTracingEnabled = enabled - } - private fun List?.serialize() = this?.map { it.serialize() } private fun DebugImage.serialize() = diff --git a/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt b/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt index be7eacb873..74c9214a6c 100644 --- a/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt +++ b/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt @@ -62,7 +62,7 @@ class SentryFlutterTest { ) assertEquals("sentry.native.android.flutter", fixture.options.nativeSdkName) - assertEquals(true, sut.autoPerformanceTracingEnabled) + assertEquals(true, SentryFlutterPlugin.autoPerformanceTracingEnabled) assertEquals(9006, fixture.options.connectionTimeoutMillis) assertEquals(9007, fixture.options.readTimeoutMillis) From f9edebc62e659ba9fe4ba42cea7505055617255d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 14:25:50 +0200 Subject: [PATCH 45/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index a62a58fa38..b4c4d71fd0 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -281,7 +281,7 @@ class SentryFlutterPlugin : private var activity: WeakReference? = null private var pluginRegistrationTime: Long? = null - + var autoPerformanceTracingEnabled: Boolean = false internal set From c40ff0cb43691ab50b7041472944459e511758b9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 15:15:07 +0200 Subject: [PATCH 46/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 17 +++---- .../sentry_flutter/SentryFlutterPlugin.swift | 35 +++++--------- .../sentry_flutter_objc/SentryFlutterPlugin.h | 3 ++ .../flutter/lib/src/native/cocoa/binding.dart | 30 ++++++++++++ .../src/native/cocoa/sentry_native_cocoa.dart | 19 ++++++++ .../flutter/lib/src/native/java/binding.dart | 47 +++++++++++++++++++ .../src/native/java/sentry_native_java.dart | 15 ++++++ .../lib/src/native/sentry_native_channel.dart | 16 +++++-- .../test/sentry_native_channel_test.dart | 47 +++++++++++++------ 9 files changed, 179 insertions(+), 50 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 09e8d34122..13151ea1a0 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -71,7 +71,6 @@ class SentryFlutterPlugin : "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) "removeTag" -> removeTag(call.argument("key"), result) - "nativeCrash" -> crash() "setReplayConfig" -> setReplayConfig(call, result) "captureReplay" -> captureReplay(result) else -> result.notImplemented() @@ -289,6 +288,15 @@ class SentryFlutterPlugin : @JvmStatic fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay + @Suppress("unused") // Used by native/jni bindings + @JvmStatic + fun nativeCrash() { + val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") + val mainThread = Looper.getMainLooper().thread + mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception) + mainThread.join(NATIVE_CRASH_WAIT_TIME) + } + @Suppress("unused") // Used by native/jni bindings @JvmStatic fun getDisplayRefreshRate(): Int? { @@ -457,13 +465,6 @@ class SentryFlutterPlugin : "debug_file" to debugFile, ) - private fun crash() { - val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") - val mainThread = Looper.getMainLooper().thread - mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception) - mainThread.join(NATIVE_CRASH_WAIT_TIME) - } - private fun Double.adjustReplaySizeToBlockSize(): Double { val remainder = this % VIDEO_BLOCK_SIZE return if (remainder <= VIDEO_BLOCK_SIZE / 2) { diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 6629269211..44bb1eaff0 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -130,15 +130,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { collectProfile(call, result) #endif - case "pauseAppHangTracking": - pauseAppHangTracking(result) - - case "resumeAppHangTracking": - resumeAppHangTracking(result) - - case "nativeCrash": - crash() - case "captureReplay": #if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS)) PrivateSentrySDKOnly.captureReplay() @@ -431,20 +422,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { result(nil) } - private func pauseAppHangTracking(_ result: @escaping FlutterResult) { - SentrySDK.pauseAppHangTracking() - result("") - } - - private func resumeAppHangTracking(_ result: @escaping FlutterResult) { - SentrySDK.resumeAppHangTracking() - result("") - } - - private func crash() { - SentrySDK.crash() - } - // MARK: - Objective-C interoperability // // Group of methods exposed to the Objective-C runtime via `@objc`. @@ -541,6 +518,18 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { #endif } + @objc public class func nativeCrash() { + SentrySDK.crash() + } + + @objc public class func pauseAppHangTracking() { + SentrySDK.pauseAppHangTracking() + } + + @objc public class func resumeAppHangTracking() { + SentrySDK.resumeAppHangTracking() + } + @objc(loadDebugImagesAsBytes:) public class func loadDebugImagesAsBytes(instructionAddresses: Set) -> NSData? { var debugImages: [DebugMeta] = [] diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index 6f2e25eb54..61af58310d 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -8,5 +8,8 @@ + (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; ++ (void)nativeCrash; ++ (void)pauseAppHangTracking; ++ (void)resumeAppHangTracking; @end #endif diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 903062aa10..73d693b44d 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1128,6 +1128,19 @@ late final _sel_fetchNativeAppStartAsBytes = late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); +late final _sel_nativeCrash = objc.registerName("nativeCrash"); +final _objc_msgSend_1pl9qdv = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, + ffi.Pointer)>>() + .asFunction< + void Function( + ffi.Pointer, ffi.Pointer)>(); +late final _sel_pauseAppHangTracking = + objc.registerName("pauseAppHangTracking"); +late final _sel_resumeAppHangTracking = + objc.registerName("resumeAppHangTracking"); /// SentryFlutterPlugin class SentryFlutterPlugin extends objc.NSObject { @@ -1186,6 +1199,23 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSData.castFromPointer(_ret, retain: true, release: true); } + /// nativeCrash + static void nativeCrash() { + _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash); + } + + /// pauseAppHangTracking + static void pauseAppHangTracking() { + _objc_msgSend_1pl9qdv( + _class_SentryFlutterPlugin, _sel_pauseAppHangTracking); + } + + /// resumeAppHangTracking + static void resumeAppHangTracking() { + _objc_msgSend_1pl9qdv( + _class_SentryFlutterPlugin, _sel_resumeAppHangTracking); + } + /// init SentryFlutterPlugin init() { objc.checkOsVersionInternal('SentryFlutterPlugin.init', diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 0a8dc2ee4a..097b26bc30 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -178,4 +178,23 @@ class SentryNativeCocoa extends SentryNativeChannel { return NativeAppStart.fromJson(json); }, ); + + @override + void nativeCrash() { + cocoa.SentryFlutterPlugin.nativeCrash(); + } + + @override + void pauseAppHangTracking() { + tryCatchSync('pauseAppHangTracking', () { + cocoa.SentryFlutterPlugin.pauseAppHangTracking(); + }); + } + + @override + void resumeAppHangTracking() { + tryCatchSync('resumeAppHangTracking', () { + cocoa.SentryFlutterPlugin.resumeAppHangTracking(); + }); + } } diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index feae89319b..f9730b691a 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1305,6 +1305,29 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } + static final _id_nativeCrash = _class.instanceMethodId( + r'nativeCrash', + r'()V', + ); + + static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final void nativeCrash()` + void nativeCrash() { + _nativeCrash(reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) + .check(); + } + static final _id_getDisplayRefreshRate = _class.instanceMethodId( r'getDisplayRefreshRate', r'()Ljava/lang/Integer;', @@ -1816,6 +1839,30 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } + static final _id_nativeCrash = _class.staticMethodId( + r'nativeCrash', + r'()V', + ); + + static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public final void nativeCrash()` + static void nativeCrash() { + _nativeCrash( + _class.reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) + .check(); + } + static final _id_getDisplayRefreshRate = _class.staticMethodId( r'getDisplayRefreshRate', r'()Ljava/lang/Integer;', diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index b79d8b9db4..817d51c00d 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -199,6 +199,21 @@ class SentryNativeJava extends SentryNativeChannel { }); } + @override + void nativeCrash() { + native.SentryFlutterPlugin.Companion.nativeCrash(); + } + + @override + void pauseAppHangTracking() { + assert(false, 'pauseAppHangTracking is not supported on Android.'); + } + + @override + void resumeAppHangTracking() { + assert(false, 'resumeAppHangTracking is not supported on Android.'); + } + @override Future close() async { await _replayRecorder?.stop(); diff --git a/packages/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart index 384bdcaca7..b1f2f20ab4 100644 --- a/packages/flutter/lib/src/native/sentry_native_channel.dart +++ b/packages/flutter/lib/src/native/sentry_native_channel.dart @@ -230,15 +230,21 @@ class SentryNativeChannel } @override - Future pauseAppHangTracking() => - channel.invokeMethod('pauseAppHangTracking'); + FutureOr pauseAppHangTracking() { + assert(false, + 'pauseAppHangTracking should not be used through method channels.'); + } @override - Future resumeAppHangTracking() => - channel.invokeMethod('resumeAppHangTracking'); + FutureOr resumeAppHangTracking() { + assert(false, + 'resumeAppHangTracking should not be used through method channels.'); + } @override - Future nativeCrash() => channel.invokeMethod('nativeCrash'); + FutureOr nativeCrash() { + assert(false, 'nativeCrash should not be used through method channels.'); + } @override bool get supportsReplay => false; diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index f35d68ea8b..7288d00971 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -263,30 +263,49 @@ void main() { }); test('pauseAppHangTracking', () async { - when(channel.invokeMethod('pauseAppHangTracking')) - .thenAnswer((_) => Future.value()); - - await sut.pauseAppHangTracking(); + if (mockPlatform.isAndroid) { + // Android doesn't support app hang tracking, so it should hit the assertion + expect(() => sut.pauseAppHangTracking(), throwsAssertionError); + } else { + // iOS/macOS should throw FFI exceptions in tests + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); + expect(() => sut.pauseAppHangTracking(), matcher); + } - verify(channel.invokeMethod('pauseAppHangTracking')); + verifyZeroInteractions(channel); }); test('resumeAppHangTracking', () async { - when(channel.invokeMethod('resumeAppHangTracking')) - .thenAnswer((_) => Future.value()); - - await sut.resumeAppHangTracking(); + if (mockPlatform.isAndroid) { + // Android doesn't support app hang tracking, so it should hit the assertion + expect(() => sut.resumeAppHangTracking(), throwsAssertionError); + } else { + // iOS/macOS should throw FFI exceptions in tests + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); + expect(() => sut.resumeAppHangTracking(), matcher); + } - verify(channel.invokeMethod('resumeAppHangTracking')); + verifyZeroInteractions(channel); }); test('nativeCrash', () async { - when(channel.invokeMethod('nativeCrash')) - .thenAnswer((_) => Future.value()); + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); - await sut.nativeCrash(); + expect(() => sut.nativeCrash(), matcher); - verify(channel.invokeMethod('nativeCrash')); + verifyZeroInteractions(channel); }); test('setReplayConfig', () async { From d4641f923f2d3537e0ca81eb58ba6fb654179f6a Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 9 Oct 2025 16:37:04 +0200 Subject: [PATCH 47/78] Breadcrumb support --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 28 +++-- .../sentry_flutter/SentryFlutterPlugin.swift | 16 +++ .../sentry_flutter_objc/SentryFlutterPlugin.h | 2 + .../flutter/lib/src/native/cocoa/binding.dart | 16 ++- .../src/native/cocoa/sentry_native_cocoa.dart | 33 ++++++ .../flutter/lib/src/native/java/binding.dart | 100 ++++++++++++++++++ .../src/native/java/sentry_native_java.dart | 37 +++++++ 7 files changed, 224 insertions(+), 8 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 13151ea1a0..9f9415b945 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -15,8 +15,12 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.sentry.Breadcrumb import io.sentry.DateUtils +import io.sentry.JsonObjectDeserializer +import io.sentry.JsonObjectReader +import io.sentry.ObjectReader import io.sentry.ScopesAdapter import io.sentry.Sentry +import io.sentry.SentryOptions import io.sentry.android.core.InternalSentrySdk import io.sentry.android.core.SentryAndroid import io.sentry.android.core.SentryAndroidOptions @@ -29,6 +33,7 @@ import io.sentry.protocol.User import io.sentry.transport.CurrentDateProvider import org.json.JSONObject import org.json.JSONArray +import java.io.StringReader import java.lang.ref.WeakReference import kotlin.math.roundToInt @@ -66,7 +71,6 @@ class SentryFlutterPlugin : "removeContexts" -> removeContexts(call.argument("key"), result) "setUser" -> setUser(call.argument("user"), result) "addBreadcrumb" -> addBreadcrumb(call.argument("breadcrumb"), result) - "clearBreadcrumbs" -> clearBreadcrumbs(result) "setExtra" -> setExtra(call.argument("key"), call.argument("value"), result) "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) @@ -202,12 +206,6 @@ class SentryFlutterPlugin : result.success("") } - private fun clearBreadcrumbs(result: Result) { - Sentry.clearBreadcrumbs() - - result.success("") - } - private fun setExtra( key: String?, value: String?, @@ -448,6 +446,22 @@ class SentryFlutterPlugin : return json.toByteArray(Charsets.UTF_8) } + @Suppress("unused") // Used by native/jni bindings + @JvmStatic + fun addBreadcrumbAsBytes(breadcrumbBytes: ByteArray) { + val logger = ScopesAdapter.getInstance().options.logger + val breadcrumbJson = breadcrumbBytes.toString(Charsets.UTF_8) + val reader = JsonObjectReader(StringReader(breadcrumbJson)) + val breadcrumb = Breadcrumb.Deserializer().deserialize(reader, logger) + Sentry.addBreadcrumb(breadcrumb) + } + + @Suppress("unused") // Used by native/jni bindings + @JvmStatic + fun clearBreadcrumbs() { + Sentry.clearBreadcrumbs() + } + internal fun setAutoPerformanceTracingEnabled(enabled: Boolean) { autoPerformanceTracingEnabled = enabled } diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 44bb1eaff0..082e13a607 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -646,6 +646,22 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { } return nil } + + @objc public class func addBreadcrumbAsBytes(_ breadcrumbBytes: NSData) { + guard let breadcrumbString = String(data: breadcrumbBytes as Data, encoding: .utf8), + let jsonData = breadcrumbString.data(using: .utf8), + let breadcrumbDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else { + return + } + let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumbDict) + SentrySDK.addBreadcrumb(breadcrumbInstance) + } + + @objc public class func clearBreadcrumbs() { + SentrySDK.configureScope { scope in + scope.clearBreadcrumbs() + } + } } // swiftlint:enable type_body_length diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index 61af58310d..83a6b0288b 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -8,6 +8,8 @@ + (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; ++ (void)addBreadcrumbAsBytes:(NSData *)breadcrumbBytes; ++ (void)clearBreadcrumbs; + (void)nativeCrash; + (void)pauseAppHangTracking; + (void)resumeAppHangTracking; diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 73d693b44d..b7fd3891c4 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1128,7 +1128,9 @@ late final _sel_fetchNativeAppStartAsBytes = late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); -late final _sel_nativeCrash = objc.registerName("nativeCrash"); +late final _sel_addBreadcrumbAsBytes_ = + objc.registerName("addBreadcrumbAsBytes:"); +late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -1137,6 +1139,7 @@ final _objc_msgSend_1pl9qdv = objc.msgSendPointer .asFunction< void Function( ffi.Pointer, ffi.Pointer)>(); +late final _sel_nativeCrash = objc.registerName("nativeCrash"); late final _sel_pauseAppHangTracking = objc.registerName("pauseAppHangTracking"); late final _sel_resumeAppHangTracking = @@ -1199,6 +1202,17 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSData.castFromPointer(_ret, retain: true, release: true); } + /// addBreadcrumbAsBytes: + static void addBreadcrumbAsBytes(objc.NSData breadcrumbBytes) { + _objc_msgSend_xtuoz7(_class_SentryFlutterPlugin, _sel_addBreadcrumbAsBytes_, + breadcrumbBytes.ref.pointer); + } + + /// clearBreadcrumbs + static void clearBreadcrumbs() { + _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_clearBreadcrumbs); + } + /// nativeCrash static void nativeCrash() { _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash); diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 097b26bc30..b2536fecb3 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; @@ -197,4 +198,36 @@ class SentryNativeCocoa extends SentryNativeChannel { cocoa.SentryFlutterPlugin.resumeAppHangTracking(); }); } + + @override + Future addBreadcrumb(Breadcrumb breadcrumb) async { + tryCatchSync('addBreadcrumb', () { + // Normalize breadcrumb data like the method channel does + final normalizedBreadcrumb = Breadcrumb( + message: breadcrumb.message, + category: breadcrumb.category, + data: breadcrumb.data != null + ? Map.from(breadcrumb.data!) + : null, + level: breadcrumb.level, + type: breadcrumb.type, + timestamp: breadcrumb.timestamp, + // ignore: invalid_use_of_internal_member + unknown: breadcrumb.unknown, + ); + + final jsonString = json.encode(normalizedBreadcrumb.toJson()); + final bytes = utf8.encode(jsonString); + final nsData = bytes.toNSData(); + + cocoa.SentryFlutterPlugin.addBreadcrumbAsBytes(nsData); + }); + } + + @override + Future clearBreadcrumbs() async { + tryCatchSync('clearBreadcrumbs', () { + cocoa.SentryFlutterPlugin.clearBreadcrumbs(); + }); + } } diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index f9730b691a..e8ea01e522 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1456,6 +1456,56 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const jni$_.JByteArrayNullableType()); } + static final _id_addBreadcrumbAsBytes = _class.instanceMethodId( + r'addBreadcrumbAsBytes', + r'([B)V', + ); + + static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public final void addBreadcrumbAsBytes(byte[] bs)` + void addBreadcrumbAsBytes( + jni$_.JByteArray bs, + ) { + final _$bs = bs.reference; + _addBreadcrumbAsBytes(reference.pointer, + _id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer) + .check(); + } + + static final _id_clearBreadcrumbs = _class.instanceMethodId( + r'clearBreadcrumbs', + r'()V', + ); + + static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final void clearBreadcrumbs()` + void clearBreadcrumbs() { + _clearBreadcrumbs( + reference.pointer, _id_clearBreadcrumbs as jni$_.JMethodIDPtr) + .check(); + } + static final _id_new$ = _class.constructorId( r'(Lkotlin/jvm/internal/DefaultConstructorMarker;)V', ); @@ -1990,6 +2040,56 @@ class SentryFlutterPlugin extends jni$_.JObject { _id_loadDebugImagesAsBytes as jni$_.JMethodIDPtr, _$set.pointer) .object(const jni$_.JByteArrayNullableType()); } + + static final _id_addBreadcrumbAsBytes = _class.staticMethodId( + r'addBreadcrumbAsBytes', + r'([B)V', + ); + + static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public final void addBreadcrumbAsBytes(byte[] bs)` + static void addBreadcrumbAsBytes( + jni$_.JByteArray bs, + ) { + final _$bs = bs.reference; + _addBreadcrumbAsBytes(_class.reference.pointer, + _id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer) + .check(); + } + + static final _id_clearBreadcrumbs = _class.staticMethodId( + r'clearBreadcrumbs', + r'()V', + ); + + static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public final void clearBreadcrumbs()` + static void clearBreadcrumbs() { + _clearBreadcrumbs(_class.reference.pointer, + _id_clearBreadcrumbs as jni$_.JMethodIDPtr) + .check(); + } } final class $SentryFlutterPlugin$NullableType diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 817d51c00d..9b83b93425 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:jni/jni.dart'; @@ -220,4 +221,40 @@ class SentryNativeJava extends SentryNativeChannel { await _envelopeSender?.close(); return super.close(); } + + @override + Future addBreadcrumb(Breadcrumb breadcrumb) async { + JByteArray? breadcrumbBytes; + + tryCatchSync('addBreadcrumb', () { + final normalizedBreadcrumb = Breadcrumb( + message: breadcrumb.message, + category: breadcrumb.category, + data: breadcrumb.data != null + ? Map.from(breadcrumb.data!) + : null, + level: breadcrumb.level, + type: breadcrumb.type, + timestamp: breadcrumb.timestamp, + // ignore: invalid_use_of_internal_member + unknown: breadcrumb.unknown, + ); + + final jsonString = json.encode(normalizedBreadcrumb.toJson()); + final bytes = utf8.encode(jsonString); + breadcrumbBytes = JByteArray.from(bytes); + + native.SentryFlutterPlugin.Companion + .addBreadcrumbAsBytes(breadcrumbBytes!); + }, finallyFn: () { + breadcrumbBytes?.release(); + }); + } + + @override + Future clearBreadcrumbs() async { + tryCatchSync('clearBreadcrumbs', () { + native.SentryFlutterPlugin.Companion.clearBreadcrumbs(); + }); + } } From 0cfd7bafd882a8a09a578bf973ac2073dd65cf3d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 11:12:57 +0200 Subject: [PATCH 48/78] Update --- .../integration_test/integration_test.dart | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/flutter/example/integration_test/integration_test.dart b/packages/flutter/example/integration_test/integration_test.dart index 82b2c0e703..fb44a7bad5 100644 --- a/packages/flutter/example/integration_test/integration_test.dart +++ b/packages/flutter/example/integration_test/integration_test.dart @@ -564,6 +564,51 @@ void main() { } }); + testWidgets('addBreadcrumb and clearBreadcrumbs sync to native', + (tester) async { + await restoreFlutterOnErrorAfter(() async { + await setupSentryAndApp(tester); + }); + + // 1. Add a breadcrumb via Dart + final testBreadcrumb = Breadcrumb( + message: 'test-breadcrumb-message', + category: 'test-category', + level: SentryLevel.info, + ); + await Sentry.addBreadcrumb(testBreadcrumb); + + // 2. Verify it appears in native via loadContexts + var contexts = await SentryFlutter.native?.loadContexts(); + expect(contexts, isNotNull); + + var breadcrumbs = contexts!['breadcrumbs'] as List?; + expect(breadcrumbs, isNotNull, + reason: 'Breadcrumbs should not be null after adding'); + expect(breadcrumbs!.isNotEmpty, isTrue, + reason: 'Breadcrumbs should not be empty after adding'); + + // Find our test breadcrumb + final testCrumb = breadcrumbs.firstWhere( + (b) => b['message'] == 'test-breadcrumb-message', + orElse: () => null, + ); + expect(testCrumb, isNotNull, + reason: 'Test breadcrumb should exist in native breadcrumbs'); + expect(testCrumb['category'], equals('test-category')); + + // 3. Clear breadcrumbs + await Sentry.configureScope((scope) async { + await scope.clearBreadcrumbs(); + }); + + // 4. Verify they're cleared in native + contexts = await SentryFlutter.native?.loadContexts(); + breadcrumbs = contexts!['breadcrumbs'] as List?; + expect(breadcrumbs == null || breadcrumbs!.isEmpty, isTrue, + reason: 'Breadcrumbs should be null or empty after clearing'); + }); + testWidgets('loads debug images through loadDebugImages', (tester) async { await restoreFlutterOnErrorAfter(() async { await setupSentryAndApp(tester); From 5ad708c2d4320557fec17778f8561d5fe3181514 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 11:15:35 +0200 Subject: [PATCH 49/78] Update --- packages/flutter/example/integration_test/integration_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/example/integration_test/integration_test.dart b/packages/flutter/example/integration_test/integration_test.dart index fb44a7bad5..0c121748ce 100644 --- a/packages/flutter/example/integration_test/integration_test.dart +++ b/packages/flutter/example/integration_test/integration_test.dart @@ -605,7 +605,7 @@ void main() { // 4. Verify they're cleared in native contexts = await SentryFlutter.native?.loadContexts(); breadcrumbs = contexts!['breadcrumbs'] as List?; - expect(breadcrumbs == null || breadcrumbs!.isEmpty, isTrue, + expect(breadcrumbs == null || breadcrumbs.isEmpty, isTrue, reason: 'Breadcrumbs should be null or empty after clearing'); }); From d5857e80e64a447260ee29557e15e5e150a2efb8 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 11:30:24 +0200 Subject: [PATCH 50/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 13 ------- .../sentry_flutter/SentryFlutterPlugin.swift | 24 ------------ .../src/native/cocoa/sentry_native_cocoa.dart | 5 +-- .../src/native/java/sentry_native_java.dart | 5 +-- .../lib/src/native/method_channel_helper.dart | 38 ------------------- .../lib/src/native/sentry_native_channel.dart | 28 +++++--------- .../lib/src/native/utils/data_normalizer.dart | 27 +++++++++++++ .../data_normalizer_test.dart} | 32 ++++++++-------- 8 files changed, 56 insertions(+), 116 deletions(-) delete mode 100644 packages/flutter/lib/src/native/method_channel_helper.dart create mode 100644 packages/flutter/lib/src/native/utils/data_normalizer.dart rename packages/flutter/test/{method_channel_helper_test.dart => utils/data_normalizer_test.dart} (76%) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 9f9415b945..e223aecc23 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -70,7 +70,6 @@ class SentryFlutterPlugin : "setContexts" -> setContexts(call.argument("key"), call.argument("value"), result) "removeContexts" -> removeContexts(call.argument("key"), result) "setUser" -> setUser(call.argument("user"), result) - "addBreadcrumb" -> addBreadcrumb(call.argument("breadcrumb"), result) "setExtra" -> setExtra(call.argument("key"), call.argument("value"), result) "removeExtra" -> removeExtra(call.argument("key"), result) "setTag" -> setTag(call.argument("key"), call.argument("value"), result) @@ -194,18 +193,6 @@ class SentryFlutterPlugin : result.success("") } - private fun addBreadcrumb( - breadcrumb: Map?, - result: Result, - ) { - if (breadcrumb != null) { - val options = ScopesAdapter.getInstance().options - val breadcrumbInstance = Breadcrumb.fromMap(breadcrumb, options) - Sentry.addBreadcrumb(breadcrumbInstance) - } - result.success("") - } - private fun setExtra( key: String?, value: String?, diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 082e13a607..6e8643b993 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -92,14 +92,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { let user = arguments?["user"] as? [String: Any] setUser(user: user, result: result) - case "addBreadcrumb": - let arguments = call.arguments as? [String: Any?] - let breadcrumb = arguments?["breadcrumb"] as? [String: Any] - addBreadcrumb(breadcrumb: breadcrumb, result: result) - - case "clearBreadcrumbs": - clearBreadcrumbs(result: result) - case "setExtra": let arguments = call.arguments as? [String: Any?] let key = arguments?["key"] as? String @@ -322,22 +314,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { result("") } - private func addBreadcrumb(breadcrumb: [String: Any]?, result: @escaping FlutterResult) { - if let breadcrumb = breadcrumb { - let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumb) - SentrySDK.addBreadcrumb(breadcrumbInstance) - } - result("") - } - - private func clearBreadcrumbs(result: @escaping FlutterResult) { - SentrySDK.configureScope { scope in - scope.clearBreadcrumbs() - - result("") - } - } - private func setExtra(key: String?, value: Any?, result: @escaping FlutterResult) { guard let key = key else { result("") diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index b2536fecb3..bfcb23755a 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -8,6 +8,7 @@ import '../../../sentry_flutter.dart'; import '../../replay/replay_config.dart'; import '../native_app_start.dart'; import '../sentry_native_channel.dart'; +import '../utils/data_normalizer.dart'; import '../utils/utf8_json.dart'; import 'binding.dart' as cocoa; import 'cocoa_replay_recorder.dart'; @@ -206,9 +207,7 @@ class SentryNativeCocoa extends SentryNativeChannel { final normalizedBreadcrumb = Breadcrumb( message: breadcrumb.message, category: breadcrumb.category, - data: breadcrumb.data != null - ? Map.from(breadcrumb.data!) - : null, + data: normalizeMap(breadcrumb.data), level: breadcrumb.level, type: breadcrumb.type, timestamp: breadcrumb.timestamp, diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 9b83b93425..d183a46128 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -9,6 +9,7 @@ import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; import '../native_app_start.dart'; import '../sentry_native_channel.dart'; +import '../utils/data_normalizer.dart'; import '../utils/utf8_json.dart'; import 'android_envelope_sender.dart'; import 'android_replay_recorder.dart'; @@ -230,9 +231,7 @@ class SentryNativeJava extends SentryNativeChannel { final normalizedBreadcrumb = Breadcrumb( message: breadcrumb.message, category: breadcrumb.category, - data: breadcrumb.data != null - ? Map.from(breadcrumb.data!) - : null, + data: normalizeMap(breadcrumb.data), level: breadcrumb.level, type: breadcrumb.type, timestamp: breadcrumb.timestamp, diff --git a/packages/flutter/lib/src/native/method_channel_helper.dart b/packages/flutter/lib/src/native/method_channel_helper.dart deleted file mode 100644 index bd3c8864b8..0000000000 --- a/packages/flutter/lib/src/native/method_channel_helper.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Makes sure no invalid data is sent over method channels. -@internal -class MethodChannelHelper { - static dynamic normalize(dynamic data) { - if (data == null) { - return null; - } - if (_isPrimitive(data)) { - return data; - } else if (data is List) { - return _normalizeList(data); - } else if (data is Map) { - return normalizeMap(data); - } else { - return data.toString(); - } - } - - static Map? normalizeMap(Map? data) { - if (data == null) { - return null; - } - return data.map((key, value) => MapEntry(key, normalize(value))); - } - - static List? _normalizeList(List? data) { - if (data == null) { - return null; - } - return data.map((e) => normalize(e)).toList(); - } - - static bool _isPrimitive(dynamic value) { - return value == null || value is String || value is num || value is bool; - } -} diff --git a/packages/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart index b1f2f20ab4..4cc4c6dec0 100644 --- a/packages/flutter/lib/src/native/sentry_native_channel.dart +++ b/packages/flutter/lib/src/native/sentry_native_channel.dart @@ -8,11 +8,11 @@ import 'package:meta/meta.dart'; import '../../sentry_flutter.dart'; import '../replay/replay_config.dart'; -import 'method_channel_helper.dart'; import 'native_app_start.dart'; import 'sentry_native_binding.dart'; import 'sentry_native_invoker.dart'; import 'sentry_safe_method_channel.dart'; +import 'utils/data_normalizer.dart'; /// Provide typed methods to access native layer via MethodChannel. @internal @@ -134,7 +134,7 @@ class SentryNativeChannel username: user.username, email: user.email, ipAddress: user.ipAddress, - data: MethodChannelHelper.normalizeMap(user.data), + data: normalizeMap(user.data), // ignore: deprecated_member_use extras: user.extras, geo: user.geo, @@ -151,29 +151,19 @@ class SentryNativeChannel @override Future addBreadcrumb(Breadcrumb breadcrumb) async { - final normalizedBreadcrumb = Breadcrumb( - message: breadcrumb.message, - category: breadcrumb.category, - data: MethodChannelHelper.normalizeMap(breadcrumb.data), - level: breadcrumb.level, - type: breadcrumb.type, - timestamp: breadcrumb.timestamp, - // ignore: invalid_use_of_internal_member - unknown: breadcrumb.unknown, - ); - await channel.invokeMethod( - 'addBreadcrumb', - {'breadcrumb': normalizedBreadcrumb.toJson()}, - ); + assert(false, "addBreadcrumb should not be used through method channels."); } @override - Future clearBreadcrumbs() => channel.invokeMethod('clearBreadcrumbs'); + Future clearBreadcrumbs() async { + assert( + false, "clearBreadcrumbs should not be used through method channels."); + } @override Future setContexts(String key, dynamic value) => channel.invokeMethod( 'setContexts', - {'key': key, 'value': MethodChannelHelper.normalize(value)}, + {'key': key, 'value': normalize(value)}, ); @override @@ -183,7 +173,7 @@ class SentryNativeChannel @override Future setExtra(String key, dynamic value) => channel.invokeMethod( 'setExtra', - {'key': key, 'value': MethodChannelHelper.normalize(value)}, + {'key': key, 'value': normalize(value)}, ); @override diff --git a/packages/flutter/lib/src/native/utils/data_normalizer.dart b/packages/flutter/lib/src/native/utils/data_normalizer.dart new file mode 100644 index 0000000000..8d0535ae30 --- /dev/null +++ b/packages/flutter/lib/src/native/utils/data_normalizer.dart @@ -0,0 +1,27 @@ +import 'package:meta/meta.dart'; + +/// Normalizes data for serialization across native boundaries. +/// Converts non-primitive types to strings for safe serialization. +@internal +dynamic normalize(dynamic data) { + if (data == null) return null; + if (_isPrimitive(data)) return data; + if (data is List) return _normalizeList(data); + if (data is Map) return normalizeMap(data); + return data.toString(); +} + +@internal +Map? normalizeMap(Map? data) { + if (data == null) return null; + return data.map((key, value) => MapEntry(key, normalize(value))); +} + +List? _normalizeList(List? data) { + if (data == null) return null; + return data.map((e) => normalize(e)).toList(); +} + +bool _isPrimitive(dynamic value) { + return value == null || value is String || value is num || value is bool; +} diff --git a/packages/flutter/test/method_channel_helper_test.dart b/packages/flutter/test/utils/data_normalizer_test.dart similarity index 76% rename from packages/flutter/test/method_channel_helper_test.dart rename to packages/flutter/test/utils/data_normalizer_test.dart index 12729e7313..e1cc2fc1fd 100644 --- a/packages/flutter/test/method_channel_helper_test.dart +++ b/packages/flutter/test/utils/data_normalizer_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/src/native/method_channel_helper.dart'; import 'package:collection/collection.dart'; +import 'package:sentry_flutter/src/native/utils/data_normalizer.dart'; void main() { group('normalize', () { @@ -13,21 +13,21 @@ void main() { 'string': 'Foo', }; - var actual = MethodChannelHelper.normalizeMap(expected); + var actual = normalizeMap(expected); expect( DeepCollectionEquality().equals(actual, expected), true, ); - expect(MethodChannelHelper.normalize(null), null); - expect(MethodChannelHelper.normalize(1), 1); - expect(MethodChannelHelper.normalize(1.1), 1.1); - expect(MethodChannelHelper.normalize(true), true); - expect(MethodChannelHelper.normalize('Foo'), 'Foo'); + expect(normalize(null), null); + expect(normalize(1), 1); + expect(normalize(1.1), 1.1); + expect(normalize(true), true); + expect(normalize('Foo'), 'Foo'); }); test('object', () { - expect(MethodChannelHelper.normalize(_CustomObject()), 'CustomObject()'); + expect(normalize(_CustomObject()), 'CustomObject()'); }); test('object in list', () { @@ -38,7 +38,7 @@ void main() { 'object': ['CustomObject()'] }; - var actual = MethodChannelHelper.normalize(input); + var actual = normalize(input); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -53,7 +53,7 @@ void main() { 'object': {'object': 'CustomObject()'} }; - var actual = MethodChannelHelper.normalize(input); + var actual = normalize(input); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -71,7 +71,7 @@ void main() { 'string': 'Foo', }; - var actual = MethodChannelHelper.normalizeMap(expected); + var actual = normalizeMap(expected); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -83,7 +83,7 @@ void main() { 'list': [null, 1, 1.1, true, 'Foo'], }; - var actual = MethodChannelHelper.normalizeMap(expected); + var actual = normalizeMap(expected); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -101,7 +101,7 @@ void main() { }, }; - var actual = MethodChannelHelper.normalizeMap(expected); + var actual = normalizeMap(expected); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -112,7 +112,7 @@ void main() { var input = {'object': _CustomObject()}; var expected = {'object': 'CustomObject()'}; - var actual = MethodChannelHelper.normalizeMap(input); + var actual = normalizeMap(input); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -127,7 +127,7 @@ void main() { 'object': ['CustomObject()'] }; - var actual = MethodChannelHelper.normalizeMap(input); + var actual = normalizeMap(input); expect( DeepCollectionEquality().equals(actual, expected), true, @@ -142,7 +142,7 @@ void main() { 'object': {'object': 'CustomObject()'} }; - var actual = MethodChannelHelper.normalizeMap(input); + var actual = normalizeMap(input); expect( DeepCollectionEquality().equals(actual, expected), true, From 21155c054dc66fd4bd3e83cf8785de729456227f Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 11:39:08 +0200 Subject: [PATCH 51/78] Update --- packages/flutter/test/sentry_native_channel_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index 7288d00971..74f9814f4b 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -10,8 +10,8 @@ import 'package:mockito/mockito.dart'; import 'package:sentry/src/platform/mock_platform.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/native/factory.dart'; -import 'package:sentry_flutter/src/native/method_channel_helper.dart'; import 'package:sentry_flutter/src/native/sentry_native_binding.dart'; +import 'package:sentry_flutter/src/native/utils/data_normalizer.dart'; import 'package:sentry_flutter/src/replay/replay_config.dart'; import 'mocks.dart'; @@ -59,7 +59,7 @@ void main() { username: user.username, email: user.email, ipAddress: user.ipAddress, - data: MethodChannelHelper.normalizeMap(user.data), + data: normalizeMap(user.data), // ignore: deprecated_member_use extras: user.extras, geo: user.geo, @@ -83,7 +83,7 @@ void main() { final normalizedBreadcrumb = Breadcrumb( message: breadcrumb.message, category: breadcrumb.category, - data: MethodChannelHelper.normalizeMap(breadcrumb.data), + data: normalizeMap(breadcrumb.data), level: breadcrumb.level, type: breadcrumb.type, timestamp: breadcrumb.timestamp, @@ -111,7 +111,7 @@ void main() { test('setContexts', () async { final value = {'object': Object()}; - final normalizedValue = MethodChannelHelper.normalize(value); + final normalizedValue = normalize(value); when(channel.invokeMethod('setContexts', { 'key': 'fixture-key', 'value': normalizedValue @@ -134,7 +134,7 @@ void main() { test('setExtra', () async { final value = {'object': Object()}; - final normalizedValue = MethodChannelHelper.normalize(value); + final normalizedValue = normalize(value); when(channel.invokeMethod( 'setExtra', {'key': 'fixture-key', 'value': normalizedValue})) .thenAnswer((_) => Future.value()); From 054c6ff81f09f82b4bf3084ec4128a016777fa0d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 11:47:44 +0200 Subject: [PATCH 52/78] Fix unit tests --- .../test/sentry_native_channel_test.dart | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/flutter/test/sentry_native_channel_test.dart b/packages/flutter/test/sentry_native_channel_test.dart index 74f9814f4b..3cd2d925f6 100644 --- a/packages/flutter/test/sentry_native_channel_test.dart +++ b/packages/flutter/test/sentry_native_channel_test.dart @@ -77,36 +77,31 @@ void main() { }); test('addBreadcrumb', () async { + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); + final breadcrumb = Breadcrumb( data: {'object': Object()}, ); - final normalizedBreadcrumb = Breadcrumb( - message: breadcrumb.message, - category: breadcrumb.category, - data: normalizeMap(breadcrumb.data), - level: breadcrumb.level, - type: breadcrumb.type, - timestamp: breadcrumb.timestamp, - // ignore: invalid_use_of_internal_member - unknown: breadcrumb.unknown, - ); - when(channel.invokeMethod( - 'addBreadcrumb', {'breadcrumb': normalizedBreadcrumb.toJson()})) - .thenAnswer((_) => Future.value()); - await sut.addBreadcrumb(breadcrumb); + expect(() => sut.addBreadcrumb(breadcrumb), matcher); - verify(channel.invokeMethod( - 'addBreadcrumb', {'breadcrumb': normalizedBreadcrumb.toJson()})); + verifyZeroInteractions(channel); }); test('clearBreadcrumbs', () async { - when(channel.invokeMethod('clearBreadcrumbs')) - .thenAnswer((_) => Future.value()); + final matcher = _nativeUnavailableMatcher( + mockPlatform, + includeLookupSymbol: true, + includeFailedToLoadClassException: true, + ); - await sut.clearBreadcrumbs(); + expect(() => sut.clearBreadcrumbs(), matcher); - verify(channel.invokeMethod('clearBreadcrumbs')); + verifyZeroInteractions(channel); }); test('setContexts', () async { From bfb5259545891ac65a081935801c069cec9c3231 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 13 Oct 2025 16:31:02 +0200 Subject: [PATCH 53/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 4 --- .../flutter/lib/src/native/java/binding.dart | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 8d283e9d56..7f1e7d3c94 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -451,10 +451,6 @@ class SentryFlutterPlugin : Sentry.clearBreadcrumbs() } - internal fun setAutoPerformanceTracingEnabled(enabled: Boolean) { - autoPerformanceTracingEnabled = enabled - } - private fun List?.serialize() = this?.map { it.serialize() } private fun DebugImage.serialize() = diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index e8ea01e522..281d75701e 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1279,6 +1279,31 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { /// The type which includes information such as the signature of this class. static const nullableType = $SentryFlutterPlugin$Companion$NullableType(); static const type = $SentryFlutterPlugin$Companion$Type(); + static final _id_getAutoPerformanceTracingEnabled = _class.instanceMethodId( + r'getAutoPerformanceTracingEnabled', + r'()Z', + ); + + static final _getAutoPerformanceTracingEnabled = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final boolean getAutoPerformanceTracingEnabled()` + bool getAutoPerformanceTracingEnabled() { + return _getAutoPerformanceTracingEnabled(reference.pointer, + _id_getAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr) + .boolean; + } + static final _id_privateSentryGetReplayIntegration = _class.instanceMethodId( r'privateSentryGetReplayIntegration', r'()Lio/sentry/android/replay/ReplayIntegration;', From a2fda2c27e9493d0950fcd07f4e4f524342a5e2b Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 14 Oct 2025 11:54:01 +0200 Subject: [PATCH 54/78] Update --- .../sentry_flutter/SentryFlutterPlugin.swift | 8 +++++--- .../lib/src/native/cocoa/sentry_native_cocoa.dart | 14 +------------- .../lib/src/native/java/sentry_native_java.dart | 13 +------------ 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 6e8643b993..957db3974d 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -624,9 +624,11 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { } @objc public class func addBreadcrumbAsBytes(_ breadcrumbBytes: NSData) { - guard let breadcrumbString = String(data: breadcrumbBytes as Data, encoding: .utf8), - let jsonData = breadcrumbString.data(using: .utf8), - let breadcrumbDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else { + guard let breadcrumbDict = try? JSONSerialization.jsonObject( + with: breadcrumbBytes as Data, + options: [] + ) as? [String: Any] else { + print("addBreadcrumb failed in native cocoa: could not parse bytes") return } let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumbDict) diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index bfcb23755a..b578b5c42c 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -203,19 +203,7 @@ class SentryNativeCocoa extends SentryNativeChannel { @override Future addBreadcrumb(Breadcrumb breadcrumb) async { tryCatchSync('addBreadcrumb', () { - // Normalize breadcrumb data like the method channel does - final normalizedBreadcrumb = Breadcrumb( - message: breadcrumb.message, - category: breadcrumb.category, - data: normalizeMap(breadcrumb.data), - level: breadcrumb.level, - type: breadcrumb.type, - timestamp: breadcrumb.timestamp, - // ignore: invalid_use_of_internal_member - unknown: breadcrumb.unknown, - ); - - final jsonString = json.encode(normalizedBreadcrumb.toJson()); + final jsonString = json.encode(breadcrumb.toJson()); final bytes = utf8.encode(jsonString); final nsData = bytes.toNSData(); diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index d183a46128..3284b34a50 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -228,18 +228,7 @@ class SentryNativeJava extends SentryNativeChannel { JByteArray? breadcrumbBytes; tryCatchSync('addBreadcrumb', () { - final normalizedBreadcrumb = Breadcrumb( - message: breadcrumb.message, - category: breadcrumb.category, - data: normalizeMap(breadcrumb.data), - level: breadcrumb.level, - type: breadcrumb.type, - timestamp: breadcrumb.timestamp, - // ignore: invalid_use_of_internal_member - unknown: breadcrumb.unknown, - ); - - final jsonString = json.encode(normalizedBreadcrumb.toJson()); + final jsonString = json.encode(breadcrumb.toJson()); final bytes = utf8.encode(jsonString); breadcrumbBytes = JByteArray.from(bytes); From 1ffba6f68752011e6db340110119fbbe6d58d701 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 14 Oct 2025 12:51:56 +0200 Subject: [PATCH 55/78] Update --- .../Sources/sentry_flutter/SentryFlutterPlugin.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 6629269211..a3babc3c43 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -535,6 +535,7 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { if let data = try? JSONSerialization.data(withJSONObject: item, options: []) { return data as NSData } + print("Failed to load native app start as bytes") return nil #else return nil @@ -570,6 +571,7 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { if let data = try? JSONSerialization.data(withJSONObject: serializedImages, options: []) { return data as NSData } + print("Failed to load debug images as bytes") return nil } @@ -655,6 +657,7 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { if let data = try? JSONSerialization.data(withJSONObject: infos, options: []) { return data as NSData } + print("Failed to load contexts as bytes") return nil } } From 3af23cc987153bb5ff77795ac793c7a7706c6bf2 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 14:07:07 +0200 Subject: [PATCH 56/78] Update --- packages/flutter/ffi-cocoa.yaml | 8 ++ .../sentry_flutter/SentryFlutterPlugin.swift | 12 --- .../sentry_flutter_objc/SentryFlutterPlugin.h | 3 - .../flutter/lib/src/native/cocoa/binding.dart | 81 +++++++++++++------ .../src/native/cocoa/sentry_native_cocoa.dart | 20 ++--- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/packages/flutter/ffi-cocoa.yaml b/packages/flutter/ffi-cocoa.yaml index 72cf5a88e0..bc1a60cdcf 100644 --- a/packages/flutter/ffi-cocoa.yaml +++ b/packages/flutter/ffi-cocoa.yaml @@ -19,8 +19,16 @@ objc-interfaces: - PrivateSentrySDKOnly - SentryId - SentryFlutterPlugin + - SentrySDK module: 'SentryId': 'Sentry' + 'SentrySDK': 'Sentry' + member-filter: + SentrySDK: + include: + - 'crash' + - 'pauseAppHangTracking' + - 'resumeAppHangTracking' preamble: | // ignore_for_file: type=lint, unused_element diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index a1b5576d54..3aa5f13e8b 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -519,18 +519,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { #endif } - @objc public class func nativeCrash() { - SentrySDK.crash() - } - - @objc public class func pauseAppHangTracking() { - SentrySDK.pauseAppHangTracking() - } - - @objc public class func resumeAppHangTracking() { - SentrySDK.resumeAppHangTracking() - } - @objc(loadDebugImagesAsBytes:) public class func loadDebugImagesAsBytes(instructionAddresses: Set) -> NSData? { var debugImages: [DebugMeta] = [] diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index 61af58310d..6f2e25eb54 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -8,8 +8,5 @@ + (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; -+ (void)nativeCrash; -+ (void)pauseAppHangTracking; -+ (void)resumeAppHangTracking; @end #endif diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 73d693b44d..d39050185c 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1120,15 +1120,8 @@ class SentryId$1 extends objc.NSObject { factory SentryId$1() => new$(); } -late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); -late final _sel_getDisplayRefreshRate = - objc.registerName("getDisplayRefreshRate"); -late final _sel_fetchNativeAppStartAsBytes = - objc.registerName("fetchNativeAppStartAsBytes"); -late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); -late final _sel_loadDebugImagesAsBytes_ = - objc.registerName("loadDebugImagesAsBytes:"); -late final _sel_nativeCrash = objc.registerName("nativeCrash"); +late final _class_SentrySDK = objc.getClass("Sentry.SentrySDK"); +late final _sel_crash = objc.registerName("crash"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -1142,6 +1135,59 @@ late final _sel_pauseAppHangTracking = late final _sel_resumeAppHangTracking = objc.registerName("resumeAppHangTracking"); +/// The main entry point for the Sentry SDK. +/// We recommend using start(configureOptions:) to initialize Sentry. +class SentrySDK extends objc.NSObject { + SentrySDK._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [SentrySDK] that points to the same underlying object as [other]. + SentrySDK.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [SentrySDK] that wraps the given raw object pointer. + SentrySDK.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [SentrySDK]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_19nvye5( + obj.ref.pointer, _sel_isKindOfClass_, _class_SentrySDK); + } + + /// This forces a crash, useful to test the SentryCrash integration. + /// note: + /// The SDK can’t report a crash when a debugger is attached. Your application needs to run + /// without a debugger attached to capture the crash and send it to Sentry the next time you launch + /// your application. + static void crash() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_crash); + } + + /// Pauses sending detected app hangs to Sentry. + /// This method doesn’t close the detection of app hangs. Instead, the app hang detection + /// will ignore detected app hangs until you call resumeAppHangTracking. + static void pauseAppHangTracking() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_pauseAppHangTracking); + } + + /// Resumes sending detected app hangs to Sentry. + static void resumeAppHangTracking() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_resumeAppHangTracking); + } +} + +late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); +late final _sel_getDisplayRefreshRate = + objc.registerName("getDisplayRefreshRate"); +late final _sel_fetchNativeAppStartAsBytes = + objc.registerName("fetchNativeAppStartAsBytes"); +late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); +late final _sel_loadDebugImagesAsBytes_ = + objc.registerName("loadDebugImagesAsBytes:"); + /// SentryFlutterPlugin class SentryFlutterPlugin extends objc.NSObject { SentryFlutterPlugin._(ffi.Pointer pointer, @@ -1199,23 +1245,6 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSData.castFromPointer(_ret, retain: true, release: true); } - /// nativeCrash - static void nativeCrash() { - _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash); - } - - /// pauseAppHangTracking - static void pauseAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_pauseAppHangTracking); - } - - /// resumeAppHangTracking - static void resumeAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_resumeAppHangTracking); - } - /// init SentryFlutterPlugin init() { objc.checkOsVersionInternal('SentryFlutterPlugin.init', diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 097b26bc30..a56347c027 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -180,21 +180,15 @@ class SentryNativeCocoa extends SentryNativeChannel { ); @override - void nativeCrash() { - cocoa.SentryFlutterPlugin.nativeCrash(); - } + void nativeCrash() => cocoa.SentrySDK.crash(); @override - void pauseAppHangTracking() { - tryCatchSync('pauseAppHangTracking', () { - cocoa.SentryFlutterPlugin.pauseAppHangTracking(); - }); - } + void pauseAppHangTracking() => tryCatchSync('pauseAppHangTracking', () { + cocoa.SentrySDK.pauseAppHangTracking(); + }); @override - void resumeAppHangTracking() { - tryCatchSync('resumeAppHangTracking', () { - cocoa.SentryFlutterPlugin.resumeAppHangTracking(); - }); - } + void resumeAppHangTracking() => tryCatchSync('resumeAppHangTracking', () { + cocoa.SentrySDK.resumeAppHangTracking(); + }); } From 7538a1cb2924eabcd42571dbe01791001efd051e Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 14:09:25 +0200 Subject: [PATCH 57/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 2 +- .../flutter/lib/src/native/java/binding.dart | 52 +++++++++++++------ .../src/native/java/sentry_native_java.dart | 4 +- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 9e675e1bb5..2690de132f 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -292,7 +292,7 @@ class SentryFlutterPlugin : @Suppress("unused") // Used by native/jni bindings @JvmStatic - fun nativeCrash() { + fun crash() { val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") val mainThread = Looper.getMainLooper().thread mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception) diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index f9730b691a..03fbaae69c 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1279,6 +1279,31 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { /// The type which includes information such as the signature of this class. static const nullableType = $SentryFlutterPlugin$Companion$NullableType(); static const type = $SentryFlutterPlugin$Companion$Type(); + static final _id_getAutoPerformanceTracingEnabled = _class.instanceMethodId( + r'getAutoPerformanceTracingEnabled', + r'()Z', + ); + + static final _getAutoPerformanceTracingEnabled = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public final boolean getAutoPerformanceTracingEnabled()` + bool getAutoPerformanceTracingEnabled() { + return _getAutoPerformanceTracingEnabled(reference.pointer, + _id_getAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr) + .boolean; + } + static final _id_privateSentryGetReplayIntegration = _class.instanceMethodId( r'privateSentryGetReplayIntegration', r'()Lio/sentry/android/replay/ReplayIntegration;', @@ -1305,12 +1330,12 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } - static final _id_nativeCrash = _class.instanceMethodId( - r'nativeCrash', + static final _id_crash = _class.instanceMethodId( + r'crash', r'()V', ); - static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + static final _crash = jni$_.ProtectedJniExtensions.lookup< jni$_.NativeFunction< jni$_.JThrowablePtr Function( jni$_.Pointer, @@ -1322,10 +1347,9 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { jni$_.JMethodIDPtr, )>(); - /// from: `public final void nativeCrash()` - void nativeCrash() { - _nativeCrash(reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) - .check(); + /// from: `public final void crash()` + void crash() { + _crash(reference.pointer, _id_crash as jni$_.JMethodIDPtr).check(); } static final _id_getDisplayRefreshRate = _class.instanceMethodId( @@ -1839,12 +1863,12 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } - static final _id_nativeCrash = _class.staticMethodId( - r'nativeCrash', + static final _id_crash = _class.staticMethodId( + r'crash', r'()V', ); - static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + static final _crash = jni$_.ProtectedJniExtensions.lookup< jni$_.NativeFunction< jni$_.JThrowablePtr Function( jni$_.Pointer, @@ -1856,11 +1880,9 @@ class SentryFlutterPlugin extends jni$_.JObject { jni$_.JMethodIDPtr, )>(); - /// from: `static public final void nativeCrash()` - static void nativeCrash() { - _nativeCrash( - _class.reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) - .check(); + /// from: `static public final void crash()` + static void crash() { + _crash(_class.reference.pointer, _id_crash as jni$_.JMethodIDPtr).check(); } static final _id_getDisplayRefreshRate = _class.staticMethodId( diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 817d51c00d..3893feab2f 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -200,9 +200,7 @@ class SentryNativeJava extends SentryNativeChannel { } @override - void nativeCrash() { - native.SentryFlutterPlugin.Companion.nativeCrash(); - } + void nativeCrash() => native.SentryFlutterPlugin.Companion.crash(); @override void pauseAppHangTracking() { From fce96c708a6c5c14770596941286c8b777c11c25 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 14:31:08 +0200 Subject: [PATCH 58/78] Update --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fefedbdabe..751275ef2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ # Changelog -## 9.7.0 +## Unreleased + +### Enhancements +- Move app hang and crash apis to use FFI/JNI ([#3289](https://github.com/getsentry/sentry-dart/pull/3289/)) + +## 9.7.0 ### Features From 958fa0d163a4a5c70e7d5c557e1d0de8546f416b Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 15:06:56 +0200 Subject: [PATCH 59/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutter.kt | 4 +++- .../main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 6 ++---- .../src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt | 2 +- .../Sources/sentry_flutter/SentryFlutterPlugin.swift | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt index e0e35b9ccc..19eec63247 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt @@ -21,6 +21,8 @@ class SentryFlutter { internal const val NATIVE_SDK = "sentry.native.android.flutter" } + var autoPerformanceTracingEnabled = false + fun updateOptions( options: SentryAndroidOptions, data: Map, @@ -105,7 +107,7 @@ class SentryFlutter { data.getIfNotNull("enableAutoPerformanceTracing") { enableAutoPerformanceTracing -> if (enableAutoPerformanceTracing) { - SentryFlutterPlugin.autoPerformanceTracingEnabled = true + autoPerformanceTracingEnabled = true } } diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index b4c4d71fd0..309581c984 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -41,7 +41,6 @@ class SentryFlutterPlugin : ActivityAware { private lateinit var channel: MethodChannel private lateinit var context: Context - private lateinit var sentryFlutter: SentryFlutter override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { pluginRegistrationTime = System.currentTimeMillis() @@ -282,8 +281,7 @@ class SentryFlutterPlugin : private var pluginRegistrationTime: Long? = null - var autoPerformanceTracingEnabled: Boolean = false - internal set + private lateinit var sentryFlutter: SentryFlutter private const val NATIVE_CRASH_WAIT_TIME = 500L @@ -319,7 +317,7 @@ class SentryFlutterPlugin : @Suppress("unused", "ReturnCount") // Used by native/jni bindings @JvmStatic fun fetchNativeAppStartAsBytes(): ByteArray? { - if (!autoPerformanceTracingEnabled) { + if (!sentryFlutter.autoPerformanceTracingEnabled) { return null } diff --git a/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt b/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt index 74c9214a6c..be7eacb873 100644 --- a/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt +++ b/packages/flutter/android/src/test/kotlin/io/sentry/flutter/SentryFlutterTest.kt @@ -62,7 +62,7 @@ class SentryFlutterTest { ) assertEquals("sentry.native.android.flutter", fixture.options.nativeSdkName) - assertEquals(true, SentryFlutterPlugin.autoPerformanceTracingEnabled) + assertEquals(true, sut.autoPerformanceTracingEnabled) assertEquals(9006, fixture.options.connectionTimeoutMillis) assertEquals(9007, fixture.options.readTimeoutMillis) diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index a3babc3c43..54fa3ccd26 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -456,7 +456,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { // https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm#L150 @objc public class func getDisplayRefreshRate() -> NSNumber? { let displayLink = CADisplayLink(target: self, selector: #selector(onDisplayLinkStatic(_:))) - displayLink.add(to: .main, forMode: .common) displayLink.isPaused = true let preferredFPS = displayLink.preferredFramesPerSecond From 84542aba130d90c21125de719968cceae1ac653e Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:01:55 +0200 Subject: [PATCH 60/78] Update --- .../sentry_flutter/SentryFlutterPlugin.swift | 24 ++++++++++++------- .../src/native/cocoa/sentry_native_cocoa.dart | 12 ++++++---- .../src/native/java/sentry_native_java.dart | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 54fa3ccd26..7d275bcca8 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -531,11 +531,13 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { "nativeSpanTimes": nativeSpanTimes ] - if let data = try? JSONSerialization.data(withJSONObject: item, options: []) { + do { + let data = try JSONSerialization.data(withJSONObject: item, options: []) return data as NSData + } catch { + print("Failed to load native app start as bytes: \(error)") + return nil } - print("Failed to load native app start as bytes") - return nil #else return nil #endif @@ -567,11 +569,13 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { } let serializedImages = debugImages.map { $0.serialize() } - if let data = try? JSONSerialization.data(withJSONObject: serializedImages, options: []) { + do { + let data = try JSONSerialization.data(withJSONObject: serializedImages, options: []) return data as NSData + } catch { + print("Failed to load debug images as bytes: \(error)") + return nil } - print("Failed to load debug images as bytes") - return nil } // swiftlint:disable:next cyclomatic_complexity @@ -653,11 +657,13 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { "sdk_name": "cocoapods:sentry-cocoa"] } - if let data = try? JSONSerialization.data(withJSONObject: infos, options: []) { + do { + let data = try JSONSerialization.data(withJSONObject: infos, options: []) return data as NSData + } catch { + print("Failed to load contexts as bytes: \(error)") + return nil } - print("Failed to load contexts as bytes") - return nil } } // swiftlint:enable type_body_length diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 0a8dc2ee4a..7d2926c393 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; @@ -118,7 +119,12 @@ class SentryNativeCocoa extends SentryNativeChannel { cocoa.SentryFlutterPlugin.loadContextsAsBytes(); if (contextsUtf8JsonBytes == null) return null; - final contexts = decodeUtf8JsonMap(contextsUtf8JsonBytes.toList()); + // Use Flutter's compute to decode the UTF-8 JSON off the main isolate. + final contexts = await compute, Map?>( + // top-level or static function required by compute; use decode helper wrapper + _decodeUtf8JsonMapEntryPoint, + contextsUtf8JsonBytes.toList(), + ); return contexts; } catch (exception, stackTrace) { options.log(SentryLevel.error, 'FFI: Failed to load contexts', @@ -160,9 +166,7 @@ class SentryNativeCocoa extends SentryNativeChannel { 'displayRefreshRate', () { final refreshRate = cocoa.SentryFlutterPlugin.getDisplayRefreshRate(); - if (refreshRate == null) return null; - - return refreshRate.intValue; + return refreshRate?.intValue; }, ); diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index b79d8b9db4..4679f2af43 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; From e744a5ec9580e4501d26edc9c29615c993a9a3ff Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:02:07 +0200 Subject: [PATCH 61/78] Update --- .../flutter/lib/src/native/cocoa/sentry_native_cocoa.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 7d2926c393..3abd2cb579 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -119,12 +119,7 @@ class SentryNativeCocoa extends SentryNativeChannel { cocoa.SentryFlutterPlugin.loadContextsAsBytes(); if (contextsUtf8JsonBytes == null) return null; - // Use Flutter's compute to decode the UTF-8 JSON off the main isolate. - final contexts = await compute, Map?>( - // top-level or static function required by compute; use decode helper wrapper - _decodeUtf8JsonMapEntryPoint, - contextsUtf8JsonBytes.toList(), - ); + final contexts = decodeUtf8JsonMap(contextsUtf8JsonBytes.toList()); return contexts; } catch (exception, stackTrace) { options.log(SentryLevel.error, 'FFI: Failed to load contexts', From 99126287aa83c5062f4217ac96bb51203734a8aa Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:05:49 +0200 Subject: [PATCH 62/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 309581c984..967f110f87 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -413,8 +413,13 @@ class SentryFlutterPlugin : options, currentScope, ) - val json = JSONObject(serializedScope).toString() - return json.toByteArray(Charsets.UTF_8) + try { + val json = JSONObject(serializedScope).toString() + return json.toByteArray(Charsets.UTF_8) + } catch (e: Exception) { + Log.e("Sentry", "Failed to serialize scope", e) + return null + } } @Suppress("unused") // Used by native/jni bindings @@ -436,8 +441,13 @@ class SentryFlutterPlugin : .serialize() } - val json = JSONArray(debugImages).toString() - return json.toByteArray(Charsets.UTF_8) + try { + val json = JSONArray(debugImages).toString() + return json.toByteArray(Charsets.UTF_8) + } catch (e: Exception) { + Log.e("Sentry", "Failed to serialize debug images", e) + return null + } } private fun List?.serialize() = this?.map { it.serialize() } From dee9256d76aa72443233f85c1d0e6e9d352121c7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:13:48 +0200 Subject: [PATCH 63/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 4 ++-- .../flutter/lib/src/native/cocoa/sentry_native_cocoa.dart | 2 -- packages/flutter/lib/src/native/java/sentry_native_java.dart | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 967f110f87..614d5739ff 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -314,7 +314,7 @@ class SentryFlutterPlugin : return refreshRate } - @Suppress("unused", "ReturnCount") // Used by native/jni bindings + @Suppress("unused", "ReturnCount", "TooGenericExceptionCaught") // Used by native/jni bindings @JvmStatic fun fetchNativeAppStartAsBytes(): ByteArray? { if (!sentryFlutter.autoPerformanceTracingEnabled) { @@ -398,7 +398,7 @@ class SentryFlutterPlugin : @JvmStatic fun getApplicationContext(): Context? = applicationContext - @Suppress("unused") // Used by native/jni bindings + @Suppress("unused", "ReturnCount", "TooGenericExceptionCaught") // Used by native/jni bindings @JvmStatic fun loadContextsAsBytes(): ByteArray? { val options = ScopesAdapter.getInstance().options diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 3abd2cb579..dfda2f4853 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; -import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; import '../../../sentry_flutter.dart'; diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 4679f2af43..2362b5e13c 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,9 +1,7 @@ import 'dart:async'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:jni/jni.dart'; -import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; From ab2f4e9cffa81c7d39eab9f807d008d30a4e6055 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:15:31 +0200 Subject: [PATCH 64/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 614d5739ff..da755ecd10 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -422,7 +422,7 @@ class SentryFlutterPlugin : } } - @Suppress("unused") // Used by native/jni bindings + @Suppress("unused", "TooGenericExceptionCaught") // Used by native/jni bindings @JvmStatic fun loadDebugImagesAsBytes(addresses: Set): ByteArray? { val options = ScopesAdapter.getInstance().options as SentryAndroidOptions From ed51094f9f274aa70356aaab0f329aa15e21d9b5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:24:25 +0200 Subject: [PATCH 65/78] Update --- packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart | 3 ++- packages/flutter/lib/src/native/java/sentry_native_java.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index dfda2f4853..2683ef14f0 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,5 +1,6 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'dart:typed_data'; +import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; import '../../../sentry_flutter.dart'; diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 2362b5e13c..a71233f89a 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:jni/jni.dart'; +import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; From 0d378b325e31668faddf99a6676aa9a310d2b4dd Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 16 Oct 2025 16:24:43 +0200 Subject: [PATCH 66/78] Update --- packages/flutter/lib/src/native/java/sentry_native_java.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index a71233f89a..b79d8b9db4 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; From d5641dc985d23ad2bb705b78107fe64f88d0bd8f Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Fri, 17 Oct 2025 10:14:59 +0200 Subject: [PATCH 67/78] Update --- packages/dart/lib/src/scope_observer.dart | 4 +- packages/flutter/ffi-cocoa.yaml | 7 + .../flutter/lib/src/native/cocoa/binding.dart | 298 ++++++++++++++++-- .../lib/src/native/cocoa/binding.dart.m | 3 + .../src/native/cocoa/sentry_native_cocoa.dart | 52 ++- .../flutter/lib/src/native/java/binding.dart | 25 -- .../lib/src/native/native_scope_observer.dart | 4 +- .../lib/src/native/sentry_native_channel.dart | 4 +- 8 files changed, 316 insertions(+), 81 deletions(-) diff --git a/packages/dart/lib/src/scope_observer.dart b/packages/dart/lib/src/scope_observer.dart index 09af0c46a0..7d657cd26c 100644 --- a/packages/dart/lib/src/scope_observer.dart +++ b/packages/dart/lib/src/scope_observer.dart @@ -7,8 +7,8 @@ abstract class ScopeObserver { Future setContexts(String key, dynamic value); Future removeContexts(String key); Future setUser(SentryUser? user); - Future addBreadcrumb(Breadcrumb breadcrumb); - Future clearBreadcrumbs(); + FutureOr addBreadcrumb(Breadcrumb breadcrumb); + FutureOr clearBreadcrumbs(); Future setExtra(String key, dynamic value); Future removeExtra(String key); Future setTag(String key, String value); diff --git a/packages/flutter/ffi-cocoa.yaml b/packages/flutter/ffi-cocoa.yaml index bc1a60cdcf..e0cf790183 100644 --- a/packages/flutter/ffi-cocoa.yaml +++ b/packages/flutter/ffi-cocoa.yaml @@ -6,6 +6,7 @@ headers: entry-points: - ./temp/Sentry.framework/PrivateHeaders/PrivateSentrySDKOnly.h - ./temp/Sentry.framework/Headers/Sentry-Swift.h + - ./temp/Sentry.framework/Headers/SentryScope.h - ./ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h compiler-opts: - -DSENTRY_TARGET_PROFILING_SUPPORTED=1 @@ -19,6 +20,7 @@ objc-interfaces: - PrivateSentrySDKOnly - SentryId - SentryFlutterPlugin + - SentryScope - SentrySDK module: 'SentryId': 'Sentry' @@ -29,6 +31,11 @@ objc-interfaces: - 'crash' - 'pauseAppHangTracking' - 'resumeAppHangTracking' + - 'configureScope:' + - 'addBreadcrumb:' + SentryScope: + include: + - 'clearBreadcrumbs' preamble: | // ignore_for_file: type=lint, unused_element diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index a0f2461826..dde21b5522 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1120,19 +1120,31 @@ class SentryId$1 extends objc.NSObject { factory SentryId$1() => new$(); } -late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); -late final _sel_getDisplayRefreshRate = - objc.registerName("getDisplayRefreshRate"); -late final _sel_fetchNativeAppStartAsBytes = - objc.registerName("fetchNativeAppStartAsBytes"); -late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); -late final _sel_loadDebugImagesAsBytes_ = - objc.registerName("loadDebugImagesAsBytes:"); -late final _sel_addBreadcrumbAsBytes_ = - objc.registerName("addBreadcrumbAsBytes:"); -late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs"); late final _class_SentrySDK = objc.getClass("Sentry.SentrySDK"); -late final _sel_crash = objc.registerName("crash"); +late final _sel_addBreadcrumb_ = objc.registerName("addBreadcrumb:"); +late final _class_SentryScope = objc.getClass("SentryScope"); + +/// WARNING: SentrySerializable is a stub. To generate bindings for this class, include +/// SentrySerializable in your config's objc-protocols list. +/// +/// SentrySerializable +interface class SentrySerializable extends objc.ObjCProtocolBase + implements objc.NSObjectProtocol { + SentrySerializable._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super(pointer, retain: retain, release: release); + + /// Constructs a [SentrySerializable] that points to the same underlying object as [other]. + SentrySerializable.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [SentrySerializable] that wraps the given raw object pointer. + SentrySerializable.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); +} + +late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -1141,7 +1153,226 @@ final _objc_msgSend_1pl9qdv = objc.msgSendPointer .asFunction< void Function( ffi.Pointer, ffi.Pointer)>(); -late final _sel_nativeCrash = objc.registerName("nativeCrash"); + +/// SentryScope +class SentryScope extends objc.NSObject implements SentrySerializable { + SentryScope._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [SentryScope] that points to the same underlying object as [other]. + SentryScope.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [SentryScope] that wraps the given raw object pointer. + SentryScope.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [SentryScope]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_19nvye5( + obj.ref.pointer, _sel_isKindOfClass_, _class_SentryScope); + } + + /// Clears all breadcrumbs in the scope + void clearBreadcrumbs() { + _objc_msgSend_1pl9qdv(this.ref.pointer, _sel_clearBreadcrumbs); + } +} + +void _ObjCBlock_ffiVoid_SentryScope_fnPtrTrampoline( + ffi.Pointer block, + ffi.Pointer arg0) => + block.ref.target + .cast< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer arg0)>>() + .asFunction)>()(arg0); +ffi.Pointer _ObjCBlock_ffiVoid_SentryScope_fnPtrCallable = + ffi.Pointer.fromFunction< + ffi.Void Function(ffi.Pointer, + ffi.Pointer)>( + _ObjCBlock_ffiVoid_SentryScope_fnPtrTrampoline) + .cast(); +void _ObjCBlock_ffiVoid_SentryScope_closureTrampoline( + ffi.Pointer block, + ffi.Pointer arg0) => + (objc.getBlockClosure(block) as void Function( + ffi.Pointer))(arg0); +ffi.Pointer _ObjCBlock_ffiVoid_SentryScope_closureCallable = + ffi.Pointer.fromFunction< + ffi.Void Function(ffi.Pointer, + ffi.Pointer)>( + _ObjCBlock_ffiVoid_SentryScope_closureTrampoline) + .cast(); +void _ObjCBlock_ffiVoid_SentryScope_listenerTrampoline( + ffi.Pointer block, ffi.Pointer arg0) { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer))(arg0); + objc.objectRelease(block.cast()); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)> + _ObjCBlock_ffiVoid_SentryScope_listenerCallable = ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_SentryScope_listenerTrampoline) + ..keepIsolateAlive = false; +void _ObjCBlock_ffiVoid_SentryScope_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0) { + try { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer))(arg0); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } +} + +ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)> + _ObjCBlock_ffiVoid_SentryScope_blockingCallable = ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.isolateLocal( + _ObjCBlock_ffiVoid_SentryScope_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)> + _ObjCBlock_ffiVoid_SentryScope_blockingListenerCallable = ffi + .NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_SentryScope_blockingTrampoline) + ..keepIsolateAlive = false; + +/// Construction methods for `objc.ObjCBlock`. +abstract final class ObjCBlock_ffiVoid_SentryScope { + /// Returns a block that wraps the given raw block pointer. + static objc.ObjCBlock castFromPointer( + ffi.Pointer pointer, + {bool retain = false, + bool release = false}) => + objc.ObjCBlock(pointer, + retain: retain, release: release); + + /// Creates a block from a C function pointer. + /// + /// This block must be invoked by native code running on the same thread as + /// the isolate that registered it. Invoking the block on the wrong thread + /// will result in a crash. + static objc.ObjCBlock fromFunctionPointer( + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer arg0)>> + ptr) => + objc.ObjCBlock( + objc.newPointerBlock( + _ObjCBlock_ffiVoid_SentryScope_fnPtrCallable, ptr.cast()), + retain: false, + release: true); + + /// Creates a block from a Dart function. + /// + /// This block must be invoked by native code running on the same thread as + /// the isolate that registered it. Invoking the block on the wrong thread + /// will result in a crash. + /// + /// If `keepIsolateAlive` is true, this block will keep this isolate alive + /// until it is garbage collected by both Dart and ObjC. + static objc.ObjCBlock fromFunction( + void Function(SentryScope) fn, + {bool keepIsolateAlive = true}) => + objc.ObjCBlock( + objc.newClosureBlock( + _ObjCBlock_ffiVoid_SentryScope_closureCallable, + (ffi.Pointer arg0) => fn( + SentryScope.castFromPointer(arg0, + retain: true, release: true)), + keepIsolateAlive), + retain: false, + release: true); + + /// Creates a listener block from a Dart function. + /// + /// This is based on FFI's NativeCallable.listener, and has the same + /// capabilities and limitations. This block can be invoked from any thread, + /// but only supports void functions, and is not run synchronously. See + /// NativeCallable.listener for more details. + /// + /// If `keepIsolateAlive` is true, this block will keep this isolate alive + /// until it is garbage collected by both Dart and ObjC. + static objc.ObjCBlock listener( + void Function(SentryScope) fn, + {bool keepIsolateAlive = true}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_SentryScope_listenerCallable.nativeFunction.cast(), + (ffi.Pointer arg0) => + fn(SentryScope.castFromPointer(arg0, retain: false, release: true)), + keepIsolateAlive); + final wrapper = _SentryCocoa_wrapListenerBlock_xtuoz7(raw); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock(wrapper, + retain: false, release: true); + } + + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// If `keepIsolateAlive` is true, this block will keep this isolate alive + /// until it is garbage collected by both Dart and ObjC. If the owner isolate + /// has shut down, and the block is invoked by native code, it may block + /// indefinitely, or have other undefined behavior. + static objc.ObjCBlock blocking( + void Function(SentryScope) fn, + {bool keepIsolateAlive = true}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_SentryScope_blockingCallable.nativeFunction.cast(), + (ffi.Pointer arg0) => + fn(SentryScope.castFromPointer(arg0, retain: false, release: true)), + keepIsolateAlive); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_SentryScope_blockingListenerCallable.nativeFunction + .cast(), + (ffi.Pointer arg0) => + fn(SentryScope.castFromPointer(arg0, retain: false, release: true)), + keepIsolateAlive); + final wrapper = _SentryCocoa_wrapBlockingBlock_xtuoz7( + raw, rawListener, objc.objCContext); + objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); + return objc.ObjCBlock(wrapper, + retain: false, release: true); + } +} + +/// Call operator for `objc.ObjCBlock`. +extension ObjCBlock_ffiVoid_SentryScope_CallExtension + on objc.ObjCBlock { + void call(SentryScope arg0) => ref.pointer.ref.invoke + .cast< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer block, + ffi.Pointer arg0)>>() + .asFunction< + void Function(ffi.Pointer, + ffi.Pointer)>()(ref.pointer, arg0.ref.pointer); +} + +late final _sel_configureScope_ = objc.registerName("configureScope:"); +late final _sel_crash = objc.registerName("crash"); late final _sel_pauseAppHangTracking = objc.registerName("pauseAppHangTracking"); late final _sel_resumeAppHangTracking = @@ -1169,6 +1400,23 @@ class SentrySDK extends objc.NSObject { obj.ref.pointer, _sel_isKindOfClass_, _class_SentrySDK); } + /// Adds a Breadcrumb to the current Scope of the current Hub. If the total number of breadcrumbs + /// exceeds the SentryOptions.maxBreadcrumbs the SDK removes the oldest breadcrumb. + /// \param crumb The Breadcrumb to add to the current Scope of the current Hub. + static void addBreadcrumb(SentryBreadcrumb crumb) { + _objc_msgSend_xtuoz7( + _class_SentrySDK, _sel_addBreadcrumb_, crumb.ref.pointer); + } + + /// Use this method to modify the current Scope of the current Hub. The SDK uses the Scope to attach + /// contextual data to events. + /// \param callback The callback for configuring the current Scope of the current Hub. + static void configureScope( + objc.ObjCBlock callback) { + _objc_msgSend_f167m6( + _class_SentrySDK, _sel_configureScope_, callback.ref.pointer); + } + /// This forces a crash, useful to test the SentryCrash integration. /// note: /// The SDK can’t report a crash when a debugger is attached. Your application needs to run @@ -1199,6 +1447,8 @@ late final _sel_fetchNativeAppStartAsBytes = late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); +late final _sel_addBreadcrumbAsBytes_ = + objc.registerName("addBreadcrumbAsBytes:"); /// SentryFlutterPlugin class SentryFlutterPlugin extends objc.NSObject { @@ -1263,28 +1513,6 @@ class SentryFlutterPlugin extends objc.NSObject { breadcrumbBytes.ref.pointer); } - /// clearBreadcrumbs - static void clearBreadcrumbs() { - _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_clearBreadcrumbs); - } - - /// nativeCrash - static void nativeCrash() { - _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash); - } - - /// pauseAppHangTracking - static void pauseAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_pauseAppHangTracking); - } - - /// resumeAppHangTracking - static void resumeAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_resumeAppHangTracking); - } - /// init SentryFlutterPlugin init() { objc.checkOsVersionInternal('SentryFlutterPlugin.init', diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart.m b/packages/flutter/lib/src/native/cocoa/binding.dart.m index 488e6027b2..cab4800aae 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart.m +++ b/packages/flutter/lib/src/native/cocoa/binding.dart.m @@ -3,6 +3,7 @@ #import #import "../../../../temp/Sentry.framework/PrivateHeaders/PrivateSentrySDKOnly.h" #import "../../../../temp/Sentry.framework/Headers/Sentry-Swift.h" +#import "../../../../temp/Sentry.framework/Headers/SentryScope.h" #import "../../../../ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h" #if !__has_feature(objc_arc) @@ -79,6 +80,8 @@ ListenerTrampoline _SentryCocoa_wrapBlockingBlock_xtuoz7( id _SentryCocoa_protocolTrampoline_1mbt9g9(id target, void * sel) { return ((ProtocolTrampoline)((id (*)(id, SEL, SEL))objc_msgSend)(target, @selector(getDOBJCDartProtocolMethodForSelector:), sel))(sel); } + +Protocol* _SentryCocoa_SentrySerializable(void) { return @protocol(SentrySerializable); } #undef BLOCKING_BLOCK_IMPL #pragma clang diagnostic pop diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index beac070302..08bbf339d4 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -188,29 +188,51 @@ class SentryNativeCocoa extends SentryNativeChannel { }); @override - void resumeAppHangTracking() { - tryCatchSync('resumeAppHangTracking', () { - cocoa.SentryFlutterPlugin.resumeAppHangTracking(); - }); - } - - @override - Future addBreadcrumb(Breadcrumb breadcrumb) async { - tryCatchSync('addBreadcrumb', () { - final jsonString = json.encode(breadcrumb.toJson()); - final bytes = utf8.encode(jsonString); - final nsData = bytes.toNSData(); + void addBreadcrumb(Breadcrumb breadcrumb) => + tryCatchSync('addBreadcrumb', () { + final nativeBreadcrumb = + cocoa.PrivateSentrySDKOnly.breadcrumbWithDictionary( + deepConvertMapNonNull(breadcrumb.toJson()).toNSDictionary()); + cocoa.SentrySDK.addBreadcrumb(nativeBreadcrumb); + }); - cocoa.SentryFlutterPlugin.addBreadcrumbAsBytes(nsData); + Map deepConvertMapNonNull(Map input) { + return input.entries.where((e) => e.value != null).map((e) { + final key = e.key as Object; + final value = e.value; + if (value is Map) { + return MapEntry(key, deepConvertMapNonNull(value)); + } else if (value is List) { + return MapEntry( + key, + value + .where((item) => item != null) + .map((item) => item is Map + ? deepConvertMapNonNull(item) + : item as Object) + .toList(), + ); + } else { + return MapEntry(key, value as Object); + } + }).fold>({}, (map, entry) { + map[entry.key] = entry.value; + return map; }); } @override - Future clearBreadcrumbs() async { + void clearBreadcrumbs() { tryCatchSync('clearBreadcrumbs', () { - cocoa.SentryFlutterPlugin.clearBreadcrumbs(); + cocoa.SentrySDK.configureScope( + cocoa.ObjCBlock_ffiVoid_SentryScope.fromFunction( + (cocoa.SentryScope scope) { + scope.clearBreadcrumbs(); + })); }); } + + @override void resumeAppHangTracking() => tryCatchSync('resumeAppHangTracking', () { cocoa.SentrySDK.resumeAppHangTracking(); }); diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index bb068c81d5..71b5e43438 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1279,31 +1279,6 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { /// The type which includes information such as the signature of this class. static const nullableType = $SentryFlutterPlugin$Companion$NullableType(); static const type = $SentryFlutterPlugin$Companion$Type(); - static final _id_getAutoPerformanceTracingEnabled = _class.instanceMethodId( - r'getAutoPerformanceTracingEnabled', - r'()Z', - ); - - static final _getAutoPerformanceTracingEnabled = - jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JniResult Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>>('globalEnv_CallBooleanMethod') - .asFunction< - jni$_.JniResult Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>(); - - /// from: `public final boolean getAutoPerformanceTracingEnabled()` - bool getAutoPerformanceTracingEnabled() { - return _getAutoPerformanceTracingEnabled(reference.pointer, - _id_getAutoPerformanceTracingEnabled as jni$_.JMethodIDPtr) - .boolean; - } - static final _id_privateSentryGetReplayIntegration = _class.instanceMethodId( r'privateSentryGetReplayIntegration', r'()Lio/sentry/android/replay/ReplayIntegration;', diff --git a/packages/flutter/lib/src/native/native_scope_observer.dart b/packages/flutter/lib/src/native/native_scope_observer.dart index 8ba1a18947..468442e5fe 100644 --- a/packages/flutter/lib/src/native/native_scope_observer.dart +++ b/packages/flutter/lib/src/native/native_scope_observer.dart @@ -39,12 +39,12 @@ class NativeScopeObserver implements ScopeObserver { } @override - Future addBreadcrumb(Breadcrumb breadcrumb) async { + FutureOr addBreadcrumb(Breadcrumb breadcrumb) async { await _native.addBreadcrumb(breadcrumb); } @override - Future clearBreadcrumbs() async { + FutureOr clearBreadcrumbs() async { await _native.clearBreadcrumbs(); } diff --git a/packages/flutter/lib/src/native/sentry_native_channel.dart b/packages/flutter/lib/src/native/sentry_native_channel.dart index 4cc4c6dec0..7c8fb5e9a0 100644 --- a/packages/flutter/lib/src/native/sentry_native_channel.dart +++ b/packages/flutter/lib/src/native/sentry_native_channel.dart @@ -150,12 +150,12 @@ class SentryNativeChannel } @override - Future addBreadcrumb(Breadcrumb breadcrumb) async { + FutureOr addBreadcrumb(Breadcrumb breadcrumb) async { assert(false, "addBreadcrumb should not be used through method channels."); } @override - Future clearBreadcrumbs() async { + FutureOr clearBreadcrumbs() async { assert( false, "clearBreadcrumbs should not be used through method channels."); } From 8eb51530a85c60ba0b93563fe02a9279bb57ee33 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Fri, 17 Oct 2025 10:55:58 +0200 Subject: [PATCH 68/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 6 - packages/flutter/ffi-jni.yaml | 1 + .../sentry_flutter_objc/include/ns_number.h | 14 + .../Sources/sentry_flutter_objc/ns_number.m | 12 + .../src/native/cocoa/sentry_native_cocoa.dart | 42 +- .../flutter/lib/src/native/java/binding.dart | 2694 +++++++++++++++++ .../src/native/java/sentry_native_java.dart | 10 +- 7 files changed, 2745 insertions(+), 34 deletions(-) create mode 100644 packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h create mode 100644 packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index b227b434c9..fc7e5fd1c5 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -453,12 +453,6 @@ class SentryFlutterPlugin : Sentry.addBreadcrumb(breadcrumb) } - @Suppress("unused") // Used by native/jni bindings - @JvmStatic - fun clearBreadcrumbs() { - Sentry.clearBreadcrumbs() - } - private fun List?.serialize() = this?.map { it.serialize() } private fun DebugImage.serialize() = diff --git a/packages/flutter/ffi-jni.yaml b/packages/flutter/ffi-jni.yaml index cf7f4e0e6e..d238eeb65f 100644 --- a/packages/flutter/ffi-jni.yaml +++ b/packages/flutter/ffi-jni.yaml @@ -16,4 +16,5 @@ classes: - io.sentry.android.core.InternalSentrySdk - io.sentry.android.replay.ReplayIntegration - io.sentry.flutter.SentryFlutterPlugin + - io.sentry.Sentry - android.graphics.Bitmap diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h new file mode 100644 index 0000000000..bed74f400f --- /dev/null +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h @@ -0,0 +1,14 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef OBJECTIVE_C_SRC_NS_NUMBER_H_ +#define OBJECTIVE_C_SRC_NS_NUMBER_H_ + +#import + +@interface NSNumber (NSNumberIsFloat) +@property (readonly) bool isFloat; +@end + +#endif // OBJECTIVE_C_SRC_NS_NUMBER_H_ diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m new file mode 100644 index 0000000000..076778887b --- /dev/null +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m @@ -0,0 +1,12 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#import "ns_number.h" + +@implementation NSNumber (NSNumberIsFloat) +-(bool)isFloat { + const char *t = [self objCType]; + return strcmp(t, @encode(float)) == 0 || strcmp(t, @encode(double)) == 0; +} +@end diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 08bbf339d4..6e7fcc0cff 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -197,28 +197,26 @@ class SentryNativeCocoa extends SentryNativeChannel { }); Map deepConvertMapNonNull(Map input) { - return input.entries.where((e) => e.value != null).map((e) { - final key = e.key as Object; - final value = e.value; - if (value is Map) { - return MapEntry(key, deepConvertMapNonNull(value)); - } else if (value is List) { - return MapEntry( - key, - value - .where((item) => item != null) - .map((item) => item is Map - ? deepConvertMapNonNull(item) - : item as Object) - .toList(), - ); - } else { - return MapEntry(key, value as Object); - } - }).fold>({}, (map, entry) { - map[entry.key] = entry.value; - return map; - }); + final out = {}; + + for (final entry in input.entries) { + final value = entry.value; + if (value == null) continue; + + out[entry.key] = switch (value) { + Map m => deepConvertMapNonNull(m), + List l => [ + for (final e in l) + if (e != null) + e is Map + ? deepConvertMapNonNull(e) + : e as Object + ], + _ => value as Object, + }; + } + + return out; } @override diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index 71b5e43438..0d8c9d1417 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -2166,6 +2166,2700 @@ final class $SentryFlutterPlugin$Type } } +/// from: `io.sentry.Sentry$OptionsConfiguration` +class Sentry$OptionsConfiguration<$T extends jni$_.JObject?> + extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType> $type; + + @jni$_.internal + final jni$_.JObjType<$T> T; + + @jni$_.internal + Sentry$OptionsConfiguration.fromReference( + this.T, + jni$_.JReference reference, + ) : $type = type<$T>(T), + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'io/sentry/Sentry$OptionsConfiguration'); + + /// The type which includes information such as the signature of this class. + static $Sentry$OptionsConfiguration$NullableType<$T> + nullableType<$T extends jni$_.JObject?>( + jni$_.JObjType<$T> T, + ) { + return $Sentry$OptionsConfiguration$NullableType<$T>( + T, + ); + } + + static $Sentry$OptionsConfiguration$Type<$T> type<$T extends jni$_.JObject?>( + jni$_.JObjType<$T> T, + ) { + return $Sentry$OptionsConfiguration$Type<$T>( + T, + ); + } + + static final _id_configure = _class.instanceMethodId( + r'configure', + r'(Lio/sentry/SentryOptions;)V', + ); + + static final _configure = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public abstract void configure(T sentryOptions)` + void configure( + $T sentryOptions, + ) { + final _$sentryOptions = sentryOptions?.reference ?? jni$_.jNullReference; + _configure(reference.pointer, _id_configure as jni$_.JMethodIDPtr, + _$sentryOptions.pointer) + .check(); + } + + /// Maps a specific port to the implemented interface. + static final core$_.Map _$impls = {}; + static jni$_.JObjectPtr _$invoke( + int port, + jni$_.JObjectPtr descriptor, + jni$_.JObjectPtr args, + ) { + return _$invokeMethod( + port, + jni$_.MethodInvocation.fromAddresses( + 0, + descriptor.address, + args.address, + ), + ); + } + + static final jni$_.Pointer< + jni$_.NativeFunction< + jni$_.JObjectPtr Function( + jni$_.Int64, jni$_.JObjectPtr, jni$_.JObjectPtr)>> + _$invokePointer = jni$_.Pointer.fromFunction(_$invoke); + + static jni$_.Pointer _$invokeMethod( + int $p, + jni$_.MethodInvocation $i, + ) { + try { + final $d = $i.methodDescriptor.toDartString(releaseOriginal: true); + final $a = $i.args; + if ($d == r'configure(Lio/sentry/SentryOptions;)V') { + _$impls[$p]!.configure( + $a![0]?.as(_$impls[$p]!.T, releaseOriginal: true), + ); + return jni$_.nullptr; + } + } catch (e) { + return jni$_.ProtectedJniExtensions.newDartException(e); + } + return jni$_.nullptr; + } + + static void implementIn<$T extends jni$_.JObject?>( + jni$_.JImplementer implementer, + $Sentry$OptionsConfiguration<$T> $impl, + ) { + late final jni$_.RawReceivePort $p; + $p = jni$_.RawReceivePort(($m) { + if ($m == null) { + _$impls.remove($p.sendPort.nativePort); + $p.close(); + return; + } + final $i = jni$_.MethodInvocation.fromMessage($m); + final $r = _$invokeMethod($p.sendPort.nativePort, $i); + jni$_.ProtectedJniExtensions.returnResult($i.result, $r); + }); + implementer.add( + r'io.sentry.Sentry$OptionsConfiguration', + $p, + _$invokePointer, + [ + if ($impl.configure$async) r'configure(Lio/sentry/SentryOptions;)V', + ], + ); + final $a = $p.sendPort.nativePort; + _$impls[$a] = $impl; + } + + factory Sentry$OptionsConfiguration.implement( + $Sentry$OptionsConfiguration<$T> $impl, + ) { + final $i = jni$_.JImplementer(); + implementIn($i, $impl); + return Sentry$OptionsConfiguration<$T>.fromReference( + $impl.T, + $i.implementReference(), + ); + } +} + +abstract base mixin class $Sentry$OptionsConfiguration< + $T extends jni$_.JObject?> { + factory $Sentry$OptionsConfiguration({ + required jni$_.JObjType<$T> T, + required void Function($T sentryOptions) configure, + bool configure$async, + }) = _$Sentry$OptionsConfiguration<$T>; + + jni$_.JObjType<$T> get T; + + void configure($T sentryOptions); + bool get configure$async => false; +} + +final class _$Sentry$OptionsConfiguration<$T extends jni$_.JObject?> + with $Sentry$OptionsConfiguration<$T> { + _$Sentry$OptionsConfiguration({ + required this.T, + required void Function($T sentryOptions) configure, + this.configure$async = false, + }) : _configure = configure; + + @core$_.override + final jni$_.JObjType<$T> T; + + final void Function($T sentryOptions) _configure; + final bool configure$async; + + void configure($T sentryOptions) { + return _configure(sentryOptions); + } +} + +final class $Sentry$OptionsConfiguration$NullableType<$T extends jni$_.JObject?> + extends jni$_.JObjType?> { + @jni$_.internal + final jni$_.JObjType<$T> T; + + @jni$_.internal + const $Sentry$OptionsConfiguration$NullableType( + this.T, + ); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Sentry$OptionsConfiguration;'; + + @jni$_.internal + @core$_.override + Sentry$OptionsConfiguration<$T>? fromReference(jni$_.JReference reference) => + reference.isNull + ? null + : Sentry$OptionsConfiguration<$T>.fromReference( + T, + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType?> get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => Object.hash($Sentry$OptionsConfiguration$NullableType, T); + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == + ($Sentry$OptionsConfiguration$NullableType<$T>) && + other is $Sentry$OptionsConfiguration$NullableType<$T> && + T == other.T; + } +} + +final class $Sentry$OptionsConfiguration$Type<$T extends jni$_.JObject?> + extends jni$_.JObjType> { + @jni$_.internal + final jni$_.JObjType<$T> T; + + @jni$_.internal + const $Sentry$OptionsConfiguration$Type( + this.T, + ); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Sentry$OptionsConfiguration;'; + + @jni$_.internal + @core$_.override + Sentry$OptionsConfiguration<$T> fromReference(jni$_.JReference reference) => + Sentry$OptionsConfiguration<$T>.fromReference( + T, + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType?> get nullableType => + $Sentry$OptionsConfiguration$NullableType<$T>(T); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => Object.hash($Sentry$OptionsConfiguration$Type, T); + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Sentry$OptionsConfiguration$Type<$T>) && + other is $Sentry$OptionsConfiguration$Type<$T> && + T == other.T; + } +} + +/// from: `io.sentry.Sentry` +class Sentry extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType $type; + + @jni$_.internal + Sentry.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = jni$_.JClass.forName(r'io/sentry/Sentry'); + + /// The type which includes information such as the signature of this class. + static const nullableType = $Sentry$NullableType(); + static const type = $Sentry$Type(); + static final _id_APP_START_PROFILING_CONFIG_FILE_NAME = _class.staticFieldId( + r'APP_START_PROFILING_CONFIG_FILE_NAME', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String APP_START_PROFILING_CONFIG_FILE_NAME` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString get APP_START_PROFILING_CONFIG_FILE_NAME => + _id_APP_START_PROFILING_CONFIG_FILE_NAME.get( + _class, const jni$_.JStringType()); + + static final _id_getCurrentHub = _class.staticMethodId( + r'getCurrentHub', + r'()Lio/sentry/IHub;', + ); + + static final _getCurrentHub = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.IHub getCurrentHub()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject getCurrentHub() { + return _getCurrentHub( + _class.reference.pointer, _id_getCurrentHub as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getCurrentScopes = _class.staticMethodId( + r'getCurrentScopes', + r'()Lio/sentry/IScopes;', + ); + + static final _getCurrentScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.IScopes getCurrentScopes()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject getCurrentScopes() { + return _getCurrentScopes(_class.reference.pointer, + _id_getCurrentScopes as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedRootScopes = _class.staticMethodId( + r'forkedRootScopes', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedRootScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.IScopes forkedRootScopes(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject forkedRootScopes( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedRootScopes(_class.reference.pointer, + _id_forkedRootScopes as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedScopes = _class.staticMethodId( + r'forkedScopes', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.IScopes forkedScopes(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject forkedScopes( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedScopes(_class.reference.pointer, + _id_forkedScopes as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedCurrentScope = _class.staticMethodId( + r'forkedCurrentScope', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedCurrentScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.IScopes forkedCurrentScope(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject forkedCurrentScope( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedCurrentScope(_class.reference.pointer, + _id_forkedCurrentScope as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_setCurrentHub = _class.staticMethodId( + r'setCurrentHub', + r'(Lio/sentry/IHub;)Lio/sentry/ISentryLifecycleToken;', + ); + + static final _setCurrentHub = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.ISentryLifecycleToken setCurrentHub(io.sentry.IHub iHub)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject setCurrentHub( + jni$_.JObject iHub, + ) { + final _$iHub = iHub.reference; + return _setCurrentHub(_class.reference.pointer, + _id_setCurrentHub as jni$_.JMethodIDPtr, _$iHub.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_setCurrentScopes = _class.staticMethodId( + r'setCurrentScopes', + r'(Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken;', + ); + + static final _setCurrentScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.ISentryLifecycleToken setCurrentScopes(io.sentry.IScopes iScopes)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject setCurrentScopes( + jni$_.JObject iScopes, + ) { + final _$iScopes = iScopes.reference; + return _setCurrentScopes(_class.reference.pointer, + _id_setCurrentScopes as jni$_.JMethodIDPtr, _$iScopes.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_getGlobalScope = _class.staticMethodId( + r'getGlobalScope', + r'()Lio/sentry/IScope;', + ); + + static final _getGlobalScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.IScope getGlobalScope()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject getGlobalScope() { + return _getGlobalScope( + _class.reference.pointer, _id_getGlobalScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_isEnabled = _class.staticMethodId( + r'isEnabled', + r'()Z', + ); + + static final _isEnabled = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public boolean isEnabled()` + static bool isEnabled() { + return _isEnabled( + _class.reference.pointer, _id_isEnabled as jni$_.JMethodIDPtr) + .boolean; + } + + static final _id_init = _class.staticMethodId( + r'init', + r'()V', + ); + + static final _init = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void init()` + static void init() { + _init(_class.reference.pointer, _id_init as jni$_.JMethodIDPtr).check(); + } + + static final _id_init$1 = _class.staticMethodId( + r'init', + r'(Ljava/lang/String;)V', + ); + + static final _init$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void init(java.lang.String string)` + static void init$1( + jni$_.JString string, + ) { + final _$string = string.reference; + _init$1(_class.reference.pointer, _id_init$1 as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_init$2 = _class.staticMethodId( + r'init', + r'(Lio/sentry/OptionsContainer;Lio/sentry/Sentry$OptionsConfiguration;)V', + ); + + static final _init$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void init(io.sentry.OptionsContainer optionsContainer, io.sentry.Sentry$OptionsConfiguration optionsConfiguration)` + static void init$2<$T extends jni$_.JObject?>( + jni$_.JObject optionsContainer, + Sentry$OptionsConfiguration<$T?> optionsConfiguration, { + jni$_.JObjType<$T>? T, + }) { + T ??= jni$_.lowestCommonSuperType([ + (optionsConfiguration.$type + as $Sentry$OptionsConfiguration$Type) + .T, + ]) as jni$_.JObjType<$T>; + final _$optionsContainer = optionsContainer.reference; + final _$optionsConfiguration = optionsConfiguration.reference; + _init$2(_class.reference.pointer, _id_init$2 as jni$_.JMethodIDPtr, + _$optionsContainer.pointer, _$optionsConfiguration.pointer) + .check(); + } + + static final _id_init$3 = _class.staticMethodId( + r'init', + r'(Lio/sentry/OptionsContainer;Lio/sentry/Sentry$OptionsConfiguration;Z)V', + ); + + static final _init$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Int32 + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + int)>(); + + /// from: `static public void init(io.sentry.OptionsContainer optionsContainer, io.sentry.Sentry$OptionsConfiguration optionsConfiguration, boolean z)` + static void init$3<$T extends jni$_.JObject?>( + jni$_.JObject optionsContainer, + Sentry$OptionsConfiguration<$T?> optionsConfiguration, + bool z, { + jni$_.JObjType<$T>? T, + }) { + T ??= jni$_.lowestCommonSuperType([ + (optionsConfiguration.$type + as $Sentry$OptionsConfiguration$Type) + .T, + ]) as jni$_.JObjType<$T>; + final _$optionsContainer = optionsContainer.reference; + final _$optionsConfiguration = optionsConfiguration.reference; + _init$3( + _class.reference.pointer, + _id_init$3 as jni$_.JMethodIDPtr, + _$optionsContainer.pointer, + _$optionsConfiguration.pointer, + z ? 1 : 0) + .check(); + } + + static final _id_init$4 = _class.staticMethodId( + r'init', + r'(Lio/sentry/Sentry$OptionsConfiguration;)V', + ); + + static final _init$4 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void init(io.sentry.Sentry$OptionsConfiguration optionsConfiguration)` + static void init$4( + Sentry$OptionsConfiguration optionsConfiguration, + ) { + final _$optionsConfiguration = optionsConfiguration.reference; + _init$4(_class.reference.pointer, _id_init$4 as jni$_.JMethodIDPtr, + _$optionsConfiguration.pointer) + .check(); + } + + static final _id_init$5 = _class.staticMethodId( + r'init', + r'(Lio/sentry/Sentry$OptionsConfiguration;Z)V', + ); + + static final _init$5 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_ + .VarArgs<(jni$_.Pointer, jni$_.Int32)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer, int)>(); + + /// from: `static public void init(io.sentry.Sentry$OptionsConfiguration optionsConfiguration, boolean z)` + static void init$5( + Sentry$OptionsConfiguration optionsConfiguration, + bool z, + ) { + final _$optionsConfiguration = optionsConfiguration.reference; + _init$5(_class.reference.pointer, _id_init$5 as jni$_.JMethodIDPtr, + _$optionsConfiguration.pointer, z ? 1 : 0) + .check(); + } + + static final _id_init$6 = _class.staticMethodId( + r'init', + r'(Lio/sentry/SentryOptions;)V', + ); + + static final _init$6 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void init(io.sentry.SentryOptions sentryOptions)` + static void init$6( + jni$_.JObject sentryOptions, + ) { + final _$sentryOptions = sentryOptions.reference; + _init$6(_class.reference.pointer, _id_init$6 as jni$_.JMethodIDPtr, + _$sentryOptions.pointer) + .check(); + } + + static final _id_close = _class.staticMethodId( + r'close', + r'()V', + ); + + static final _close = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void close()` + static void close() { + _close(_class.reference.pointer, _id_close as jni$_.JMethodIDPtr).check(); + } + + static final _id_captureEvent = _class.staticMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureEvent( + jni$_.JObject sentryEvent, + ) { + final _$sentryEvent = sentryEvent.reference; + return _captureEvent(_class.reference.pointer, + _id_captureEvent as jni$_.JMethodIDPtr, _$sentryEvent.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureEvent$1 = _class.staticMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureEvent$1( + jni$_.JObject sentryEvent, + jni$_.JObject scopeCallback, + ) { + final _$sentryEvent = sentryEvent.reference; + final _$scopeCallback = scopeCallback.reference; + return _captureEvent$1( + _class.reference.pointer, + _id_captureEvent$1 as jni$_.JMethodIDPtr, + _$sentryEvent.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureEvent$2 = _class.staticMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureEvent$2( + jni$_.JObject sentryEvent, + jni$_.JObject? hint, + ) { + final _$sentryEvent = sentryEvent.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureEvent$2( + _class.reference.pointer, + _id_captureEvent$2 as jni$_.JMethodIDPtr, + _$sentryEvent.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureEvent$3 = _class.staticMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureEvent$3( + jni$_.JObject sentryEvent, + jni$_.JObject? hint, + jni$_.JObject scopeCallback, + ) { + final _$sentryEvent = sentryEvent.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + return _captureEvent$3( + _class.reference.pointer, + _id_captureEvent$3 as jni$_.JMethodIDPtr, + _$sentryEvent.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage = _class.staticMethodId( + r'captureMessage', + r'(Ljava/lang/String;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureMessage(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureMessage( + jni$_.JString string, + ) { + final _$string = string.reference; + return _captureMessage(_class.reference.pointer, + _id_captureMessage as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage$1 = _class.staticMethodId( + r'captureMessage', + r'(Ljava/lang/String;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureMessage(java.lang.String string, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureMessage$1( + jni$_.JString string, + jni$_.JObject scopeCallback, + ) { + final _$string = string.reference; + final _$scopeCallback = scopeCallback.reference; + return _captureMessage$1( + _class.reference.pointer, + _id_captureMessage$1 as jni$_.JMethodIDPtr, + _$string.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage$2 = _class.staticMethodId( + r'captureMessage', + r'(Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureMessage(java.lang.String string, io.sentry.SentryLevel sentryLevel)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureMessage$2( + jni$_.JString string, + jni$_.JObject sentryLevel, + ) { + final _$string = string.reference; + final _$sentryLevel = sentryLevel.reference; + return _captureMessage$2( + _class.reference.pointer, + _id_captureMessage$2 as jni$_.JMethodIDPtr, + _$string.pointer, + _$sentryLevel.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage$3 = _class.staticMethodId( + r'captureMessage', + r'(Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureMessage(java.lang.String string, io.sentry.SentryLevel sentryLevel, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureMessage$3( + jni$_.JString string, + jni$_.JObject sentryLevel, + jni$_.JObject scopeCallback, + ) { + final _$string = string.reference; + final _$sentryLevel = sentryLevel.reference; + final _$scopeCallback = scopeCallback.reference; + return _captureMessage$3( + _class.reference.pointer, + _id_captureMessage$3 as jni$_.JMethodIDPtr, + _$string.pointer, + _$sentryLevel.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback = _class.staticMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureFeedback( + jni$_.JObject feedback, + ) { + final _$feedback = feedback.reference; + return _captureFeedback(_class.reference.pointer, + _id_captureFeedback as jni$_.JMethodIDPtr, _$feedback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback$1 = _class.staticMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureFeedback$1( + jni$_.JObject feedback, + jni$_.JObject? hint, + ) { + final _$feedback = feedback.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureFeedback$1( + _class.reference.pointer, + _id_captureFeedback$1 as jni$_.JMethodIDPtr, + _$feedback.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback$2 = _class.staticMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureFeedback$2( + jni$_.JObject feedback, + jni$_.JObject? hint, + jni$_.JObject? scopeCallback, + ) { + final _$feedback = feedback.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback?.reference ?? jni$_.jNullReference; + return _captureFeedback$2( + _class.reference.pointer, + _id_captureFeedback$2 as jni$_.JMethodIDPtr, + _$feedback.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException = _class.staticMethodId( + r'captureException', + r'(Ljava/lang/Throwable;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureException( + jni$_.JObject throwable, + ) { + final _$throwable = throwable.reference; + return _captureException(_class.reference.pointer, + _id_captureException as jni$_.JMethodIDPtr, _$throwable.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException$1 = _class.staticMethodId( + r'captureException', + r'(Ljava/lang/Throwable;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureException$1( + jni$_.JObject throwable, + jni$_.JObject scopeCallback, + ) { + final _$throwable = throwable.reference; + final _$scopeCallback = scopeCallback.reference; + return _captureException$1( + _class.reference.pointer, + _id_captureException$1 as jni$_.JMethodIDPtr, + _$throwable.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException$2 = _class.staticMethodId( + r'captureException', + r'(Ljava/lang/Throwable;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureException$2( + jni$_.JObject throwable, + jni$_.JObject? hint, + ) { + final _$throwable = throwable.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureException$2( + _class.reference.pointer, + _id_captureException$2 as jni$_.JMethodIDPtr, + _$throwable.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException$3 = _class.staticMethodId( + r'captureException', + r'(Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureException$3( + jni$_.JObject throwable, + jni$_.JObject? hint, + jni$_.JObject scopeCallback, + ) { + final _$throwable = throwable.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + return _captureException$3( + _class.reference.pointer, + _id_captureException$3 as jni$_.JMethodIDPtr, + _$throwable.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureUserFeedback = _class.staticMethodId( + r'captureUserFeedback', + r'(Lio/sentry/UserFeedback;)V', + ); + + static final _captureUserFeedback = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void captureUserFeedback(io.sentry.UserFeedback userFeedback)` + static void captureUserFeedback( + jni$_.JObject userFeedback, + ) { + final _$userFeedback = userFeedback.reference; + _captureUserFeedback( + _class.reference.pointer, + _id_captureUserFeedback as jni$_.JMethodIDPtr, + _$userFeedback.pointer) + .check(); + } + + static final _id_addBreadcrumb = _class.staticMethodId( + r'addBreadcrumb', + r'(Lio/sentry/Breadcrumb;Lio/sentry/Hint;)V', + ); + + static final _addBreadcrumb = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb, io.sentry.Hint hint)` + static void addBreadcrumb( + jni$_.JObject breadcrumb, + jni$_.JObject? hint, + ) { + final _$breadcrumb = breadcrumb.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + _addBreadcrumb( + _class.reference.pointer, + _id_addBreadcrumb as jni$_.JMethodIDPtr, + _$breadcrumb.pointer, + _$hint.pointer) + .check(); + } + + static final _id_addBreadcrumb$1 = _class.staticMethodId( + r'addBreadcrumb', + r'(Lio/sentry/Breadcrumb;)V', + ); + + static final _addBreadcrumb$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb)` + static void addBreadcrumb$1( + jni$_.JObject breadcrumb, + ) { + final _$breadcrumb = breadcrumb.reference; + _addBreadcrumb$1(_class.reference.pointer, + _id_addBreadcrumb$1 as jni$_.JMethodIDPtr, _$breadcrumb.pointer) + .check(); + } + + static final _id_addBreadcrumb$2 = _class.staticMethodId( + r'addBreadcrumb', + r'(Ljava/lang/String;)V', + ); + + static final _addBreadcrumb$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void addBreadcrumb(java.lang.String string)` + static void addBreadcrumb$2( + jni$_.JString string, + ) { + final _$string = string.reference; + _addBreadcrumb$2(_class.reference.pointer, + _id_addBreadcrumb$2 as jni$_.JMethodIDPtr, _$string.pointer) + .check(); + } + + static final _id_addBreadcrumb$3 = _class.staticMethodId( + r'addBreadcrumb', + r'(Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _addBreadcrumb$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void addBreadcrumb(java.lang.String string, java.lang.String string1)` + static void addBreadcrumb$3( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + _addBreadcrumb$3( + _class.reference.pointer, + _id_addBreadcrumb$3 as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer) + .check(); + } + + static final _id_setLevel = _class.staticMethodId( + r'setLevel', + r'(Lio/sentry/SentryLevel;)V', + ); + + static final _setLevel = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void setLevel(io.sentry.SentryLevel sentryLevel)` + static void setLevel( + jni$_.JObject? sentryLevel, + ) { + final _$sentryLevel = sentryLevel?.reference ?? jni$_.jNullReference; + _setLevel(_class.reference.pointer, _id_setLevel as jni$_.JMethodIDPtr, + _$sentryLevel.pointer) + .check(); + } + + static final _id_setTransaction = _class.staticMethodId( + r'setTransaction', + r'(Ljava/lang/String;)V', + ); + + static final _setTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void setTransaction(java.lang.String string)` + static void setTransaction( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setTransaction(_class.reference.pointer, + _id_setTransaction as jni$_.JMethodIDPtr, _$string.pointer) + .check(); + } + + static final _id_setUser = _class.staticMethodId( + r'setUser', + r'(Lio/sentry/protocol/User;)V', + ); + + static final _setUser = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void setUser(io.sentry.protocol.User user)` + static void setUser( + jni$_.JObject? user, + ) { + final _$user = user?.reference ?? jni$_.jNullReference; + _setUser(_class.reference.pointer, _id_setUser as jni$_.JMethodIDPtr, + _$user.pointer) + .check(); + } + + static final _id_setFingerprint = _class.staticMethodId( + r'setFingerprint', + r'(Ljava/util/List;)V', + ); + + static final _setFingerprint = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void setFingerprint(java.util.List list)` + static void setFingerprint( + jni$_.JList list, + ) { + final _$list = list.reference; + _setFingerprint(_class.reference.pointer, + _id_setFingerprint as jni$_.JMethodIDPtr, _$list.pointer) + .check(); + } + + static final _id_clearBreadcrumbs = _class.staticMethodId( + r'clearBreadcrumbs', + r'()V', + ); + + static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void clearBreadcrumbs()` + static void clearBreadcrumbs() { + _clearBreadcrumbs(_class.reference.pointer, + _id_clearBreadcrumbs as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_setTag = _class.staticMethodId( + r'setTag', + r'(Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _setTag = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void setTag(java.lang.String string, java.lang.String string1)` + static void setTag( + jni$_.JString? string, + jni$_.JString? string1, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + _setTag(_class.reference.pointer, _id_setTag as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .check(); + } + + static final _id_removeTag = _class.staticMethodId( + r'removeTag', + r'(Ljava/lang/String;)V', + ); + + static final _removeTag = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void removeTag(java.lang.String string)` + static void removeTag( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _removeTag(_class.reference.pointer, _id_removeTag as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_setExtra = _class.staticMethodId( + r'setExtra', + r'(Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _setExtra = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void setExtra(java.lang.String string, java.lang.String string1)` + static void setExtra( + jni$_.JString? string, + jni$_.JString? string1, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + _setExtra(_class.reference.pointer, _id_setExtra as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .check(); + } + + static final _id_removeExtra = _class.staticMethodId( + r'removeExtra', + r'(Ljava/lang/String;)V', + ); + + static final _removeExtra = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void removeExtra(java.lang.String string)` + static void removeExtra( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _removeExtra(_class.reference.pointer, + _id_removeExtra as jni$_.JMethodIDPtr, _$string.pointer) + .check(); + } + + static final _id_getLastEventId = _class.staticMethodId( + r'getLastEventId', + r'()Lio/sentry/protocol/SentryId;', + ); + + static final _getLastEventId = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.protocol.SentryId getLastEventId()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject getLastEventId() { + return _getLastEventId( + _class.reference.pointer, _id_getLastEventId as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_pushScope = _class.staticMethodId( + r'pushScope', + r'()Lio/sentry/ISentryLifecycleToken;', + ); + + static final _pushScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.ISentryLifecycleToken pushScope()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject pushScope() { + return _pushScope( + _class.reference.pointer, _id_pushScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_pushIsolationScope = _class.staticMethodId( + r'pushIsolationScope', + r'()Lio/sentry/ISentryLifecycleToken;', + ); + + static final _pushIsolationScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.ISentryLifecycleToken pushIsolationScope()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject pushIsolationScope() { + return _pushIsolationScope(_class.reference.pointer, + _id_pushIsolationScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_popScope = _class.staticMethodId( + r'popScope', + r'()V', + ); + + static final _popScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void popScope()` + static void popScope() { + _popScope(_class.reference.pointer, _id_popScope as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_withScope = _class.staticMethodId( + r'withScope', + r'(Lio/sentry/ScopeCallback;)V', + ); + + static final _withScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void withScope(io.sentry.ScopeCallback scopeCallback)` + static void withScope( + jni$_.JObject scopeCallback, + ) { + final _$scopeCallback = scopeCallback.reference; + _withScope(_class.reference.pointer, _id_withScope as jni$_.JMethodIDPtr, + _$scopeCallback.pointer) + .check(); + } + + static final _id_withIsolationScope = _class.staticMethodId( + r'withIsolationScope', + r'(Lio/sentry/ScopeCallback;)V', + ); + + static final _withIsolationScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void withIsolationScope(io.sentry.ScopeCallback scopeCallback)` + static void withIsolationScope( + jni$_.JObject scopeCallback, + ) { + final _$scopeCallback = scopeCallback.reference; + _withIsolationScope( + _class.reference.pointer, + _id_withIsolationScope as jni$_.JMethodIDPtr, + _$scopeCallback.pointer) + .check(); + } + + static final _id_configureScope = _class.staticMethodId( + r'configureScope', + r'(Lio/sentry/ScopeCallback;)V', + ); + + static final _configureScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void configureScope(io.sentry.ScopeCallback scopeCallback)` + static void configureScope( + jni$_.JObject scopeCallback, + ) { + final _$scopeCallback = scopeCallback.reference; + _configureScope(_class.reference.pointer, + _id_configureScope as jni$_.JMethodIDPtr, _$scopeCallback.pointer) + .check(); + } + + static final _id_configureScope$1 = _class.staticMethodId( + r'configureScope', + r'(Lio/sentry/ScopeType;Lio/sentry/ScopeCallback;)V', + ); + + static final _configureScope$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void configureScope(io.sentry.ScopeType scopeType, io.sentry.ScopeCallback scopeCallback)` + static void configureScope$1( + jni$_.JObject? scopeType, + jni$_.JObject scopeCallback, + ) { + final _$scopeType = scopeType?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + _configureScope$1( + _class.reference.pointer, + _id_configureScope$1 as jni$_.JMethodIDPtr, + _$scopeType.pointer, + _$scopeCallback.pointer) + .check(); + } + + static final _id_bindClient = _class.staticMethodId( + r'bindClient', + r'(Lio/sentry/ISentryClient;)V', + ); + + static final _bindClient = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void bindClient(io.sentry.ISentryClient iSentryClient)` + static void bindClient( + jni$_.JObject iSentryClient, + ) { + final _$iSentryClient = iSentryClient.reference; + _bindClient(_class.reference.pointer, _id_bindClient as jni$_.JMethodIDPtr, + _$iSentryClient.pointer) + .check(); + } + + static final _id_isHealthy = _class.staticMethodId( + r'isHealthy', + r'()Z', + ); + + static final _isHealthy = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public boolean isHealthy()` + static bool isHealthy() { + return _isHealthy( + _class.reference.pointer, _id_isHealthy as jni$_.JMethodIDPtr) + .boolean; + } + + static final _id_flush = _class.staticMethodId( + r'flush', + r'(J)V', + ); + + static final _flush = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.VarArgs<(jni$_.Int64,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `static public void flush(long j)` + static void flush( + int j, + ) { + _flush(_class.reference.pointer, _id_flush as jni$_.JMethodIDPtr, j) + .check(); + } + + static final _id_startSession = _class.staticMethodId( + r'startSession', + r'()V', + ); + + static final _startSession = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void startSession()` + static void startSession() { + _startSession( + _class.reference.pointer, _id_startSession as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_endSession = _class.staticMethodId( + r'endSession', + r'()V', + ); + + static final _endSession = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void endSession()` + static void endSession() { + _endSession(_class.reference.pointer, _id_endSession as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_startTransaction = _class.staticMethodId( + r'startTransaction', + r'(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.ITransaction startTransaction(java.lang.String string, java.lang.String string1)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject startTransaction( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + return _startTransaction( + _class.reference.pointer, + _id_startTransaction as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startTransaction$1 = _class.staticMethodId( + r'startTransaction', + r'(Ljava/lang/String;Ljava/lang/String;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.ITransaction startTransaction(java.lang.String string, java.lang.String string1, io.sentry.TransactionOptions transactionOptions)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject startTransaction$1( + jni$_.JString string, + jni$_.JString string1, + jni$_.JObject transactionOptions, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + final _$transactionOptions = transactionOptions.reference; + return _startTransaction$1( + _class.reference.pointer, + _id_startTransaction$1 as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$transactionOptions.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startTransaction$2 = _class.staticMethodId( + r'startTransaction', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.ITransaction startTransaction(java.lang.String string, java.lang.String string1, java.lang.String string2, io.sentry.TransactionOptions transactionOptions)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject startTransaction$2( + jni$_.JString string, + jni$_.JString string1, + jni$_.JString? string2, + jni$_.JObject transactionOptions, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + final _$transactionOptions = transactionOptions.reference; + return _startTransaction$2( + _class.reference.pointer, + _id_startTransaction$2 as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer, + _$transactionOptions.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startTransaction$3 = _class.staticMethodId( + r'startTransaction', + r'(Lio/sentry/TransactionContext;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.ITransaction startTransaction(io.sentry.TransactionContext transactionContext)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject startTransaction$3( + jni$_.JObject transactionContext, + ) { + final _$transactionContext = transactionContext.reference; + return _startTransaction$3( + _class.reference.pointer, + _id_startTransaction$3 as jni$_.JMethodIDPtr, + _$transactionContext.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startTransaction$4 = _class.staticMethodId( + r'startTransaction', + r'(Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction$4 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.ITransaction startTransaction(io.sentry.TransactionContext transactionContext, io.sentry.TransactionOptions transactionOptions)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject startTransaction$4( + jni$_.JObject transactionContext, + jni$_.JObject transactionOptions, + ) { + final _$transactionContext = transactionContext.reference; + final _$transactionOptions = transactionOptions.reference; + return _startTransaction$4( + _class.reference.pointer, + _id_startTransaction$4 as jni$_.JMethodIDPtr, + _$transactionContext.pointer, + _$transactionOptions.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startProfiler = _class.staticMethodId( + r'startProfiler', + r'()V', + ); + + static final _startProfiler = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void startProfiler()` + static void startProfiler() { + _startProfiler( + _class.reference.pointer, _id_startProfiler as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_stopProfiler = _class.staticMethodId( + r'stopProfiler', + r'()V', + ); + + static final _stopProfiler = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void stopProfiler()` + static void stopProfiler() { + _stopProfiler( + _class.reference.pointer, _id_stopProfiler as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_getSpan = _class.staticMethodId( + r'getSpan', + r'()Lio/sentry/ISpan;', + ); + + static final _getSpan = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.ISpan getSpan()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject? getSpan() { + return _getSpan(_class.reference.pointer, _id_getSpan as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_isCrashedLastRun = _class.staticMethodId( + r'isCrashedLastRun', + r'()Ljava/lang/Boolean;', + ); + + static final _isCrashedLastRun = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public java.lang.Boolean isCrashedLastRun()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JBoolean? isCrashedLastRun() { + return _isCrashedLastRun(_class.reference.pointer, + _id_isCrashedLastRun as jni$_.JMethodIDPtr) + .object(const jni$_.JBooleanNullableType()); + } + + static final _id_reportFullyDisplayed = _class.staticMethodId( + r'reportFullyDisplayed', + r'()V', + ); + + static final _reportFullyDisplayed = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void reportFullyDisplayed()` + static void reportFullyDisplayed() { + _reportFullyDisplayed(_class.reference.pointer, + _id_reportFullyDisplayed as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_continueTrace = _class.staticMethodId( + r'continueTrace', + r'(Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;', + ); + + static final _continueTrace = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.TransactionContext continueTrace(java.lang.String string, java.util.List list)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject? continueTrace( + jni$_.JString? string, + jni$_.JList? list, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$list = list?.reference ?? jni$_.jNullReference; + return _continueTrace( + _class.reference.pointer, + _id_continueTrace as jni$_.JMethodIDPtr, + _$string.pointer, + _$list.pointer) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_getTraceparent = _class.staticMethodId( + r'getTraceparent', + r'()Lio/sentry/SentryTraceHeader;', + ); + + static final _getTraceparent = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.SentryTraceHeader getTraceparent()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject? getTraceparent() { + return _getTraceparent( + _class.reference.pointer, _id_getTraceparent as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_getBaggage = _class.staticMethodId( + r'getBaggage', + r'()Lio/sentry/BaggageHeader;', + ); + + static final _getBaggage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.BaggageHeader getBaggage()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject? getBaggage() { + return _getBaggage( + _class.reference.pointer, _id_getBaggage as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_captureCheckIn = _class.staticMethodId( + r'captureCheckIn', + r'(Lio/sentry/CheckIn;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureCheckIn = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.protocol.SentryId captureCheckIn(io.sentry.CheckIn checkIn)` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject captureCheckIn( + jni$_.JObject checkIn, + ) { + final _$checkIn = checkIn.reference; + return _captureCheckIn(_class.reference.pointer, + _id_captureCheckIn as jni$_.JMethodIDPtr, _$checkIn.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_logger = _class.staticMethodId( + r'logger', + r'()Lio/sentry/logger/ILoggerApi;', + ); + + static final _logger = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.logger.ILoggerApi logger()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject logger() { + return _logger(_class.reference.pointer, _id_logger as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_replay = _class.staticMethodId( + r'replay', + r'()Lio/sentry/IReplayApi;', + ); + + static final _replay = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.IReplayApi replay()` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JObject replay() { + return _replay(_class.reference.pointer, _id_replay as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_showUserFeedbackDialog = _class.staticMethodId( + r'showUserFeedbackDialog', + r'()V', + ); + + static final _showUserFeedbackDialog = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public void showUserFeedbackDialog()` + static void showUserFeedbackDialog() { + _showUserFeedbackDialog(_class.reference.pointer, + _id_showUserFeedbackDialog as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_showUserFeedbackDialog$1 = _class.staticMethodId( + r'showUserFeedbackDialog', + r'(Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;)V', + ); + + static final _showUserFeedbackDialog$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public void showUserFeedbackDialog(io.sentry.SentryFeedbackOptions$OptionsConfigurator optionsConfigurator)` + static void showUserFeedbackDialog$1( + jni$_.JObject? optionsConfigurator, + ) { + final _$optionsConfigurator = + optionsConfigurator?.reference ?? jni$_.jNullReference; + _showUserFeedbackDialog$1( + _class.reference.pointer, + _id_showUserFeedbackDialog$1 as jni$_.JMethodIDPtr, + _$optionsConfigurator.pointer) + .check(); + } + + static final _id_showUserFeedbackDialog$2 = _class.staticMethodId( + r'showUserFeedbackDialog', + r'(Lio/sentry/protocol/SentryId;Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;)V', + ); + + static final _showUserFeedbackDialog$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public void showUserFeedbackDialog(io.sentry.protocol.SentryId sentryId, io.sentry.SentryFeedbackOptions$OptionsConfigurator optionsConfigurator)` + static void showUserFeedbackDialog$2( + jni$_.JObject? sentryId, + jni$_.JObject? optionsConfigurator, + ) { + final _$sentryId = sentryId?.reference ?? jni$_.jNullReference; + final _$optionsConfigurator = + optionsConfigurator?.reference ?? jni$_.jNullReference; + _showUserFeedbackDialog$2( + _class.reference.pointer, + _id_showUserFeedbackDialog$2 as jni$_.JMethodIDPtr, + _$sentryId.pointer, + _$optionsConfigurator.pointer) + .check(); + } +} + +final class $Sentry$NullableType extends jni$_.JObjType { + @jni$_.internal + const $Sentry$NullableType(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Sentry;'; + + @jni$_.internal + @core$_.override + Sentry? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : Sentry.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Sentry$NullableType).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Sentry$NullableType) && + other is $Sentry$NullableType; + } +} + +final class $Sentry$Type extends jni$_.JObjType { + @jni$_.internal + const $Sentry$Type(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Sentry;'; + + @jni$_.internal + @core$_.override + Sentry fromReference(jni$_.JReference reference) => Sentry.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => const $Sentry$NullableType(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Sentry$Type).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Sentry$Type) && other is $Sentry$Type; + } +} + /// from: `android.graphics.Bitmap$CompressFormat` class Bitmap$CompressFormat extends jni$_.JObject { @jni$_.internal diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 07daa53c69..5288c71d5e 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -222,7 +222,7 @@ class SentryNativeJava extends SentryNativeChannel { } @override - Future addBreadcrumb(Breadcrumb breadcrumb) async { + void addBreadcrumb(Breadcrumb breadcrumb) { JByteArray? breadcrumbBytes; tryCatchSync('addBreadcrumb', () { @@ -238,9 +238,7 @@ class SentryNativeJava extends SentryNativeChannel { } @override - Future clearBreadcrumbs() async { - tryCatchSync('clearBreadcrumbs', () { - native.SentryFlutterPlugin.Companion.clearBreadcrumbs(); - }); - } + void clearBreadcrumbs() => tryCatchSync('clearBreadcrumbs', () { + native.Sentry.clearBreadcrumbs(); + }); } From 6bcf81b1e2794b46bf0cbf5124920dca7b3204c9 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Fri, 17 Oct 2025 11:26:27 +0200 Subject: [PATCH 69/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 10 - packages/flutter/ffi-jni.yaml | 2 + .../sentry_flutter/SentryFlutterPlugin.swift | 18 - .../sentry_flutter_objc/SentryFlutterPlugin.h | 1 - .../sentry_flutter_objc/include/ns_number.h | 3 + .../Sources/sentry_flutter_objc/ns_number.m | 3 + .../flutter/lib/src/native/cocoa/binding.dart | 8 - .../src/native/cocoa/sentry_native_cocoa.dart | 68 +- .../flutter/lib/src/native/java/binding.dart | 3858 ++++++++++++++++- .../src/native/java/sentry_native_java.dart | 45 +- 10 files changed, 3833 insertions(+), 183 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index fc7e5fd1c5..fdddd4beb9 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -443,16 +443,6 @@ class SentryFlutterPlugin : } } - @Suppress("unused") // Used by native/jni bindings - @JvmStatic - fun addBreadcrumbAsBytes(breadcrumbBytes: ByteArray) { - val logger = ScopesAdapter.getInstance().options.logger - val breadcrumbJson = breadcrumbBytes.toString(Charsets.UTF_8) - val reader = JsonObjectReader(StringReader(breadcrumbJson)) - val breadcrumb = Breadcrumb.Deserializer().deserialize(reader, logger) - Sentry.addBreadcrumb(breadcrumb) - } - private fun List?.serialize() = this?.map { it.serialize() } private fun DebugImage.serialize() = diff --git a/packages/flutter/ffi-jni.yaml b/packages/flutter/ffi-jni.yaml index d238eeb65f..0b2265758e 100644 --- a/packages/flutter/ffi-jni.yaml +++ b/packages/flutter/ffi-jni.yaml @@ -17,4 +17,6 @@ classes: - io.sentry.android.replay.ReplayIntegration - io.sentry.flutter.SentryFlutterPlugin - io.sentry.Sentry + - io.sentry.Breadcrumb + - io.sentry.ScopesAdapter - android.graphics.Bitmap diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index d5ef14ab0b..fa6862f036 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -618,24 +618,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { return nil } } - - @objc public class func addBreadcrumbAsBytes(_ breadcrumbBytes: NSData) { - guard let breadcrumbDict = try? JSONSerialization.jsonObject( - with: breadcrumbBytes as Data, - options: [] - ) as? [String: Any] else { - print("addBreadcrumb failed in native cocoa: could not parse bytes") - return - } - let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumbDict) - SentrySDK.addBreadcrumb(breadcrumbInstance) - } - - @objc public class func clearBreadcrumbs() { - SentrySDK.configureScope { scope in - scope.clearBreadcrumbs() - } - } } // swiftlint:enable type_body_length diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index 140830bc5c..6f2e25eb54 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -8,6 +8,5 @@ + (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; -+ (void)addBreadcrumbAsBytes:(NSData *)breadcrumbBytes; @end #endif diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h index bed74f400f..d21b0e40da 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/include/ns_number.h @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// We need to add this file until we update the objective_c package due to a bug +// See the issue: https://github.com/dart-lang/native/pull/2581 + #ifndef OBJECTIVE_C_SRC_NS_NUMBER_H_ #define OBJECTIVE_C_SRC_NS_NUMBER_H_ diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m index 076778887b..1ed9f0129e 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/ns_number.m @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +// We need to add this file until we update the objective_c package due to a bug +// See the issue: https://github.com/dart-lang/native/pull/2581 + #import "ns_number.h" @implementation NSNumber (NSNumberIsFloat) diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index dde21b5522..65fb990cd2 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1447,8 +1447,6 @@ late final _sel_fetchNativeAppStartAsBytes = late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); late final _sel_loadDebugImagesAsBytes_ = objc.registerName("loadDebugImagesAsBytes:"); -late final _sel_addBreadcrumbAsBytes_ = - objc.registerName("addBreadcrumbAsBytes:"); /// SentryFlutterPlugin class SentryFlutterPlugin extends objc.NSObject { @@ -1507,12 +1505,6 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSData.castFromPointer(_ret, retain: true, release: true); } - /// addBreadcrumbAsBytes: - static void addBreadcrumbAsBytes(objc.NSData breadcrumbBytes) { - _objc_msgSend_xtuoz7(_class_SentryFlutterPlugin, _sel_addBreadcrumbAsBytes_, - breadcrumbBytes.ref.pointer); - } - /// init SentryFlutterPlugin init() { objc.checkOsVersionInternal('SentryFlutterPlugin.init', diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 6e7fcc0cff..bb7a1f66d3 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:objective_c/objective_c.dart'; @@ -8,7 +7,6 @@ import '../../../sentry_flutter.dart'; import '../../replay/replay_config.dart'; import '../native_app_start.dart'; import '../sentry_native_channel.dart'; -import '../utils/data_normalizer.dart'; import '../utils/utf8_json.dart'; import 'binding.dart' as cocoa; import 'cocoa_replay_recorder.dart'; @@ -192,46 +190,46 @@ class SentryNativeCocoa extends SentryNativeChannel { tryCatchSync('addBreadcrumb', () { final nativeBreadcrumb = cocoa.PrivateSentrySDKOnly.breadcrumbWithDictionary( - deepConvertMapNonNull(breadcrumb.toJson()).toNSDictionary()); + _deepConvertMapNonNull(breadcrumb.toJson()).toNSDictionary()); cocoa.SentrySDK.addBreadcrumb(nativeBreadcrumb); }); - Map deepConvertMapNonNull(Map input) { - final out = {}; - - for (final entry in input.entries) { - final value = entry.value; - if (value == null) continue; - - out[entry.key] = switch (value) { - Map m => deepConvertMapNonNull(m), - List l => [ - for (final e in l) - if (e != null) - e is Map - ? deepConvertMapNonNull(e) - : e as Object - ], - _ => value as Object, - }; - } - - return out; - } - @override - void clearBreadcrumbs() { - tryCatchSync('clearBreadcrumbs', () { - cocoa.SentrySDK.configureScope( - cocoa.ObjCBlock_ffiVoid_SentryScope.fromFunction( - (cocoa.SentryScope scope) { - scope.clearBreadcrumbs(); - })); - }); - } + void clearBreadcrumbs() => tryCatchSync('clearBreadcrumbs', () { + cocoa.SentrySDK.configureScope( + cocoa.ObjCBlock_ffiVoid_SentryScope.fromFunction( + (cocoa.SentryScope scope) { + scope.clearBreadcrumbs(); + })); + }); @override void resumeAppHangTracking() => tryCatchSync('resumeAppHangTracking', () { cocoa.SentrySDK.resumeAppHangTracking(); }); } + +/// This map conversion is needed so we can use the toNSDictionary extension function +/// provided by the objective_c package. +Map _deepConvertMapNonNull(Map input) { + final out = {}; + + for (final entry in input.entries) { + final value = entry.value; + if (value == null) continue; + + out[entry.key] = switch (value) { + Map m => _deepConvertMapNonNull(m), + List l => [ + for (final e in l) + if (e != null) + e is Map + ? _deepConvertMapNonNull(e) + : e as Object + ], + _ => value as Object, + }; + } + + return out; +} diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index 0d8c9d1417..830d9f6d6f 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1455,56 +1455,6 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const jni$_.JByteArrayNullableType()); } - static final _id_addBreadcrumbAsBytes = _class.instanceMethodId( - r'addBreadcrumbAsBytes', - r'([B)V', - ); - - static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - jni$_.VarArgs<(jni$_.Pointer,)>)>>( - 'globalEnv_CallVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function(jni$_.Pointer, - jni$_.JMethodIDPtr, jni$_.Pointer)>(); - - /// from: `public final void addBreadcrumbAsBytes(byte[] bs)` - void addBreadcrumbAsBytes( - jni$_.JByteArray bs, - ) { - final _$bs = bs.reference; - _addBreadcrumbAsBytes(reference.pointer, - _id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer) - .check(); - } - - static final _id_clearBreadcrumbs = _class.instanceMethodId( - r'clearBreadcrumbs', - r'()V', - ); - - static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>>('globalEnv_CallVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>(); - - /// from: `public final void clearBreadcrumbs()` - void clearBreadcrumbs() { - _clearBreadcrumbs( - reference.pointer, _id_clearBreadcrumbs as jni$_.JMethodIDPtr) - .check(); - } - static final _id_new$ = _class.constructorId( r'(Lkotlin/jvm/internal/DefaultConstructorMarker;)V', ); @@ -2037,56 +1987,6 @@ class SentryFlutterPlugin extends jni$_.JObject { _id_loadDebugImagesAsBytes as jni$_.JMethodIDPtr, _$set.pointer) .object(const jni$_.JByteArrayNullableType()); } - - static final _id_addBreadcrumbAsBytes = _class.staticMethodId( - r'addBreadcrumbAsBytes', - r'([B)V', - ); - - static final _addBreadcrumbAsBytes = jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - jni$_.VarArgs<(jni$_.Pointer,)>)>>( - 'globalEnv_CallStaticVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function(jni$_.Pointer, - jni$_.JMethodIDPtr, jni$_.Pointer)>(); - - /// from: `static public final void addBreadcrumbAsBytes(byte[] bs)` - static void addBreadcrumbAsBytes( - jni$_.JByteArray bs, - ) { - final _$bs = bs.reference; - _addBreadcrumbAsBytes(_class.reference.pointer, - _id_addBreadcrumbAsBytes as jni$_.JMethodIDPtr, _$bs.pointer) - .check(); - } - - static final _id_clearBreadcrumbs = _class.staticMethodId( - r'clearBreadcrumbs', - r'()V', - ); - - static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< - jni$_.NativeFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>>('globalEnv_CallStaticVoidMethod') - .asFunction< - jni$_.JThrowablePtr Function( - jni$_.Pointer, - jni$_.JMethodIDPtr, - )>(); - - /// from: `static public final void clearBreadcrumbs()` - static void clearBreadcrumbs() { - _clearBreadcrumbs(_class.reference.pointer, - _id_clearBreadcrumbs as jni$_.JMethodIDPtr) - .check(); - } } final class $SentryFlutterPlugin$NullableType @@ -3533,7 +3433,7 @@ class Sentry extends jni$_.JObject { /// from: `static public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb, io.sentry.Hint hint)` static void addBreadcrumb( - jni$_.JObject breadcrumb, + Breadcrumb breadcrumb, jni$_.JObject? hint, ) { final _$breadcrumb = breadcrumb.reference; @@ -3564,7 +3464,7 @@ class Sentry extends jni$_.JObject { /// from: `static public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb)` static void addBreadcrumb$1( - jni$_.JObject breadcrumb, + Breadcrumb breadcrumb, ) { final _$breadcrumb = breadcrumb.reference; _addBreadcrumb$1(_class.reference.pointer, @@ -4860,6 +4760,3760 @@ final class $Sentry$Type extends jni$_.JObjType { } } +/// from: `io.sentry.Breadcrumb$Deserializer` +class Breadcrumb$Deserializer extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType $type; + + @jni$_.internal + Breadcrumb$Deserializer.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + jni$_.JClass.forName(r'io/sentry/Breadcrumb$Deserializer'); + + /// The type which includes information such as the signature of this class. + static const nullableType = $Breadcrumb$Deserializer$NullableType(); + static const type = $Breadcrumb$Deserializer$Type(); + static final _id_new$ = _class.constructorId( + r'()V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void ()` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb$Deserializer() { + return Breadcrumb$Deserializer.fromReference( + _new$(_class.reference.pointer, _id_new$ as jni$_.JMethodIDPtr) + .reference); + } + + static final _id_deserialize = _class.instanceMethodId( + r'deserialize', + r'(Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/Breadcrumb;', + ); + + static final _deserialize = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.Breadcrumb deserialize(io.sentry.ObjectReader objectReader, io.sentry.ILogger iLogger)` + /// The returned object must be released after use, by calling the [release] method. + Breadcrumb deserialize( + jni$_.JObject objectReader, + jni$_.JObject iLogger, + ) { + final _$objectReader = objectReader.reference; + final _$iLogger = iLogger.reference; + return _deserialize( + reference.pointer, + _id_deserialize as jni$_.JMethodIDPtr, + _$objectReader.pointer, + _$iLogger.pointer) + .object(const $Breadcrumb$Type()); + } +} + +final class $Breadcrumb$Deserializer$NullableType + extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$Deserializer$NullableType(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb$Deserializer;'; + + @jni$_.internal + @core$_.override + Breadcrumb$Deserializer? fromReference(jni$_.JReference reference) => + reference.isNull + ? null + : Breadcrumb$Deserializer.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$Deserializer$NullableType).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$Deserializer$NullableType) && + other is $Breadcrumb$Deserializer$NullableType; + } +} + +final class $Breadcrumb$Deserializer$Type + extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$Deserializer$Type(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb$Deserializer;'; + + @jni$_.internal + @core$_.override + Breadcrumb$Deserializer fromReference(jni$_.JReference reference) => + Breadcrumb$Deserializer.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => + const $Breadcrumb$Deserializer$NullableType(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$Deserializer$Type).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$Deserializer$Type) && + other is $Breadcrumb$Deserializer$Type; + } +} + +/// from: `io.sentry.Breadcrumb$JsonKeys` +class Breadcrumb$JsonKeys extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType $type; + + @jni$_.internal + Breadcrumb$JsonKeys.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = jni$_.JClass.forName(r'io/sentry/Breadcrumb$JsonKeys'); + + /// The type which includes information such as the signature of this class. + static const nullableType = $Breadcrumb$JsonKeys$NullableType(); + static const type = $Breadcrumb$JsonKeys$Type(); + static final _id_TIMESTAMP = _class.staticFieldId( + r'TIMESTAMP', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String TIMESTAMP` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get TIMESTAMP => + _id_TIMESTAMP.get(_class, const jni$_.JStringNullableType()); + + static final _id_MESSAGE = _class.staticFieldId( + r'MESSAGE', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String MESSAGE` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get MESSAGE => + _id_MESSAGE.get(_class, const jni$_.JStringNullableType()); + + static final _id_TYPE = _class.staticFieldId( + r'TYPE', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String TYPE` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get TYPE => + _id_TYPE.get(_class, const jni$_.JStringNullableType()); + + static final _id_DATA = _class.staticFieldId( + r'DATA', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String DATA` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get DATA => + _id_DATA.get(_class, const jni$_.JStringNullableType()); + + static final _id_CATEGORY = _class.staticFieldId( + r'CATEGORY', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String CATEGORY` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get CATEGORY => + _id_CATEGORY.get(_class, const jni$_.JStringNullableType()); + + static final _id_ORIGIN = _class.staticFieldId( + r'ORIGIN', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String ORIGIN` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get ORIGIN => + _id_ORIGIN.get(_class, const jni$_.JStringNullableType()); + + static final _id_LEVEL = _class.staticFieldId( + r'LEVEL', + r'Ljava/lang/String;', + ); + + /// from: `static public final java.lang.String LEVEL` + /// The returned object must be released after use, by calling the [release] method. + static jni$_.JString? get LEVEL => + _id_LEVEL.get(_class, const jni$_.JStringNullableType()); + + static final _id_new$ = _class.constructorId( + r'()V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void ()` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb$JsonKeys() { + return Breadcrumb$JsonKeys.fromReference( + _new$(_class.reference.pointer, _id_new$ as jni$_.JMethodIDPtr) + .reference); + } +} + +final class $Breadcrumb$JsonKeys$NullableType + extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$JsonKeys$NullableType(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb$JsonKeys;'; + + @jni$_.internal + @core$_.override + Breadcrumb$JsonKeys? fromReference(jni$_.JReference reference) => + reference.isNull + ? null + : Breadcrumb$JsonKeys.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$JsonKeys$NullableType).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$JsonKeys$NullableType) && + other is $Breadcrumb$JsonKeys$NullableType; + } +} + +final class $Breadcrumb$JsonKeys$Type + extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$JsonKeys$Type(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb$JsonKeys;'; + + @jni$_.internal + @core$_.override + Breadcrumb$JsonKeys fromReference(jni$_.JReference reference) => + Breadcrumb$JsonKeys.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => + const $Breadcrumb$JsonKeys$NullableType(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$JsonKeys$Type).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$JsonKeys$Type) && + other is $Breadcrumb$JsonKeys$Type; + } +} + +/// from: `io.sentry.Breadcrumb` +class Breadcrumb extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType $type; + + @jni$_.internal + Breadcrumb.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = jni$_.JClass.forName(r'io/sentry/Breadcrumb'); + + /// The type which includes information such as the signature of this class. + static const nullableType = $Breadcrumb$NullableType(); + static const type = $Breadcrumb$Type(); + static final _id_new$ = _class.constructorId( + r'(Ljava/util/Date;)V', + ); + + static final _new$ = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void (java.util.Date date)` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb( + jni$_.JObject date, + ) { + final _$date = date.reference; + return Breadcrumb.fromReference(_new$(_class.reference.pointer, + _id_new$ as jni$_.JMethodIDPtr, _$date.pointer) + .reference); + } + + static final _id_new$1 = _class.constructorId( + r'(J)V', + ); + + static final _new$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Int64,)>)>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `public void (long j)` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb.new$1( + int j, + ) { + return Breadcrumb.fromReference( + _new$1(_class.reference.pointer, _id_new$1 as jni$_.JMethodIDPtr, j) + .reference); + } + + static final _id_fromMap = _class.staticMethodId( + r'fromMap', + r'(Ljava/util/Map;Lio/sentry/SentryOptions;)Lio/sentry/Breadcrumb;', + ); + + static final _fromMap = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb fromMap(java.util.Map map, io.sentry.SentryOptions sentryOptions)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb? fromMap( + jni$_.JMap map, + jni$_.JObject sentryOptions, + ) { + final _$map = map.reference; + final _$sentryOptions = sentryOptions.reference; + return _fromMap(_class.reference.pointer, _id_fromMap as jni$_.JMethodIDPtr, + _$map.pointer, _$sentryOptions.pointer) + .object(const $Breadcrumb$NullableType()); + } + + static final _id_http = _class.staticMethodId( + r'http', + r'(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _http = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb http(java.lang.String string, java.lang.String string1)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb http( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + return _http(_class.reference.pointer, _id_http as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_http$1 = _class.staticMethodId( + r'http', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lio/sentry/Breadcrumb;', + ); + + static final _http$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb http(java.lang.String string, java.lang.String string1, java.lang.Integer integer)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb http$1( + jni$_.JString string, + jni$_.JString string1, + jni$_.JInteger? integer, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + final _$integer = integer?.reference ?? jni$_.jNullReference; + return _http$1(_class.reference.pointer, _id_http$1 as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer, _$integer.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_graphqlOperation = _class.staticMethodId( + r'graphqlOperation', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _graphqlOperation = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb graphqlOperation(java.lang.String string, java.lang.String string1, java.lang.String string2)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb graphqlOperation( + jni$_.JString? string, + jni$_.JString? string1, + jni$_.JString? string2, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + return _graphqlOperation( + _class.reference.pointer, + _id_graphqlOperation as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_graphqlDataFetcher = _class.staticMethodId( + r'graphqlDataFetcher', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _graphqlDataFetcher = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb graphqlDataFetcher(java.lang.String string, java.lang.String string1, java.lang.String string2, java.lang.String string3)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb graphqlDataFetcher( + jni$_.JString? string, + jni$_.JString? string1, + jni$_.JString? string2, + jni$_.JString? string3, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + final _$string3 = string3?.reference ?? jni$_.jNullReference; + return _graphqlDataFetcher( + _class.reference.pointer, + _id_graphqlDataFetcher as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer, + _$string3.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_graphqlDataLoader = _class.staticMethodId( + r'graphqlDataLoader', + r'(Ljava/lang/Iterable;Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _graphqlDataLoader = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb graphqlDataLoader(java.lang.Iterable iterable, java.lang.Class class, java.lang.Class class1, java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb graphqlDataLoader( + jni$_.JObject iterable, + jni$_.JObject? class$, + jni$_.JObject? class1, + jni$_.JString? string, + ) { + final _$iterable = iterable.reference; + final _$class$ = class$?.reference ?? jni$_.jNullReference; + final _$class1 = class1?.reference ?? jni$_.jNullReference; + final _$string = string?.reference ?? jni$_.jNullReference; + return _graphqlDataLoader( + _class.reference.pointer, + _id_graphqlDataLoader as jni$_.JMethodIDPtr, + _$iterable.pointer, + _$class$.pointer, + _$class1.pointer, + _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_navigation = _class.staticMethodId( + r'navigation', + r'(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _navigation = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb navigation(java.lang.String string, java.lang.String string1)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb navigation( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + return _navigation( + _class.reference.pointer, + _id_navigation as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_transaction = _class.staticMethodId( + r'transaction', + r'(Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _transaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb transaction(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb transaction( + jni$_.JString string, + ) { + final _$string = string.reference; + return _transaction(_class.reference.pointer, + _id_transaction as jni$_.JMethodIDPtr, _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_debug = _class.staticMethodId( + r'debug', + r'(Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _debug = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb debug(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb debug( + jni$_.JString string, + ) { + final _$string = string.reference; + return _debug(_class.reference.pointer, _id_debug as jni$_.JMethodIDPtr, + _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_error = _class.staticMethodId( + r'error', + r'(Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _error = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb error(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb error( + jni$_.JString string, + ) { + final _$string = string.reference; + return _error(_class.reference.pointer, _id_error as jni$_.JMethodIDPtr, + _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_info = _class.staticMethodId( + r'info', + r'(Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _info = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb info(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb info( + jni$_.JString string, + ) { + final _$string = string.reference; + return _info(_class.reference.pointer, _id_info as jni$_.JMethodIDPtr, + _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_query = _class.staticMethodId( + r'query', + r'(Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _query = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb query(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb query( + jni$_.JString string, + ) { + final _$string = string.reference; + return _query(_class.reference.pointer, _id_query as jni$_.JMethodIDPtr, + _$string.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_ui = _class.staticMethodId( + r'ui', + r'(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _ui = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb ui(java.lang.String string, java.lang.String string1)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb ui( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + return _ui(_class.reference.pointer, _id_ui as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_user = _class.staticMethodId( + r'user', + r'(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _user = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb user(java.lang.String string, java.lang.String string1)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb user( + jni$_.JString string, + jni$_.JString string1, + ) { + final _$string = string.reference; + final _$string1 = string1.reference; + return _user(_class.reference.pointer, _id_user as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_userInteraction = _class.staticMethodId( + r'userInteraction', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb;', + ); + + static final _userInteraction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb userInteraction(java.lang.String string, java.lang.String string1, java.lang.String string2)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb userInteraction( + jni$_.JString string, + jni$_.JString? string1, + jni$_.JString? string2, + ) { + final _$string = string.reference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + return _userInteraction( + _class.reference.pointer, + _id_userInteraction as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_userInteraction$1 = _class.staticMethodId( + r'userInteraction', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/sentry/Breadcrumb;', + ); + + static final _userInteraction$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb userInteraction(java.lang.String string, java.lang.String string1, java.lang.String string2, java.lang.String string3, java.util.Map map)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb userInteraction$1( + jni$_.JString string, + jni$_.JString? string1, + jni$_.JString? string2, + jni$_.JString? string3, + jni$_.JMap map, + ) { + final _$string = string.reference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + final _$string3 = string3?.reference ?? jni$_.jNullReference; + final _$map = map.reference; + return _userInteraction$1( + _class.reference.pointer, + _id_userInteraction$1 as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer, + _$string3.pointer, + _$map.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_userInteraction$2 = _class.staticMethodId( + r'userInteraction', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/sentry/Breadcrumb;', + ); + + static final _userInteraction$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `static public io.sentry.Breadcrumb userInteraction(java.lang.String string, java.lang.String string1, java.lang.String string2, java.util.Map map)` + /// The returned object must be released after use, by calling the [release] method. + static Breadcrumb userInteraction$2( + jni$_.JString string, + jni$_.JString? string1, + jni$_.JString? string2, + jni$_.JMap map, + ) { + final _$string = string.reference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + final _$string2 = string2?.reference ?? jni$_.jNullReference; + final _$map = map.reference; + return _userInteraction$2( + _class.reference.pointer, + _id_userInteraction$2 as jni$_.JMethodIDPtr, + _$string.pointer, + _$string1.pointer, + _$string2.pointer, + _$map.pointer) + .object(const $Breadcrumb$Type()); + } + + static final _id_new$2 = _class.constructorId( + r'()V', + ); + + static final _new$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void ()` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb.new$2() { + return Breadcrumb.fromReference( + _new$2(_class.reference.pointer, _id_new$2 as jni$_.JMethodIDPtr) + .reference); + } + + static final _id_new$3 = _class.constructorId( + r'(Ljava/lang/String;)V', + ); + + static final _new$3 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_NewObject') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void (java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + factory Breadcrumb.new$3( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + return Breadcrumb.fromReference(_new$3(_class.reference.pointer, + _id_new$3 as jni$_.JMethodIDPtr, _$string.pointer) + .reference); + } + + static final _id_getTimestamp = _class.instanceMethodId( + r'getTimestamp', + r'()Ljava/util/Date;', + ); + + static final _getTimestamp = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.util.Date getTimestamp()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getTimestamp() { + return _getTimestamp( + reference.pointer, _id_getTimestamp as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getMessage = _class.instanceMethodId( + r'getMessage', + r'()Ljava/lang/String;', + ); + + static final _getMessage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.lang.String getMessage()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString? getMessage() { + return _getMessage(reference.pointer, _id_getMessage as jni$_.JMethodIDPtr) + .object(const jni$_.JStringNullableType()); + } + + static final _id_setMessage = _class.instanceMethodId( + r'setMessage', + r'(Ljava/lang/String;)V', + ); + + static final _setMessage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setMessage(java.lang.String string)` + void setMessage( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setMessage(reference.pointer, _id_setMessage as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getType = _class.instanceMethodId( + r'getType', + r'()Ljava/lang/String;', + ); + + static final _getType = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.lang.String getType()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString? getType() { + return _getType(reference.pointer, _id_getType as jni$_.JMethodIDPtr) + .object(const jni$_.JStringNullableType()); + } + + static final _id_setType = _class.instanceMethodId( + r'setType', + r'(Ljava/lang/String;)V', + ); + + static final _setType = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setType(java.lang.String string)` + void setType( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setType(reference.pointer, _id_setType as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getData = _class.instanceMethodId( + r'getData', + r'()Ljava/util/Map;', + ); + + static final _getData = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.util.Map getData()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JMap getData() { + return _getData(reference.pointer, _id_getData as jni$_.JMethodIDPtr) + .object>( + const jni$_.JMapType( + jni$_.JStringNullableType(), jni$_.JObjectNullableType())); + } + + static final _id_getData$1 = _class.instanceMethodId( + r'getData', + r'(Ljava/lang/String;)Ljava/lang/Object;', + ); + + static final _getData$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public java.lang.Object getData(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getData$1( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + return _getData$1(reference.pointer, _id_getData$1 as jni$_.JMethodIDPtr, + _$string.pointer) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_setData = _class.instanceMethodId( + r'setData', + r'(Ljava/lang/String;Ljava/lang/Object;)V', + ); + + static final _setData = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void setData(java.lang.String string, java.lang.Object object)` + void setData( + jni$_.JString? string, + jni$_.JObject? object, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$object = object?.reference ?? jni$_.jNullReference; + _setData(reference.pointer, _id_setData as jni$_.JMethodIDPtr, + _$string.pointer, _$object.pointer) + .check(); + } + + static final _id_removeData = _class.instanceMethodId( + r'removeData', + r'(Ljava/lang/String;)V', + ); + + static final _removeData = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void removeData(java.lang.String string)` + void removeData( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _removeData(reference.pointer, _id_removeData as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getCategory = _class.instanceMethodId( + r'getCategory', + r'()Ljava/lang/String;', + ); + + static final _getCategory = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.lang.String getCategory()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString? getCategory() { + return _getCategory( + reference.pointer, _id_getCategory as jni$_.JMethodIDPtr) + .object(const jni$_.JStringNullableType()); + } + + static final _id_setCategory = _class.instanceMethodId( + r'setCategory', + r'(Ljava/lang/String;)V', + ); + + static final _setCategory = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setCategory(java.lang.String string)` + void setCategory( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setCategory(reference.pointer, _id_setCategory as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getOrigin = _class.instanceMethodId( + r'getOrigin', + r'()Ljava/lang/String;', + ); + + static final _getOrigin = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.lang.String getOrigin()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JString? getOrigin() { + return _getOrigin(reference.pointer, _id_getOrigin as jni$_.JMethodIDPtr) + .object(const jni$_.JStringNullableType()); + } + + static final _id_setOrigin = _class.instanceMethodId( + r'setOrigin', + r'(Ljava/lang/String;)V', + ); + + static final _setOrigin = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setOrigin(java.lang.String string)` + void setOrigin( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setOrigin(reference.pointer, _id_setOrigin as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getLevel = _class.instanceMethodId( + r'getLevel', + r'()Lio/sentry/SentryLevel;', + ); + + static final _getLevel = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.SentryLevel getLevel()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getLevel() { + return _getLevel(reference.pointer, _id_getLevel as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_setLevel = _class.instanceMethodId( + r'setLevel', + r'(Lio/sentry/SentryLevel;)V', + ); + + static final _setLevel = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setLevel(io.sentry.SentryLevel sentryLevel)` + void setLevel( + jni$_.JObject? sentryLevel, + ) { + final _$sentryLevel = sentryLevel?.reference ?? jni$_.jNullReference; + _setLevel(reference.pointer, _id_setLevel as jni$_.JMethodIDPtr, + _$sentryLevel.pointer) + .check(); + } + + static final _id_equals = _class.instanceMethodId( + r'equals', + r'(Ljava/lang/Object;)Z', + ); + + static final _equals = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public boolean equals(java.lang.Object object)` + bool equals( + jni$_.JObject? object, + ) { + final _$object = object?.reference ?? jni$_.jNullReference; + return _equals(reference.pointer, _id_equals as jni$_.JMethodIDPtr, + _$object.pointer) + .boolean; + } + + static final _id_hashCode$1 = _class.instanceMethodId( + r'hashCode', + r'()I', + ); + + static final _hashCode$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallIntMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public int hashCode()` + int hashCode$1() { + return _hashCode$1(reference.pointer, _id_hashCode$1 as jni$_.JMethodIDPtr) + .integer; + } + + static final _id_getUnknown = _class.instanceMethodId( + r'getUnknown', + r'()Ljava/util/Map;', + ); + + static final _getUnknown = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.util.Map getUnknown()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JMap? getUnknown() { + return _getUnknown(reference.pointer, _id_getUnknown as jni$_.JMethodIDPtr) + .object?>( + const jni$_.JMapNullableType( + jni$_.JStringNullableType(), jni$_.JObjectNullableType())); + } + + static final _id_setUnknown = _class.instanceMethodId( + r'setUnknown', + r'(Ljava/util/Map;)V', + ); + + static final _setUnknown = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setUnknown(java.util.Map map)` + void setUnknown( + jni$_.JMap? map, + ) { + final _$map = map?.reference ?? jni$_.jNullReference; + _setUnknown(reference.pointer, _id_setUnknown as jni$_.JMethodIDPtr, + _$map.pointer) + .check(); + } + + static final _id_compareTo = _class.instanceMethodId( + r'compareTo', + r'(Lio/sentry/Breadcrumb;)I', + ); + + static final _compareTo = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallIntMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public int compareTo(io.sentry.Breadcrumb breadcrumb)` + int compareTo( + Breadcrumb breadcrumb, + ) { + final _$breadcrumb = breadcrumb.reference; + return _compareTo(reference.pointer, _id_compareTo as jni$_.JMethodIDPtr, + _$breadcrumb.pointer) + .integer; + } + + static final _id_serialize = _class.instanceMethodId( + r'serialize', + r'(Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V', + ); + + static final _serialize = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void serialize(io.sentry.ObjectWriter objectWriter, io.sentry.ILogger iLogger)` + void serialize( + jni$_.JObject objectWriter, + jni$_.JObject iLogger, + ) { + final _$objectWriter = objectWriter.reference; + final _$iLogger = iLogger.reference; + _serialize(reference.pointer, _id_serialize as jni$_.JMethodIDPtr, + _$objectWriter.pointer, _$iLogger.pointer) + .check(); + } + + bool operator <(Breadcrumb breadcrumb) { + return compareTo(breadcrumb) < 0; + } + + bool operator <=(Breadcrumb breadcrumb) { + return compareTo(breadcrumb) <= 0; + } + + bool operator >(Breadcrumb breadcrumb) { + return compareTo(breadcrumb) > 0; + } + + bool operator >=(Breadcrumb breadcrumb) { + return compareTo(breadcrumb) >= 0; + } +} + +final class $Breadcrumb$NullableType extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$NullableType(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb;'; + + @jni$_.internal + @core$_.override + Breadcrumb? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : Breadcrumb.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$NullableType).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$NullableType) && + other is $Breadcrumb$NullableType; + } +} + +final class $Breadcrumb$Type extends jni$_.JObjType { + @jni$_.internal + const $Breadcrumb$Type(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/Breadcrumb;'; + + @jni$_.internal + @core$_.override + Breadcrumb fromReference(jni$_.JReference reference) => + Breadcrumb.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => + const $Breadcrumb$NullableType(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($Breadcrumb$Type).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($Breadcrumb$Type) && other is $Breadcrumb$Type; + } +} + +/// from: `io.sentry.ScopesAdapter` +class ScopesAdapter extends jni$_.JObject { + @jni$_.internal + @core$_.override + final jni$_.JObjType $type; + + @jni$_.internal + ScopesAdapter.fromReference( + jni$_.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = jni$_.JClass.forName(r'io/sentry/ScopesAdapter'); + + /// The type which includes information such as the signature of this class. + static const nullableType = $ScopesAdapter$NullableType(); + static const type = $ScopesAdapter$Type(); + static final _id_getInstance = _class.staticMethodId( + r'getInstance', + r'()Lio/sentry/ScopesAdapter;', + ); + + static final _getInstance = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallStaticObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `static public io.sentry.ScopesAdapter getInstance()` + /// The returned object must be released after use, by calling the [release] method. + static ScopesAdapter? getInstance() { + return _getInstance( + _class.reference.pointer, _id_getInstance as jni$_.JMethodIDPtr) + .object(const $ScopesAdapter$NullableType()); + } + + static final _id_isEnabled = _class.instanceMethodId( + r'isEnabled', + r'()Z', + ); + + static final _isEnabled = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public boolean isEnabled()` + bool isEnabled() { + return _isEnabled(reference.pointer, _id_isEnabled as jni$_.JMethodIDPtr) + .boolean; + } + + static final _id_captureEvent = _class.instanceMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureEvent( + jni$_.JObject sentryEvent, + jni$_.JObject? hint, + ) { + final _$sentryEvent = sentryEvent.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureEvent( + reference.pointer, + _id_captureEvent as jni$_.JMethodIDPtr, + _$sentryEvent.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureEvent$1 = _class.instanceMethodId( + r'captureEvent', + r'(Lio/sentry/SentryEvent;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEvent$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureEvent(io.sentry.SentryEvent sentryEvent, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureEvent$1( + jni$_.JObject sentryEvent, + jni$_.JObject? hint, + jni$_.JObject scopeCallback, + ) { + final _$sentryEvent = sentryEvent.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + return _captureEvent$1( + reference.pointer, + _id_captureEvent$1 as jni$_.JMethodIDPtr, + _$sentryEvent.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage = _class.instanceMethodId( + r'captureMessage', + r'(Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureMessage(java.lang.String string, io.sentry.SentryLevel sentryLevel)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureMessage( + jni$_.JString string, + jni$_.JObject sentryLevel, + ) { + final _$string = string.reference; + final _$sentryLevel = sentryLevel.reference; + return _captureMessage( + reference.pointer, + _id_captureMessage as jni$_.JMethodIDPtr, + _$string.pointer, + _$sentryLevel.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureMessage$1 = _class.instanceMethodId( + r'captureMessage', + r'(Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureMessage$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureMessage(java.lang.String string, io.sentry.SentryLevel sentryLevel, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureMessage$1( + jni$_.JString string, + jni$_.JObject sentryLevel, + jni$_.JObject scopeCallback, + ) { + final _$string = string.reference; + final _$sentryLevel = sentryLevel.reference; + final _$scopeCallback = scopeCallback.reference; + return _captureMessage$1( + reference.pointer, + _id_captureMessage$1 as jni$_.JMethodIDPtr, + _$string.pointer, + _$sentryLevel.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback = _class.instanceMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureFeedback( + jni$_.JObject feedback, + ) { + final _$feedback = feedback.reference; + return _captureFeedback(reference.pointer, + _id_captureFeedback as jni$_.JMethodIDPtr, _$feedback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback$1 = _class.instanceMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureFeedback$1( + jni$_.JObject feedback, + jni$_.JObject? hint, + ) { + final _$feedback = feedback.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureFeedback$1( + reference.pointer, + _id_captureFeedback$1 as jni$_.JMethodIDPtr, + _$feedback.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureFeedback$2 = _class.instanceMethodId( + r'captureFeedback', + r'(Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureFeedback$2 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureFeedback(io.sentry.protocol.Feedback feedback, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureFeedback$2( + jni$_.JObject feedback, + jni$_.JObject? hint, + jni$_.JObject? scopeCallback, + ) { + final _$feedback = feedback.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback?.reference ?? jni$_.jNullReference; + return _captureFeedback$2( + reference.pointer, + _id_captureFeedback$2 as jni$_.JMethodIDPtr, + _$feedback.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureEnvelope = _class.instanceMethodId( + r'captureEnvelope', + r'(Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureEnvelope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureEnvelope(io.sentry.SentryEnvelope sentryEnvelope, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureEnvelope( + jni$_.JObject sentryEnvelope, + jni$_.JObject? hint, + ) { + final _$sentryEnvelope = sentryEnvelope.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureEnvelope( + reference.pointer, + _id_captureEnvelope as jni$_.JMethodIDPtr, + _$sentryEnvelope.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException = _class.instanceMethodId( + r'captureException', + r'(Ljava/lang/Throwable;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureException( + jni$_.JObject throwable, + jni$_.JObject? hint, + ) { + final _$throwable = throwable.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureException( + reference.pointer, + _id_captureException as jni$_.JMethodIDPtr, + _$throwable.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureException$1 = _class.instanceMethodId( + r'captureException', + r'(Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureException$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureException(java.lang.Throwable throwable, io.sentry.Hint hint, io.sentry.ScopeCallback scopeCallback)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureException$1( + jni$_.JObject throwable, + jni$_.JObject? hint, + jni$_.JObject scopeCallback, + ) { + final _$throwable = throwable.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + return _captureException$1( + reference.pointer, + _id_captureException$1 as jni$_.JMethodIDPtr, + _$throwable.pointer, + _$hint.pointer, + _$scopeCallback.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureUserFeedback = _class.instanceMethodId( + r'captureUserFeedback', + r'(Lio/sentry/UserFeedback;)V', + ); + + static final _captureUserFeedback = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void captureUserFeedback(io.sentry.UserFeedback userFeedback)` + void captureUserFeedback( + jni$_.JObject userFeedback, + ) { + final _$userFeedback = userFeedback.reference; + _captureUserFeedback( + reference.pointer, + _id_captureUserFeedback as jni$_.JMethodIDPtr, + _$userFeedback.pointer) + .check(); + } + + static final _id_startSession = _class.instanceMethodId( + r'startSession', + r'()V', + ); + + static final _startSession = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void startSession()` + void startSession() { + _startSession(reference.pointer, _id_startSession as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_endSession = _class.instanceMethodId( + r'endSession', + r'()V', + ); + + static final _endSession = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void endSession()` + void endSession() { + _endSession(reference.pointer, _id_endSession as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_close = _class.instanceMethodId( + r'close', + r'(Z)V', + ); + + static final _close = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Int32,)>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `public void close(boolean z)` + void close( + bool z, + ) { + _close(reference.pointer, _id_close as jni$_.JMethodIDPtr, z ? 1 : 0) + .check(); + } + + static final _id_close$1 = _class.instanceMethodId( + r'close', + r'()V', + ); + + static final _close$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void close()` + void close$1() { + _close$1(reference.pointer, _id_close$1 as jni$_.JMethodIDPtr).check(); + } + + static final _id_addBreadcrumb = _class.instanceMethodId( + r'addBreadcrumb', + r'(Lio/sentry/Breadcrumb;Lio/sentry/Hint;)V', + ); + + static final _addBreadcrumb = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb, io.sentry.Hint hint)` + void addBreadcrumb( + Breadcrumb breadcrumb, + jni$_.JObject? hint, + ) { + final _$breadcrumb = breadcrumb.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + _addBreadcrumb(reference.pointer, _id_addBreadcrumb as jni$_.JMethodIDPtr, + _$breadcrumb.pointer, _$hint.pointer) + .check(); + } + + static final _id_addBreadcrumb$1 = _class.instanceMethodId( + r'addBreadcrumb', + r'(Lio/sentry/Breadcrumb;)V', + ); + + static final _addBreadcrumb$1 = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void addBreadcrumb(io.sentry.Breadcrumb breadcrumb)` + void addBreadcrumb$1( + Breadcrumb breadcrumb, + ) { + final _$breadcrumb = breadcrumb.reference; + _addBreadcrumb$1(reference.pointer, + _id_addBreadcrumb$1 as jni$_.JMethodIDPtr, _$breadcrumb.pointer) + .check(); + } + + static final _id_setLevel = _class.instanceMethodId( + r'setLevel', + r'(Lio/sentry/SentryLevel;)V', + ); + + static final _setLevel = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setLevel(io.sentry.SentryLevel sentryLevel)` + void setLevel( + jni$_.JObject? sentryLevel, + ) { + final _$sentryLevel = sentryLevel?.reference ?? jni$_.jNullReference; + _setLevel(reference.pointer, _id_setLevel as jni$_.JMethodIDPtr, + _$sentryLevel.pointer) + .check(); + } + + static final _id_setTransaction = _class.instanceMethodId( + r'setTransaction', + r'(Ljava/lang/String;)V', + ); + + static final _setTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setTransaction(java.lang.String string)` + void setTransaction( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _setTransaction(reference.pointer, _id_setTransaction as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_setUser = _class.instanceMethodId( + r'setUser', + r'(Lio/sentry/protocol/User;)V', + ); + + static final _setUser = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setUser(io.sentry.protocol.User user)` + void setUser( + jni$_.JObject? user, + ) { + final _$user = user?.reference ?? jni$_.jNullReference; + _setUser(reference.pointer, _id_setUser as jni$_.JMethodIDPtr, + _$user.pointer) + .check(); + } + + static final _id_setFingerprint = _class.instanceMethodId( + r'setFingerprint', + r'(Ljava/util/List;)V', + ); + + static final _setFingerprint = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setFingerprint(java.util.List list)` + void setFingerprint( + jni$_.JList list, + ) { + final _$list = list.reference; + _setFingerprint(reference.pointer, _id_setFingerprint as jni$_.JMethodIDPtr, + _$list.pointer) + .check(); + } + + static final _id_clearBreadcrumbs = _class.instanceMethodId( + r'clearBreadcrumbs', + r'()V', + ); + + static final _clearBreadcrumbs = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void clearBreadcrumbs()` + void clearBreadcrumbs() { + _clearBreadcrumbs( + reference.pointer, _id_clearBreadcrumbs as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_setTag = _class.instanceMethodId( + r'setTag', + r'(Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _setTag = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void setTag(java.lang.String string, java.lang.String string1)` + void setTag( + jni$_.JString? string, + jni$_.JString? string1, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + _setTag(reference.pointer, _id_setTag as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .check(); + } + + static final _id_removeTag = _class.instanceMethodId( + r'removeTag', + r'(Ljava/lang/String;)V', + ); + + static final _removeTag = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void removeTag(java.lang.String string)` + void removeTag( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _removeTag(reference.pointer, _id_removeTag as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_setExtra = _class.instanceMethodId( + r'setExtra', + r'(Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _setExtra = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void setExtra(java.lang.String string, java.lang.String string1)` + void setExtra( + jni$_.JString? string, + jni$_.JString? string1, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$string1 = string1?.reference ?? jni$_.jNullReference; + _setExtra(reference.pointer, _id_setExtra as jni$_.JMethodIDPtr, + _$string.pointer, _$string1.pointer) + .check(); + } + + static final _id_removeExtra = _class.instanceMethodId( + r'removeExtra', + r'(Ljava/lang/String;)V', + ); + + static final _removeExtra = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void removeExtra(java.lang.String string)` + void removeExtra( + jni$_.JString? string, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + _removeExtra(reference.pointer, _id_removeExtra as jni$_.JMethodIDPtr, + _$string.pointer) + .check(); + } + + static final _id_getLastEventId = _class.instanceMethodId( + r'getLastEventId', + r'()Lio/sentry/protocol/SentryId;', + ); + + static final _getLastEventId = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.protocol.SentryId getLastEventId()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getLastEventId() { + return _getLastEventId( + reference.pointer, _id_getLastEventId as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_pushScope = _class.instanceMethodId( + r'pushScope', + r'()Lio/sentry/ISentryLifecycleToken;', + ); + + static final _pushScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.ISentryLifecycleToken pushScope()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject pushScope() { + return _pushScope(reference.pointer, _id_pushScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_pushIsolationScope = _class.instanceMethodId( + r'pushIsolationScope', + r'()Lio/sentry/ISentryLifecycleToken;', + ); + + static final _pushIsolationScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.ISentryLifecycleToken pushIsolationScope()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject pushIsolationScope() { + return _pushIsolationScope( + reference.pointer, _id_pushIsolationScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_popScope = _class.instanceMethodId( + r'popScope', + r'()V', + ); + + static final _popScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void popScope()` + void popScope() { + _popScope(reference.pointer, _id_popScope as jni$_.JMethodIDPtr).check(); + } + + static final _id_withScope = _class.instanceMethodId( + r'withScope', + r'(Lio/sentry/ScopeCallback;)V', + ); + + static final _withScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void withScope(io.sentry.ScopeCallback scopeCallback)` + void withScope( + jni$_.JObject scopeCallback, + ) { + final _$scopeCallback = scopeCallback.reference; + _withScope(reference.pointer, _id_withScope as jni$_.JMethodIDPtr, + _$scopeCallback.pointer) + .check(); + } + + static final _id_withIsolationScope = _class.instanceMethodId( + r'withIsolationScope', + r'(Lio/sentry/ScopeCallback;)V', + ); + + static final _withIsolationScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void withIsolationScope(io.sentry.ScopeCallback scopeCallback)` + void withIsolationScope( + jni$_.JObject scopeCallback, + ) { + final _$scopeCallback = scopeCallback.reference; + _withIsolationScope( + reference.pointer, + _id_withIsolationScope as jni$_.JMethodIDPtr, + _$scopeCallback.pointer) + .check(); + } + + static final _id_configureScope = _class.instanceMethodId( + r'configureScope', + r'(Lio/sentry/ScopeType;Lio/sentry/ScopeCallback;)V', + ); + + static final _configureScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void configureScope(io.sentry.ScopeType scopeType, io.sentry.ScopeCallback scopeCallback)` + void configureScope( + jni$_.JObject? scopeType, + jni$_.JObject scopeCallback, + ) { + final _$scopeType = scopeType?.reference ?? jni$_.jNullReference; + final _$scopeCallback = scopeCallback.reference; + _configureScope(reference.pointer, _id_configureScope as jni$_.JMethodIDPtr, + _$scopeType.pointer, _$scopeCallback.pointer) + .check(); + } + + static final _id_bindClient = _class.instanceMethodId( + r'bindClient', + r'(Lio/sentry/ISentryClient;)V', + ); + + static final _bindClient = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void bindClient(io.sentry.ISentryClient iSentryClient)` + void bindClient( + jni$_.JObject iSentryClient, + ) { + final _$iSentryClient = iSentryClient.reference; + _bindClient(reference.pointer, _id_bindClient as jni$_.JMethodIDPtr, + _$iSentryClient.pointer) + .check(); + } + + static final _id_isHealthy = _class.instanceMethodId( + r'isHealthy', + r'()Z', + ); + + static final _isHealthy = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public boolean isHealthy()` + bool isHealthy() { + return _isHealthy(reference.pointer, _id_isHealthy as jni$_.JMethodIDPtr) + .boolean; + } + + static final _id_flush = _class.instanceMethodId( + r'flush', + r'(J)V', + ); + + static final _flush = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Int64,)>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, jni$_.JMethodIDPtr, int)>(); + + /// from: `public void flush(long j)` + void flush( + int j, + ) { + _flush(reference.pointer, _id_flush as jni$_.JMethodIDPtr, j).check(); + } + + static final _id_clone = _class.instanceMethodId( + r'clone', + r'()Lio/sentry/IHub;', + ); + + static final _clone = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.IHub clone()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject clone() { + return _clone(reference.pointer, _id_clone as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedScopes = _class.instanceMethodId( + r'forkedScopes', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.IScopes forkedScopes(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject forkedScopes( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedScopes(reference.pointer, + _id_forkedScopes as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedCurrentScope = _class.instanceMethodId( + r'forkedCurrentScope', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedCurrentScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.IScopes forkedCurrentScope(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject forkedCurrentScope( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedCurrentScope(reference.pointer, + _id_forkedCurrentScope as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_forkedRootScopes = _class.instanceMethodId( + r'forkedRootScopes', + r'(Ljava/lang/String;)Lio/sentry/IScopes;', + ); + + static final _forkedRootScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.IScopes forkedRootScopes(java.lang.String string)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject forkedRootScopes( + jni$_.JString string, + ) { + final _$string = string.reference; + return _forkedRootScopes(reference.pointer, + _id_forkedRootScopes as jni$_.JMethodIDPtr, _$string.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_makeCurrent = _class.instanceMethodId( + r'makeCurrent', + r'()Lio/sentry/ISentryLifecycleToken;', + ); + + static final _makeCurrent = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.ISentryLifecycleToken makeCurrent()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject makeCurrent() { + return _makeCurrent( + reference.pointer, _id_makeCurrent as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getScope = _class.instanceMethodId( + r'getScope', + r'()Lio/sentry/IScope;', + ); + + static final _getScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.IScope getScope()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getScope() { + return _getScope(reference.pointer, _id_getScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getIsolationScope = _class.instanceMethodId( + r'getIsolationScope', + r'()Lio/sentry/IScope;', + ); + + static final _getIsolationScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.IScope getIsolationScope()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getIsolationScope() { + return _getIsolationScope( + reference.pointer, _id_getIsolationScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getGlobalScope = _class.instanceMethodId( + r'getGlobalScope', + r'()Lio/sentry/IScope;', + ); + + static final _getGlobalScope = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.IScope getGlobalScope()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getGlobalScope() { + return _getGlobalScope( + reference.pointer, _id_getGlobalScope as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_getParentScopes = _class.instanceMethodId( + r'getParentScopes', + r'()Lio/sentry/IScopes;', + ); + + static final _getParentScopes = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.IScopes getParentScopes()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getParentScopes() { + return _getParentScopes( + reference.pointer, _id_getParentScopes as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_isAncestorOf = _class.instanceMethodId( + r'isAncestorOf', + r'(Lio/sentry/IScopes;)Z', + ); + + static final _isAncestorOf = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallBooleanMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public boolean isAncestorOf(io.sentry.IScopes iScopes)` + bool isAncestorOf( + jni$_.JObject? iScopes, + ) { + final _$iScopes = iScopes?.reference ?? jni$_.jNullReference; + return _isAncestorOf(reference.pointer, + _id_isAncestorOf as jni$_.JMethodIDPtr, _$iScopes.pointer) + .boolean; + } + + static final _id_captureTransaction = _class.instanceMethodId( + r'captureTransaction', + r'(Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureTransaction(io.sentry.protocol.SentryTransaction sentryTransaction, io.sentry.TraceContext traceContext, io.sentry.Hint hint, io.sentry.ProfilingTraceData profilingTraceData)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureTransaction( + jni$_.JObject sentryTransaction, + jni$_.JObject? traceContext, + jni$_.JObject? hint, + jni$_.JObject? profilingTraceData, + ) { + final _$sentryTransaction = sentryTransaction.reference; + final _$traceContext = traceContext?.reference ?? jni$_.jNullReference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + final _$profilingTraceData = + profilingTraceData?.reference ?? jni$_.jNullReference; + return _captureTransaction( + reference.pointer, + _id_captureTransaction as jni$_.JMethodIDPtr, + _$sentryTransaction.pointer, + _$traceContext.pointer, + _$hint.pointer, + _$profilingTraceData.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_captureProfileChunk = _class.instanceMethodId( + r'captureProfileChunk', + r'(Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureProfileChunk = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureProfileChunk(io.sentry.ProfileChunk profileChunk)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureProfileChunk( + jni$_.JObject profileChunk, + ) { + final _$profileChunk = profileChunk.reference; + return _captureProfileChunk( + reference.pointer, + _id_captureProfileChunk as jni$_.JMethodIDPtr, + _$profileChunk.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startTransaction = _class.instanceMethodId( + r'startTransaction', + r'(Lio/sentry/TransactionContext;Lio/sentry/TransactionOptions;)Lio/sentry/ITransaction;', + ); + + static final _startTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.ITransaction startTransaction(io.sentry.TransactionContext transactionContext, io.sentry.TransactionOptions transactionOptions)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject startTransaction( + jni$_.JObject transactionContext, + jni$_.JObject transactionOptions, + ) { + final _$transactionContext = transactionContext.reference; + final _$transactionOptions = transactionOptions.reference; + return _startTransaction( + reference.pointer, + _id_startTransaction as jni$_.JMethodIDPtr, + _$transactionContext.pointer, + _$transactionOptions.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_startProfiler = _class.instanceMethodId( + r'startProfiler', + r'()V', + ); + + static final _startProfiler = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void startProfiler()` + void startProfiler() { + _startProfiler(reference.pointer, _id_startProfiler as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_stopProfiler = _class.instanceMethodId( + r'stopProfiler', + r'()V', + ); + + static final _stopProfiler = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void stopProfiler()` + void stopProfiler() { + _stopProfiler(reference.pointer, _id_stopProfiler as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_setSpanContext = _class.instanceMethodId( + r'setSpanContext', + r'(Ljava/lang/Throwable;Lio/sentry/ISpan;Ljava/lang/String;)V', + ); + + static final _setSpanContext = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public void setSpanContext(java.lang.Throwable throwable, io.sentry.ISpan iSpan, java.lang.String string)` + void setSpanContext( + jni$_.JObject throwable, + jni$_.JObject iSpan, + jni$_.JString string, + ) { + final _$throwable = throwable.reference; + final _$iSpan = iSpan.reference; + final _$string = string.reference; + _setSpanContext(reference.pointer, _id_setSpanContext as jni$_.JMethodIDPtr, + _$throwable.pointer, _$iSpan.pointer, _$string.pointer) + .check(); + } + + static final _id_getSpan = _class.instanceMethodId( + r'getSpan', + r'()Lio/sentry/ISpan;', + ); + + static final _getSpan = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.ISpan getSpan()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getSpan() { + return _getSpan(reference.pointer, _id_getSpan as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_setActiveSpan = _class.instanceMethodId( + r'setActiveSpan', + r'(Lio/sentry/ISpan;)V', + ); + + static final _setActiveSpan = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public void setActiveSpan(io.sentry.ISpan iSpan)` + void setActiveSpan( + jni$_.JObject? iSpan, + ) { + final _$iSpan = iSpan?.reference ?? jni$_.jNullReference; + _setActiveSpan(reference.pointer, _id_setActiveSpan as jni$_.JMethodIDPtr, + _$iSpan.pointer) + .check(); + } + + static final _id_getTransaction = _class.instanceMethodId( + r'getTransaction', + r'()Lio/sentry/ITransaction;', + ); + + static final _getTransaction = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.ITransaction getTransaction()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getTransaction() { + return _getTransaction( + reference.pointer, _id_getTransaction as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_getOptions = _class.instanceMethodId( + r'getOptions', + r'()Lio/sentry/SentryOptions;', + ); + + static final _getOptions = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.SentryOptions getOptions()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject getOptions() { + return _getOptions(reference.pointer, _id_getOptions as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } + + static final _id_isCrashedLastRun = _class.instanceMethodId( + r'isCrashedLastRun', + r'()Ljava/lang/Boolean;', + ); + + static final _isCrashedLastRun = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public java.lang.Boolean isCrashedLastRun()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JBoolean? isCrashedLastRun() { + return _isCrashedLastRun( + reference.pointer, _id_isCrashedLastRun as jni$_.JMethodIDPtr) + .object(const jni$_.JBooleanNullableType()); + } + + static final _id_reportFullyDisplayed = _class.instanceMethodId( + r'reportFullyDisplayed', + r'()V', + ); + + static final _reportFullyDisplayed = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public void reportFullyDisplayed()` + void reportFullyDisplayed() { + _reportFullyDisplayed( + reference.pointer, _id_reportFullyDisplayed as jni$_.JMethodIDPtr) + .check(); + } + + static final _id_continueTrace = _class.instanceMethodId( + r'continueTrace', + r'(Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;', + ); + + static final _continueTrace = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.TransactionContext continueTrace(java.lang.String string, java.util.List list)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? continueTrace( + jni$_.JString? string, + jni$_.JList? list, + ) { + final _$string = string?.reference ?? jni$_.jNullReference; + final _$list = list?.reference ?? jni$_.jNullReference; + return _continueTrace( + reference.pointer, + _id_continueTrace as jni$_.JMethodIDPtr, + _$string.pointer, + _$list.pointer) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_getTraceparent = _class.instanceMethodId( + r'getTraceparent', + r'()Lio/sentry/SentryTraceHeader;', + ); + + static final _getTraceparent = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.SentryTraceHeader getTraceparent()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getTraceparent() { + return _getTraceparent( + reference.pointer, _id_getTraceparent as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_getBaggage = _class.instanceMethodId( + r'getBaggage', + r'()Lio/sentry/BaggageHeader;', + ); + + static final _getBaggage = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.BaggageHeader getBaggage()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getBaggage() { + return _getBaggage(reference.pointer, _id_getBaggage as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_captureCheckIn = _class.instanceMethodId( + r'captureCheckIn', + r'(Lio/sentry/CheckIn;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureCheckIn = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs<(jni$_.Pointer,)>)>>( + 'globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function(jni$_.Pointer, + jni$_.JMethodIDPtr, jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureCheckIn(io.sentry.CheckIn checkIn)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureCheckIn( + jni$_.JObject checkIn, + ) { + final _$checkIn = checkIn.reference; + return _captureCheckIn(reference.pointer, + _id_captureCheckIn as jni$_.JMethodIDPtr, _$checkIn.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_getRateLimiter = _class.instanceMethodId( + r'getRateLimiter', + r'()Lio/sentry/transport/RateLimiter;', + ); + + static final _getRateLimiter = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.transport.RateLimiter getRateLimiter()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject? getRateLimiter() { + return _getRateLimiter( + reference.pointer, _id_getRateLimiter as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectNullableType()); + } + + static final _id_captureReplay = _class.instanceMethodId( + r'captureReplay', + r'(Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;', + ); + + static final _captureReplay = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer + )>)>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer)>(); + + /// from: `public io.sentry.protocol.SentryId captureReplay(io.sentry.SentryReplayEvent sentryReplayEvent, io.sentry.Hint hint)` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject captureReplay( + jni$_.JObject sentryReplayEvent, + jni$_.JObject? hint, + ) { + final _$sentryReplayEvent = sentryReplayEvent.reference; + final _$hint = hint?.reference ?? jni$_.jNullReference; + return _captureReplay( + reference.pointer, + _id_captureReplay as jni$_.JMethodIDPtr, + _$sentryReplayEvent.pointer, + _$hint.pointer) + .object(const jni$_.JObjectType()); + } + + static final _id_logger = _class.instanceMethodId( + r'logger', + r'()Lio/sentry/logger/ILoggerApi;', + ); + + static final _logger = jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>>('globalEnv_CallObjectMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + )>(); + + /// from: `public io.sentry.logger.ILoggerApi logger()` + /// The returned object must be released after use, by calling the [release] method. + jni$_.JObject logger() { + return _logger(reference.pointer, _id_logger as jni$_.JMethodIDPtr) + .object(const jni$_.JObjectType()); + } +} + +final class $ScopesAdapter$NullableType extends jni$_.JObjType { + @jni$_.internal + const $ScopesAdapter$NullableType(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/ScopesAdapter;'; + + @jni$_.internal + @core$_.override + ScopesAdapter? fromReference(jni$_.JReference reference) => reference.isNull + ? null + : ScopesAdapter.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => this; + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($ScopesAdapter$NullableType).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($ScopesAdapter$NullableType) && + other is $ScopesAdapter$NullableType; + } +} + +final class $ScopesAdapter$Type extends jni$_.JObjType { + @jni$_.internal + const $ScopesAdapter$Type(); + + @jni$_.internal + @core$_.override + String get signature => r'Lio/sentry/ScopesAdapter;'; + + @jni$_.internal + @core$_.override + ScopesAdapter fromReference(jni$_.JReference reference) => + ScopesAdapter.fromReference( + reference, + ); + @jni$_.internal + @core$_.override + jni$_.JObjType get superType => const jni$_.JObjectNullableType(); + + @jni$_.internal + @core$_.override + jni$_.JObjType get nullableType => + const $ScopesAdapter$NullableType(); + + @jni$_.internal + @core$_.override + final superCount = 1; + + @core$_.override + int get hashCode => ($ScopesAdapter$Type).hashCode; + + @core$_.override + bool operator ==(Object other) { + return other.runtimeType == ($ScopesAdapter$Type) && + other is $ScopesAdapter$Type; + } +} + /// from: `android.graphics.Bitmap$CompressFormat` class Bitmap$CompressFormat extends jni$_.JObject { @jni$_.internal diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 5288c71d5e..61e9164ea7 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:typed_data'; import 'package:jni/jni.dart'; @@ -9,7 +8,6 @@ import '../../../sentry_flutter.dart'; import '../../replay/scheduled_recorder_config.dart'; import '../native_app_start.dart'; import '../sentry_native_channel.dart'; -import '../utils/data_normalizer.dart'; import '../utils/utf8_json.dart'; import 'android_envelope_sender.dart'; import 'android_replay_recorder.dart'; @@ -223,17 +221,21 @@ class SentryNativeJava extends SentryNativeChannel { @override void addBreadcrumb(Breadcrumb breadcrumb) { - JByteArray? breadcrumbBytes; + native.Breadcrumb? nativeBreadcrumb; + JObject? nativeOptions; tryCatchSync('addBreadcrumb', () { - final jsonString = json.encode(breadcrumb.toJson()); - final bytes = utf8.encode(jsonString); - breadcrumbBytes = JByteArray.from(bytes); + nativeOptions = native.ScopesAdapter.getInstance()?.getOptions(); + if (nativeOptions == null) return; - native.SentryFlutterPlugin.Companion - .addBreadcrumbAsBytes(breadcrumbBytes!); + nativeBreadcrumb = native.Breadcrumb.fromMap( + _dartToJMap(breadcrumb.toJson()), nativeOptions!); + if (nativeBreadcrumb == null) return; + + native.Sentry.addBreadcrumb$1(nativeBreadcrumb!); }, finallyFn: () { - breadcrumbBytes?.release(); + nativeOptions?.release(); + nativeBreadcrumb?.release(); }); } @@ -242,3 +244,28 @@ class SentryNativeJava extends SentryNativeChannel { native.Sentry.clearBreadcrumbs(); }); } + +JObject? _dartToJObject(Object? value) => switch (value) { + null => null, + String s => s.toJString(), + bool b => b.toJBoolean(), + int i => i.toJLong(), // safer for 64-bit + double d => d.toJDouble(), + List l => _dartToJList(l), + Map m => _dartToJMap(m), + _ => null + }; + +JList _dartToJList(List values) { + final jlist = JList.array(JObject.nullableType); + jlist.addAll(values.map(_dartToJObject)); + return jlist; +} + +JMap _dartToJMap(Map json) { + final jmap = JMap.hash(JString.type, JObject.nullableType); + for (final entry in json.entries) { + jmap[entry.key.toJString()] = _dartToJObject(entry.value); + } + return jmap; +} From 5cc9c78e2fb2ec3a6c3711e332fecdda605011ff Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Fri, 17 Oct 2025 12:50:45 +0200 Subject: [PATCH 70/78] Update --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index fdddd4beb9..f834947b5f 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -15,12 +15,8 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.sentry.Breadcrumb import io.sentry.DateUtils -import io.sentry.JsonObjectDeserializer -import io.sentry.JsonObjectReader -import io.sentry.ObjectReader import io.sentry.ScopesAdapter import io.sentry.Sentry -import io.sentry.SentryOptions import io.sentry.android.core.InternalSentrySdk import io.sentry.android.core.SentryAndroid import io.sentry.android.core.SentryAndroidOptions @@ -33,7 +29,6 @@ import io.sentry.protocol.User import io.sentry.transport.CurrentDateProvider import org.json.JSONObject import org.json.JSONArray -import java.io.StringReader import java.lang.ref.WeakReference import kotlin.math.roundToInt From aacf1ffcaa7923962c66159a0f59e8ceedf31cb2 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 20 Oct 2025 09:30:03 +0200 Subject: [PATCH 71/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 2 +- .../flutter/lib/src/native/java/binding.dart | 27 +++++++++---------- .../src/native/java/sentry_native_java.dart | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index c16aa31892..b3a3eb128c 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -290,7 +290,7 @@ class SentryFlutterPlugin : @Suppress("unused") // Used by native/jni bindings @JvmStatic - fun nativeCrash() { + fun crash() { val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") val mainThread = Looper.getMainLooper().thread mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception) diff --git a/packages/flutter/lib/src/native/java/binding.dart b/packages/flutter/lib/src/native/java/binding.dart index f9730b691a..9b59a003b3 100644 --- a/packages/flutter/lib/src/native/java/binding.dart +++ b/packages/flutter/lib/src/native/java/binding.dart @@ -1305,12 +1305,12 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } - static final _id_nativeCrash = _class.instanceMethodId( - r'nativeCrash', + static final _id_crash = _class.instanceMethodId( + r'crash', r'()V', ); - static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + static final _crash = jni$_.ProtectedJniExtensions.lookup< jni$_.NativeFunction< jni$_.JThrowablePtr Function( jni$_.Pointer, @@ -1322,10 +1322,9 @@ class SentryFlutterPlugin$Companion extends jni$_.JObject { jni$_.JMethodIDPtr, )>(); - /// from: `public final void nativeCrash()` - void nativeCrash() { - _nativeCrash(reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) - .check(); + /// from: `public final void crash()` + void crash() { + _crash(reference.pointer, _id_crash as jni$_.JMethodIDPtr).check(); } static final _id_getDisplayRefreshRate = _class.instanceMethodId( @@ -1839,12 +1838,12 @@ class SentryFlutterPlugin extends jni$_.JObject { .object(const $ReplayIntegration$NullableType()); } - static final _id_nativeCrash = _class.staticMethodId( - r'nativeCrash', + static final _id_crash = _class.staticMethodId( + r'crash', r'()V', ); - static final _nativeCrash = jni$_.ProtectedJniExtensions.lookup< + static final _crash = jni$_.ProtectedJniExtensions.lookup< jni$_.NativeFunction< jni$_.JThrowablePtr Function( jni$_.Pointer, @@ -1856,11 +1855,9 @@ class SentryFlutterPlugin extends jni$_.JObject { jni$_.JMethodIDPtr, )>(); - /// from: `static public final void nativeCrash()` - static void nativeCrash() { - _nativeCrash( - _class.reference.pointer, _id_nativeCrash as jni$_.JMethodIDPtr) - .check(); + /// from: `static public final void crash()` + static void crash() { + _crash(_class.reference.pointer, _id_crash as jni$_.JMethodIDPtr).check(); } static final _id_getDisplayRefreshRate = _class.staticMethodId( diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 817d51c00d..bbf2e2a265 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -201,7 +201,7 @@ class SentryNativeJava extends SentryNativeChannel { @override void nativeCrash() { - native.SentryFlutterPlugin.Companion.nativeCrash(); + native.SentryFlutterPlugin.Companion.crash(); } @override From 714279ea20b70a2cd8ac1f365c999bfd4946b81f Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 20 Oct 2025 10:07:20 +0200 Subject: [PATCH 72/78] Update --- packages/flutter/ffi-cocoa.yaml | 8 ++ .../sentry_flutter/SentryFlutterPlugin.swift | 12 --- .../sentry_flutter_objc/SentryFlutterPlugin.h | 3 - .../flutter/lib/src/native/cocoa/binding.dart | 81 +++++++++++++------ .../src/native/cocoa/sentry_native_cocoa.dart | 20 ++--- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/packages/flutter/ffi-cocoa.yaml b/packages/flutter/ffi-cocoa.yaml index 72cf5a88e0..bc1a60cdcf 100644 --- a/packages/flutter/ffi-cocoa.yaml +++ b/packages/flutter/ffi-cocoa.yaml @@ -19,8 +19,16 @@ objc-interfaces: - PrivateSentrySDKOnly - SentryId - SentryFlutterPlugin + - SentrySDK module: 'SentryId': 'Sentry' + 'SentrySDK': 'Sentry' + member-filter: + SentrySDK: + include: + - 'crash' + - 'pauseAppHangTracking' + - 'resumeAppHangTracking' preamble: | // ignore_for_file: type=lint, unused_element diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift index 8e2e644e69..ca10f8140a 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift @@ -520,18 +520,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin { #endif } - @objc public class func nativeCrash() { - SentrySDK.crash() - } - - @objc public class func pauseAppHangTracking() { - SentrySDK.pauseAppHangTracking() - } - - @objc public class func resumeAppHangTracking() { - SentrySDK.resumeAppHangTracking() - } - @objc(loadDebugImagesAsBytes:) public class func loadDebugImagesAsBytes(instructionAddresses: Set) -> NSData? { var debugImages: [DebugMeta] = [] diff --git a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h index 61af58310d..6f2e25eb54 100644 --- a/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h +++ b/packages/flutter/ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h @@ -8,8 +8,5 @@ + (nullable NSData *)fetchNativeAppStartAsBytes; + (nullable NSData *)loadContextsAsBytes; + (nullable NSData *)loadDebugImagesAsBytes:(NSSet *)instructionAddresses; -+ (void)nativeCrash; -+ (void)pauseAppHangTracking; -+ (void)resumeAppHangTracking; @end #endif diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 73d693b44d..d39050185c 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1120,15 +1120,8 @@ class SentryId$1 extends objc.NSObject { factory SentryId$1() => new$(); } -late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); -late final _sel_getDisplayRefreshRate = - objc.registerName("getDisplayRefreshRate"); -late final _sel_fetchNativeAppStartAsBytes = - objc.registerName("fetchNativeAppStartAsBytes"); -late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); -late final _sel_loadDebugImagesAsBytes_ = - objc.registerName("loadDebugImagesAsBytes:"); -late final _sel_nativeCrash = objc.registerName("nativeCrash"); +late final _class_SentrySDK = objc.getClass("Sentry.SentrySDK"); +late final _sel_crash = objc.registerName("crash"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -1142,6 +1135,59 @@ late final _sel_pauseAppHangTracking = late final _sel_resumeAppHangTracking = objc.registerName("resumeAppHangTracking"); +/// The main entry point for the Sentry SDK. +/// We recommend using start(configureOptions:) to initialize Sentry. +class SentrySDK extends objc.NSObject { + SentrySDK._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [SentrySDK] that points to the same underlying object as [other]. + SentrySDK.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [SentrySDK] that wraps the given raw object pointer. + SentrySDK.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [SentrySDK]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_19nvye5( + obj.ref.pointer, _sel_isKindOfClass_, _class_SentrySDK); + } + + /// This forces a crash, useful to test the SentryCrash integration. + /// note: + /// The SDK can’t report a crash when a debugger is attached. Your application needs to run + /// without a debugger attached to capture the crash and send it to Sentry the next time you launch + /// your application. + static void crash() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_crash); + } + + /// Pauses sending detected app hangs to Sentry. + /// This method doesn’t close the detection of app hangs. Instead, the app hang detection + /// will ignore detected app hangs until you call resumeAppHangTracking. + static void pauseAppHangTracking() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_pauseAppHangTracking); + } + + /// Resumes sending detected app hangs to Sentry. + static void resumeAppHangTracking() { + _objc_msgSend_1pl9qdv(_class_SentrySDK, _sel_resumeAppHangTracking); + } +} + +late final _class_SentryFlutterPlugin = objc.getClass("SentryFlutterPlugin"); +late final _sel_getDisplayRefreshRate = + objc.registerName("getDisplayRefreshRate"); +late final _sel_fetchNativeAppStartAsBytes = + objc.registerName("fetchNativeAppStartAsBytes"); +late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes"); +late final _sel_loadDebugImagesAsBytes_ = + objc.registerName("loadDebugImagesAsBytes:"); + /// SentryFlutterPlugin class SentryFlutterPlugin extends objc.NSObject { SentryFlutterPlugin._(ffi.Pointer pointer, @@ -1199,23 +1245,6 @@ class SentryFlutterPlugin extends objc.NSObject { : objc.NSData.castFromPointer(_ret, retain: true, release: true); } - /// nativeCrash - static void nativeCrash() { - _objc_msgSend_1pl9qdv(_class_SentryFlutterPlugin, _sel_nativeCrash); - } - - /// pauseAppHangTracking - static void pauseAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_pauseAppHangTracking); - } - - /// resumeAppHangTracking - static void resumeAppHangTracking() { - _objc_msgSend_1pl9qdv( - _class_SentryFlutterPlugin, _sel_resumeAppHangTracking); - } - /// init SentryFlutterPlugin init() { objc.checkOsVersionInternal('SentryFlutterPlugin.init', diff --git a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart index 3901503d4a..a156be1e33 100644 --- a/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart +++ b/packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart @@ -178,21 +178,15 @@ class SentryNativeCocoa extends SentryNativeChannel { ); @override - void nativeCrash() { - cocoa.SentryFlutterPlugin.nativeCrash(); - } + void nativeCrash() => cocoa.SentrySDK.crash(); @override - void pauseAppHangTracking() { - tryCatchSync('pauseAppHangTracking', () { - cocoa.SentryFlutterPlugin.pauseAppHangTracking(); - }); - } + void pauseAppHangTracking() => tryCatchSync('pauseAppHangTracking', () { + cocoa.SentrySDK.pauseAppHangTracking(); + }); @override - void resumeAppHangTracking() { - tryCatchSync('resumeAppHangTracking', () { - cocoa.SentryFlutterPlugin.resumeAppHangTracking(); - }); - } + void resumeAppHangTracking() => tryCatchSync('resumeAppHangTracking', () { + cocoa.SentrySDK.resumeAppHangTracking(); + }); } From b82a031dd29fdff17471aa6c16355b2424c85fc6 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Mon, 20 Oct 2025 10:14:03 +0200 Subject: [PATCH 73/78] Update --- packages/flutter/lib/src/native/cocoa/binding.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 63c30c0f46..65fb990cd2 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1145,7 +1145,6 @@ interface class SentrySerializable extends objc.ObjCProtocolBase } late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs"); -late final _sel_crash = objc.registerName("crash"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< From 1e52e9fb3b335ec803f033f9899c1bb232c2afc5 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 23 Oct 2025 14:13:39 +0200 Subject: [PATCH 74/78] Update --- .../io/sentry/flutter/SentryFlutterPlugin.kt | 115 ------------------ .../flutter/lib/src/native/cocoa/binding.dart | 1 - 2 files changed, 116 deletions(-) diff --git a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 586bc70ab8..5ad695fdb5 100644 --- a/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -268,121 +268,6 @@ class SentryFlutterPlugin : @JvmStatic fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay - @Suppress("unused") // Used by native/jni bindings - @JvmStatic - fun crash() { - val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") - val mainThread = Looper.getMainLooper().thread - mainThread.uncaughtExceptionHandler?.uncaughtException(mainThread, exception) - mainThread.join(NATIVE_CRASH_WAIT_TIME) - } - - @Suppress("unused") // Used by native/jni bindings - @JvmStatic - fun getDisplayRefreshRate(): Int? { - var refreshRate: Int? = null - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val display = activity?.get()?.display - if (display != null) { - refreshRate = display.refreshRate.toInt() - } - } else { - val display = - activity - ?.get() - ?.window - ?.windowManager - ?.defaultDisplay - if (display != null) { - refreshRate = display.refreshRate.toInt() - } - } - - return refreshRate - } - - @Suppress("unused", "ReturnCount", "TooGenericExceptionCaught") // Used by native/jni bindings - @JvmStatic - fun fetchNativeAppStartAsBytes(): ByteArray? { - if (!sentryFlutter.autoPerformanceTracingEnabled) { - return null - } - - val appStartMetrics = AppStartMetrics.getInstance() - - if (!appStartMetrics.isAppLaunchedInForeground || - appStartMetrics.appStartTimeSpan.durationMs > APP_START_MAX_DURATION_MS - ) { - Log.w( - "Sentry", - "Invalid app start data: app not launched in foreground or app start took too long (>60s)", - ) - return null - } - - val appStartTimeSpan = appStartMetrics.appStartTimeSpan - val appStartTime = appStartTimeSpan.startTimestamp - val isColdStart = appStartMetrics.appStartType == AppStartMetrics.AppStartType.COLD - - if (appStartTime == null) { - Log.w("Sentry", "App start won't be sent due to missing appStartTime") - return null - } - - val appStartTimeMillis = DateUtils.nanosToMillis(appStartTime.nanoTimestamp().toDouble()) - val item = - mutableMapOf( - "pluginRegistrationTime" to pluginRegistrationTime, - "appStartTime" to appStartTimeMillis, - "isColdStart" to isColdStart, - ) - - val androidNativeSpans = mutableMapOf() - - val processInitSpan = - TimeSpan().apply { - description = "Process Initialization" - setStartUnixTimeMs(appStartTimeSpan.startTimestampMs) - setStartedAt(appStartTimeSpan.startUptimeMs) - setStoppedAt(appStartMetrics.classLoadedUptimeMs) - } - addTimeSpanToMap(processInitSpan, androidNativeSpans) - - val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan - addTimeSpanToMap(applicationOnCreateSpan, androidNativeSpans) - - val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans - contentProviderSpans.forEach { span -> - addTimeSpanToMap(span, androidNativeSpans) - } - - appStartMetrics.activityLifecycleTimeSpans.forEach { span -> - addTimeSpanToMap(span.onCreate, androidNativeSpans) - addTimeSpanToMap(span.onStart, androidNativeSpans) - } - - item["nativeSpanTimes"] = androidNativeSpans - - val json = JSONObject(item).toString() - return json.toByteArray(Charsets.UTF_8) - } - - private fun addTimeSpanToMap( - span: TimeSpan, - map: MutableMap, - ) { - if (span.startTimestamp == null) return - - span.description?.let { description -> - map[description] = - mapOf( - "startTimestampMsSinceEpoch" to span.startTimestampMs, - "stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs, - ) - } - } - @JvmStatic fun crash() { val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException") diff --git a/packages/flutter/lib/src/native/cocoa/binding.dart b/packages/flutter/lib/src/native/cocoa/binding.dart index 63c30c0f46..65fb990cd2 100644 --- a/packages/flutter/lib/src/native/cocoa/binding.dart +++ b/packages/flutter/lib/src/native/cocoa/binding.dart @@ -1145,7 +1145,6 @@ interface class SentrySerializable extends objc.ObjCProtocolBase } late final _sel_clearBreadcrumbs = objc.registerName("clearBreadcrumbs"); -late final _sel_crash = objc.registerName("crash"); final _objc_msgSend_1pl9qdv = objc.msgSendPointer .cast< ffi.NativeFunction< From a95a35969f1230538101d610c38a33500dc0c993 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 23 Oct 2025 14:52:01 +0200 Subject: [PATCH 75/78] Update --- .../src/native/java/sentry_native_java.dart | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 1d5a1c5093..b3ad895380 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:ffi/ffi.dart'; import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; @@ -245,21 +246,18 @@ class SentryNativeJava extends SentryNativeChannel { @override void addBreadcrumb(Breadcrumb breadcrumb) { - native.Breadcrumb? nativeBreadcrumb; - JObject? nativeOptions; - tryCatchSync('addBreadcrumb', () { - nativeOptions = native.ScopesAdapter.getInstance()?.getOptions(); - if (nativeOptions == null) return; - - nativeBreadcrumb = native.Breadcrumb.fromMap( - _dartToJMap(breadcrumb.toJson()), nativeOptions!); - if (nativeBreadcrumb == null) return; - - native.Sentry.addBreadcrumb$1(nativeBreadcrumb!); - }, finallyFn: () { - nativeOptions?.release(); - nativeBreadcrumb?.release(); + using((arena) { + final nativeOptions = native.ScopesAdapter.getInstance()?.getOptions() + ?..releasedBy(arena); + if (nativeOptions == null) return; + // Wrap the entire conversion in arena to auto-cleanup all JObjects + final jMap = _dartToJMap(breadcrumb.toJson(), arena); + final nativeBreadcrumb = native.Breadcrumb.fromMap(jMap, nativeOptions) + ?..releasedBy(arena); + if (nativeBreadcrumb == null) return; + native.Sentry.addBreadcrumb$1(nativeBreadcrumb); + }); }); } @@ -269,27 +267,36 @@ class SentryNativeJava extends SentryNativeChannel { }); } -JObject? _dartToJObject(Object? value) => switch (value) { +JObject? _dartToJObject(Object? value, Arena arena) => switch (value) { null => null, - String s => s.toJString(), - bool b => b.toJBoolean(), - int i => i.toJLong(), // safer for 64-bit - double d => d.toJDouble(), - List l => _dartToJList(l), - Map m => _dartToJMap(m), + String s => s.toJString()..releasedBy(arena), + bool b => b.toJBoolean()..releasedBy(arena), + int i => i.toJLong()..releasedBy(arena), + double d => d.toJDouble()..releasedBy(arena), + List l => _dartToJList(l, arena), + Map m => _dartToJMap(m, arena), _ => null }; -JList _dartToJList(List values) { - final jlist = JList.array(JObject.nullableType); - jlist.addAll(values.map(_dartToJObject)); +JList _dartToJList(List values, Arena arena) { + final jlist = JList.array(JObject.nullableType)..releasedBy(arena); + + for (final value in values) { + final jObj = _dartToJObject(value, arena); + jlist.add(jObj); + } + return jlist; } -JMap _dartToJMap(Map json) { - final jmap = JMap.hash(JString.type, JObject.nullableType); +JMap _dartToJMap(Map json, Arena arena) { + final jmap = JMap.hash(JString.type, JObject.nullableType)..releasedBy(arena); + for (final entry in json.entries) { - jmap[entry.key.toJString()] = _dartToJObject(entry.value); + final key = entry.key.toJString()..releasedBy(arena); + final value = _dartToJObject(entry.value, arena); + jmap[key] = value; } + return jmap; } From 0a1009883acb2c6501b248bc294f502dd21682c7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 23 Oct 2025 14:57:09 +0200 Subject: [PATCH 76/78] Fix analyze --- packages/flutter/lib/src/native/java/sentry_native_java.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index b3ad895380..cce5ba88d8 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:ffi/ffi.dart'; import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; From ca1fed5f1c73eb73997be7cdad0fba84df6a41bc Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 23 Oct 2025 14:57:40 +0200 Subject: [PATCH 77/78] Remove comment --- packages/flutter/lib/src/native/java/sentry_native_java.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index cce5ba88d8..4168f2e575 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -250,7 +250,6 @@ class SentryNativeJava extends SentryNativeChannel { final nativeOptions = native.ScopesAdapter.getInstance()?.getOptions() ?..releasedBy(arena); if (nativeOptions == null) return; - // Wrap the entire conversion in arena to auto-cleanup all JObjects final jMap = _dartToJMap(breadcrumb.toJson(), arena); final nativeBreadcrumb = native.Breadcrumb.fromMap(jMap, nativeOptions) ?..releasedBy(arena); From 3729c2b8bd4f832c117f3f6005ceb72337b134fa Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Thu, 23 Oct 2025 15:29:03 +0200 Subject: [PATCH 78/78] Update formatting --- .../src/native/java/sentry_native_java.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/native/java/sentry_native_java.dart b/packages/flutter/lib/src/native/java/sentry_native_java.dart index 4168f2e575..77305db9b9 100644 --- a/packages/flutter/lib/src/native/java/sentry_native_java.dart +++ b/packages/flutter/lib/src/native/java/sentry_native_java.dart @@ -244,20 +244,20 @@ class SentryNativeJava extends SentryNativeChannel { } @override - void addBreadcrumb(Breadcrumb breadcrumb) { - tryCatchSync('addBreadcrumb', () { - using((arena) { - final nativeOptions = native.ScopesAdapter.getInstance()?.getOptions() - ?..releasedBy(arena); - if (nativeOptions == null) return; - final jMap = _dartToJMap(breadcrumb.toJson(), arena); - final nativeBreadcrumb = native.Breadcrumb.fromMap(jMap, nativeOptions) - ?..releasedBy(arena); - if (nativeBreadcrumb == null) return; - native.Sentry.addBreadcrumb$1(nativeBreadcrumb); + void addBreadcrumb(Breadcrumb breadcrumb) => + tryCatchSync('addBreadcrumb', () { + using((arena) { + final nativeOptions = native.ScopesAdapter.getInstance()?.getOptions() + ?..releasedBy(arena); + if (nativeOptions == null) return; + final jMap = _dartToJMap(breadcrumb.toJson(), arena); + final nativeBreadcrumb = + native.Breadcrumb.fromMap(jMap, nativeOptions) + ?..releasedBy(arena); + if (nativeBreadcrumb == null) return; + native.Sentry.addBreadcrumb$1(nativeBreadcrumb); + }); }); - }); - } @override void clearBreadcrumbs() => tryCatchSync('clearBreadcrumbs', () {