Skip to content

Commit 393f8ec

Browse files
authored
Refactor: use FFI/JNI for setUser (#3295)
* 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 * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update CHANGELOG * Update * JNI memory leak fix for setUser
1 parent c0dde50 commit 393f8ec

File tree

14 files changed

+1119
-85
lines changed

14 files changed

+1119
-85
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 `setUser` to use FFI/JNI ([#3295](https://github.com/getsentry/sentry-dart/pull/3295/))
1213
- Refactor native breadcrumbs sync to use FFI/JNI ([#3293](https://github.com/getsentry/sentry-dart/pull/3293/))
1314
- Refactor app hang and crash apis to use FFI/JNI ([#3289](https://github.com/getsentry/sentry-dart/pull/3289/))
1415
- Refactor `AndroidReplayRecorder` to use the new worker isolate api ([#3296](https://github.com/getsentry/sentry-dart/pull/3296/))

packages/dart/lib/src/scope_observer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'protocol/sentry_user.dart';
66
abstract class ScopeObserver {
77
Future<void> setContexts(String key, dynamic value);
88
Future<void> removeContexts(String key);
9-
Future<void> setUser(SentryUser? user);
9+
FutureOr<void> setUser(SentryUser? user);
1010
FutureOr<void> addBreadcrumb(Breadcrumb breadcrumb);
1111
FutureOr<void> clearBreadcrumbs();
1212
Future<void> setExtra(String key, dynamic value);

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ class SentryFlutterPlugin :
6363
"closeNativeSdk" -> closeNativeSdk(result)
6464
"setContexts" -> setContexts(call.argument("key"), call.argument("value"), result)
6565
"removeContexts" -> removeContexts(call.argument("key"), result)
66-
"setUser" -> setUser(call.argument("user"), result)
6766
"setExtra" -> setExtra(call.argument("key"), call.argument("value"), result)
6867
"removeExtra" -> removeExtra(call.argument("key"), result)
6968
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
@@ -173,20 +172,6 @@ class SentryFlutterPlugin :
173172
}
174173
}
175174

176-
private fun setUser(
177-
user: Map<String, Any?>?,
178-
result: Result,
179-
) {
180-
if (user != null) {
181-
val options = ScopesAdapter.getInstance().options
182-
val userInstance = User.fromMap(user, options)
183-
Sentry.setUser(userInstance)
184-
} else {
185-
Sentry.setUser(null)
186-
}
187-
result.success("")
188-
}
189-
190175
private fun setExtra(
191176
key: String?,
192177
value: String?,
@@ -268,6 +253,7 @@ class SentryFlutterPlugin :
268253
@JvmStatic
269254
fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay
270255

256+
@Suppress("unused") // Used by native/jni bindings
271257
@JvmStatic
272258
fun crash() {
273259
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")

packages/flutter/example/integration_test/integration_test.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,45 @@ void main() {
609609
reason: 'Breadcrumbs should be null or empty after clearing');
610610
});
611611

612+
testWidgets('setUser syncs to native', (tester) async {
613+
await restoreFlutterOnErrorAfter(() async {
614+
await setupSentryAndApp(tester);
615+
});
616+
617+
// 1. Set a user via Dart
618+
final testUser = SentryUser(
619+
id: 'test-user-id',
620+
email: 'test@example.com',
621+
username: 'test-username',
622+
);
623+
await Sentry.configureScope((scope) async {
624+
await scope.setUser(testUser);
625+
});
626+
627+
// 2. Verify it appears in native via loadContexts
628+
var contexts = await SentryFlutter.native?.loadContexts();
629+
expect(contexts, isNotNull);
630+
631+
var user = contexts!['user'] as Map<dynamic, dynamic>?;
632+
expect(user, isNotNull, reason: 'User should not be null after setting');
633+
expect(user!['id'], equals('test-user-id'));
634+
expect(user['email'], equals('test@example.com'));
635+
expect(user['username'], equals('test-username'));
636+
637+
// 3. Clear user (after clearing the id should remain)
638+
await Sentry.configureScope((scope) async {
639+
await scope.setUser(null);
640+
});
641+
642+
// 4. Verify it's cleared in native
643+
contexts = await SentryFlutter.native?.loadContexts();
644+
user = contexts!['user'] as Map<dynamic, dynamic>?;
645+
expect(user!['email'], isNull);
646+
expect(user['username'], isNull);
647+
expect(user['id'], isNotNull);
648+
expect(user['id'], isNotEmpty);
649+
});
650+
612651
testWidgets('loads debug images through loadDebugImages', (tester) async {
613652
await restoreFlutterOnErrorAfter(() async {
614653
await setupSentryAndApp(tester);

packages/flutter/ffi-cocoa.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ objc-interfaces:
2222
- SentryFlutterPlugin
2323
- SentryScope
2424
- SentrySDK
25+
- SentryUser
2526
module:
2627
'SentryId': 'Sentry'
2728
'SentrySDK': 'Sentry'
@@ -33,6 +34,7 @@ objc-interfaces:
3334
- 'resumeAppHangTracking'
3435
- 'configureScope:'
3536
- 'addBreadcrumb:'
37+
- 'setUser:'
3638
SentryScope:
3739
include:
3840
- 'clearBreadcrumbs'

packages/flutter/ffi-jni.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ classes:
1919
- io.sentry.Sentry
2020
- io.sentry.Breadcrumb
2121
- io.sentry.ScopesAdapter
22+
- io.sentry.protocol.User
2223
- android.graphics.Bitmap

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
8787
let key = arguments?["key"] as? String
8888
removeContexts(key: key, result: result)
8989

90-
case "setUser":
91-
let arguments = call.arguments as? [String: Any?]
92-
let user = arguments?["user"] as? [String: Any]
93-
setUser(user: user, result: result)
94-
9590
case "setExtra":
9691
let arguments = call.arguments as? [String: Any?]
9792
let key = arguments?["key"] as? String
@@ -304,16 +299,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
304299
}
305300
}
306301

307-
private func setUser(user: [String: Any]?, result: @escaping FlutterResult) {
308-
if let user = user {
309-
let userInstance = PrivateSentrySDKOnly.user(with: user)
310-
SentrySDK.setUser(userInstance)
311-
} else {
312-
SentrySDK.setUser(nil)
313-
}
314-
result("")
315-
}
316-
317302
private func setExtra(key: String?, value: Any?, result: @escaping FlutterResult) {
318303
guard let key = key else {
319304
result("")

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,8 @@ final _objc_msgSend_1s56lr9 = objc.msgSendPointer
526526
ffi.Pointer<objc.ObjCSelector>, bool)>();
527527
late final _sel_appStartMeasurementWithSpans =
528528
objc.registerName("appStartMeasurementWithSpans");
529+
late final _class_SentryUser = objc.getClass("SentryUser");
529530

530-
/// WARNING: SentryUser is a stub. To generate bindings for this class, include
531-
/// SentryUser in your config's objc-interfaces list.
532-
///
533531
/// SentryUser
534532
class SentryUser extends objc.ObjCObjectBase {
535533
SentryUser._(ffi.Pointer<objc.ObjCObject> pointer,
@@ -544,6 +542,12 @@ class SentryUser extends objc.ObjCObjectBase {
544542
SentryUser.castFromPointer(ffi.Pointer<objc.ObjCObject> other,
545543
{bool retain = false, bool release = false})
546544
: this._(other, retain: retain, release: release);
545+
546+
/// Returns whether [obj] is an instance of [SentryUser].
547+
static bool isInstance(objc.ObjCObjectBase obj) {
548+
return _objc_msgSend_19nvye5(
549+
obj.ref.pointer, _sel_isKindOfClass_, _class_SentryUser);
550+
}
547551
}
548552

549553
late final _sel_userWithDictionary_ = objc.registerName("userWithDictionary:");
@@ -1372,6 +1376,7 @@ extension ObjCBlock_ffiVoid_SentryScope_CallExtension
13721376
}
13731377

13741378
late final _sel_configureScope_ = objc.registerName("configureScope:");
1379+
late final _sel_setUser_ = objc.registerName("setUser:");
13751380
late final _sel_crash = objc.registerName("crash");
13761381
late final _sel_pauseAppHangTracking =
13771382
objc.registerName("pauseAppHangTracking");
@@ -1417,6 +1422,15 @@ class SentrySDK extends objc.NSObject {
14171422
_class_SentrySDK, _sel_configureScope_, callback.ref.pointer);
14181423
}
14191424

1425+
/// Set <code>user</code> to the current <code>Scope</code> of the current <code>Hub</code>.
1426+
/// note:
1427+
/// You must start the SDK before calling this method, otherwise it doesn’t set the user.
1428+
/// \param user The user to set to the current <code>Scope</code>.
1429+
static void setUser(SentryUser? user) {
1430+
_objc_msgSend_xtuoz7(
1431+
_class_SentrySDK, _sel_setUser_, user?.ref.pointer ?? ffi.nullptr);
1432+
}
1433+
14201434
/// This forces a crash, useful to test the <code>SentryCrash</code> integration.
14211435
/// note:
14221436
/// The SDK can’t report a crash when a debugger is attached. Your application needs to run

packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,19 @@ class SentryNativeCocoa extends SentryNativeChannel {
222222
void resumeAppHangTracking() => tryCatchSync('resumeAppHangTracking', () {
223223
cocoa.SentrySDK.resumeAppHangTracking();
224224
});
225+
226+
@override
227+
void setUser(SentryUser? user) => tryCatchSync('setUser', () {
228+
if (user == null) {
229+
cocoa.SentrySDK.setUser(null);
230+
} else {
231+
final dictionary =
232+
_deepConvertMapNonNull(user.toJson()).toNSDictionary();
233+
final cUser =
234+
cocoa.PrivateSentrySDKOnly.userWithDictionary(dictionary);
235+
cocoa.SentrySDK.setUser(cUser);
236+
}
237+
});
225238
}
226239

227240
/// This map conversion is needed so we can use the toNSDictionary extension function

0 commit comments

Comments
 (0)