Skip to content

Commit c0dde50

Browse files
authored
Refactor: move native breadcrumbs sync to FFI/JNI (#3293)
* Update * Update * Update * Update * Update * Update * Configure diagnostic log * Update log messages * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Fix test * Update * Update * Update * Add automatedTestMode option * Update * Fix web tests * Update * Update * Add close * Review * Review * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Fix tests * Update * Update * Update * Update * Breadcrumb support * Update * Update * Update * Update * Fix unit tests * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Fix analyze * Remove comment * Update formatting
1 parent ad121c0 commit c0dde50

File tree

20 files changed

+6974
-147
lines changed

20 files changed

+6974
-147
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
### Enhancements
1111

12+
- Refactor native breadcrumbs sync to use FFI/JNI ([#3293](https://github.com/getsentry/sentry-dart/pull/3293/))
1213
- Refactor app hang and crash apis to use FFI/JNI ([#3289](https://github.com/getsentry/sentry-dart/pull/3289/))
1314
- Refactor `AndroidReplayRecorder` to use the new worker isolate api ([#3296](https://github.com/getsentry/sentry-dart/pull/3296/))
1415
- Refactor fetching app start and display refresh rate to use FFI and JNI ([#3288](https://github.com/getsentry/sentry-dart/pull/3288/))

packages/dart/lib/src/scope_observer.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ abstract class ScopeObserver {
77
Future<void> setContexts(String key, dynamic value);
88
Future<void> removeContexts(String key);
99
Future<void> setUser(SentryUser? user);
10-
Future<void> addBreadcrumb(Breadcrumb breadcrumb);
11-
Future<void> clearBreadcrumbs();
10+
FutureOr<void> addBreadcrumb(Breadcrumb breadcrumb);
11+
FutureOr<void> clearBreadcrumbs();
1212
Future<void> setExtra(String key, dynamic value);
1313
Future<void> removeExtra(String key);
1414
Future<void> setTag(String key, String value);

packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ class SentryFlutterPlugin :
6464
"setContexts" -> setContexts(call.argument("key"), call.argument("value"), result)
6565
"removeContexts" -> removeContexts(call.argument("key"), result)
6666
"setUser" -> setUser(call.argument("user"), result)
67-
"addBreadcrumb" -> addBreadcrumb(call.argument("breadcrumb"), result)
68-
"clearBreadcrumbs" -> clearBreadcrumbs(result)
6967
"setExtra" -> setExtra(call.argument("key"), call.argument("value"), result)
7068
"removeExtra" -> removeExtra(call.argument("key"), result)
7169
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
@@ -189,24 +187,6 @@ class SentryFlutterPlugin :
189187
result.success("")
190188
}
191189

192-
private fun addBreadcrumb(
193-
breadcrumb: Map<String, Any?>?,
194-
result: Result,
195-
) {
196-
if (breadcrumb != null) {
197-
val options = ScopesAdapter.getInstance().options
198-
val breadcrumbInstance = Breadcrumb.fromMap(breadcrumb, options)
199-
Sentry.addBreadcrumb(breadcrumbInstance)
200-
}
201-
result.success("")
202-
}
203-
204-
private fun clearBreadcrumbs(result: Result) {
205-
Sentry.clearBreadcrumbs()
206-
207-
result.success("")
208-
}
209-
210190
private fun setExtra(
211191
key: String?,
212192
value: String?,

packages/flutter/example/integration_test/integration_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,51 @@ void main() {
564564
}
565565
});
566566

567+
testWidgets('addBreadcrumb and clearBreadcrumbs sync to native',
568+
(tester) async {
569+
await restoreFlutterOnErrorAfter(() async {
570+
await setupSentryAndApp(tester);
571+
});
572+
573+
// 1. Add a breadcrumb via Dart
574+
final testBreadcrumb = Breadcrumb(
575+
message: 'test-breadcrumb-message',
576+
category: 'test-category',
577+
level: SentryLevel.info,
578+
);
579+
await Sentry.addBreadcrumb(testBreadcrumb);
580+
581+
// 2. Verify it appears in native via loadContexts
582+
var contexts = await SentryFlutter.native?.loadContexts();
583+
expect(contexts, isNotNull);
584+
585+
var breadcrumbs = contexts!['breadcrumbs'] as List<dynamic>?;
586+
expect(breadcrumbs, isNotNull,
587+
reason: 'Breadcrumbs should not be null after adding');
588+
expect(breadcrumbs!.isNotEmpty, isTrue,
589+
reason: 'Breadcrumbs should not be empty after adding');
590+
591+
// Find our test breadcrumb
592+
final testCrumb = breadcrumbs.firstWhere(
593+
(b) => b['message'] == 'test-breadcrumb-message',
594+
orElse: () => null,
595+
);
596+
expect(testCrumb, isNotNull,
597+
reason: 'Test breadcrumb should exist in native breadcrumbs');
598+
expect(testCrumb['category'], equals('test-category'));
599+
600+
// 3. Clear breadcrumbs
601+
await Sentry.configureScope((scope) async {
602+
await scope.clearBreadcrumbs();
603+
});
604+
605+
// 4. Verify they're cleared in native
606+
contexts = await SentryFlutter.native?.loadContexts();
607+
breadcrumbs = contexts!['breadcrumbs'] as List<dynamic>?;
608+
expect(breadcrumbs == null || breadcrumbs.isEmpty, isTrue,
609+
reason: 'Breadcrumbs should be null or empty after clearing');
610+
});
611+
567612
testWidgets('loads debug images through loadDebugImages', (tester) async {
568613
await restoreFlutterOnErrorAfter(() async {
569614
await setupSentryAndApp(tester);

packages/flutter/ffi-cocoa.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ headers:
66
entry-points:
77
- ./temp/Sentry.framework/PrivateHeaders/PrivateSentrySDKOnly.h
88
- ./temp/Sentry.framework/Headers/Sentry-Swift.h
9+
- ./temp/Sentry.framework/Headers/SentryScope.h
910
- ./ios/sentry_flutter/Sources/sentry_flutter_objc/SentryFlutterPlugin.h
1011
compiler-opts:
1112
- -DSENTRY_TARGET_PROFILING_SUPPORTED=1
@@ -19,6 +20,7 @@ objc-interfaces:
1920
- PrivateSentrySDKOnly
2021
- SentryId
2122
- SentryFlutterPlugin
23+
- SentryScope
2224
- SentrySDK
2325
module:
2426
'SentryId': 'Sentry'
@@ -29,6 +31,11 @@ objc-interfaces:
2931
- 'crash'
3032
- 'pauseAppHangTracking'
3133
- 'resumeAppHangTracking'
34+
- 'configureScope:'
35+
- 'addBreadcrumb:'
36+
SentryScope:
37+
include:
38+
- 'clearBreadcrumbs'
3239
preamble: |
3340
// ignore_for_file: type=lint, unused_element
3441

packages/flutter/ffi-jni.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ classes:
1616
- io.sentry.android.core.InternalSentrySdk
1717
- io.sentry.android.replay.ReplayIntegration
1818
- io.sentry.flutter.SentryFlutterPlugin
19+
- io.sentry.Sentry
20+
- io.sentry.Breadcrumb
21+
- io.sentry.ScopesAdapter
1922
- android.graphics.Bitmap

packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
9292
let user = arguments?["user"] as? [String: Any]
9393
setUser(user: user, result: result)
9494

95-
case "addBreadcrumb":
96-
let arguments = call.arguments as? [String: Any?]
97-
let breadcrumb = arguments?["breadcrumb"] as? [String: Any]
98-
addBreadcrumb(breadcrumb: breadcrumb, result: result)
99-
100-
case "clearBreadcrumbs":
101-
clearBreadcrumbs(result: result)
102-
10395
case "setExtra":
10496
let arguments = call.arguments as? [String: Any?]
10597
let key = arguments?["key"] as? String
@@ -322,22 +314,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
322314
result("")
323315
}
324316

325-
private func addBreadcrumb(breadcrumb: [String: Any]?, result: @escaping FlutterResult) {
326-
if let breadcrumb = breadcrumb {
327-
let breadcrumbInstance = PrivateSentrySDKOnly.breadcrumb(with: breadcrumb)
328-
SentrySDK.addBreadcrumb(breadcrumbInstance)
329-
}
330-
result("")
331-
}
332-
333-
private func clearBreadcrumbs(result: @escaping FlutterResult) {
334-
SentrySDK.configureScope { scope in
335-
scope.clearBreadcrumbs()
336-
337-
result("")
338-
}
339-
}
340-
341317
private func setExtra(key: String?, value: Any?, result: @escaping FlutterResult) {
342318
guard let key = key else {
343319
result("")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// We need to add this file until we update the objective_c package due to a bug
6+
// See the issue: https://github.com/dart-lang/native/pull/2581
7+
8+
#ifndef OBJECTIVE_C_SRC_NS_NUMBER_H_
9+
#define OBJECTIVE_C_SRC_NS_NUMBER_H_
10+
11+
#import <Foundation/Foundation.h>
12+
13+
@interface NSNumber (NSNumberIsFloat)
14+
@property (readonly) bool isFloat;
15+
@end
16+
17+
#endif // OBJECTIVE_C_SRC_NS_NUMBER_H_
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// We need to add this file until we update the objective_c package due to a bug
6+
// See the issue: https://github.com/dart-lang/native/pull/2581
7+
8+
#import "ns_number.h"
9+
10+
@implementation NSNumber (NSNumberIsFloat)
11+
-(bool)isFloat {
12+
const char *t = [self objCType];
13+
return strcmp(t, @encode(float)) == 0 || strcmp(t, @encode(double)) == 0;
14+
}
15+
@end

0 commit comments

Comments
 (0)