Skip to content

Commit 9555112

Browse files
Provide a way to cause an example native crash from Flutter (#2239)
* add SentryFlutter.nativeCrash() for Android and iOS * add changelog entry * remove unused variable * improved kotlin implementation * fix kotlin analysis warnings * Update CHANGELOG.md Co-authored-by: Giancarlo Buenaflor <[email protected]> * fix kotlin linter errors * remove whitespace * add Description for nativeCrash --------- Co-authored-by: Giancarlo Buenaflor <[email protected]>
1 parent 256df44 commit 9555112

File tree

8 files changed

+75
-19
lines changed

8 files changed

+75
-19
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features
66

7+
- Add `SentryFlutter.nativeCrash()` using MethodChannels for Android and iOS ([#2239](https://github.com/getsentry/sentry-dart/pull/2239))
8+
- This can be used to test if native crash reporting works
79
- Add `ignoreRoutes` parameter to `SentryNavigatorObserver`. ([#2218](https://github.com/getsentry/sentry-dart/pull/2218))
810
- This will ignore the Routes and prevent the Route from being pushed to the Sentry server.
911
- Ignored routes will also create no TTID and TTFD spans.

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

+19-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.sentry.flutter
33
import android.app.Activity
44
import android.content.Context
55
import android.os.Build
6+
import android.os.Looper
67
import android.util.Log
78
import io.flutter.embedding.engine.plugins.FlutterPlugin
89
import io.flutter.embedding.engine.plugins.activity.ActivityAware
@@ -49,8 +50,8 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
4950

5051
sentryFlutter =
5152
SentryFlutter(
52-
androidSdk = androidSdk,
53-
nativeSdk = nativeSdk,
53+
androidSdk = ANDROID_SDK,
54+
nativeSdk = NATIVE_SDK,
5455
)
5556
}
5657

@@ -74,6 +75,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
7475
"removeTag" -> removeTag(call.argument("key"), result)
7576
"loadContexts" -> loadContexts(result)
7677
"displayRefreshRate" -> displayRefreshRate(result)
78+
"nativeCrash" -> crash()
7779
else -> result.notImplemented()
7880
}
7981
}
@@ -413,16 +415,17 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
413415
}
414416

415417
companion object {
418+
private const val FLUTTER_SDK = "sentry.dart.flutter"
419+
private const val ANDROID_SDK = "sentry.java.android.flutter"
420+
private const val NATIVE_SDK = "sentry.native.android.flutter"
421+
private const val NATIVE_CRASH_WAIT_TIME = 500L
416422

417-
private const val flutterSdk = "sentry.dart.flutter"
418-
private const val androidSdk = "sentry.java.android.flutter"
419-
private const val nativeSdk = "sentry.native.android.flutter"
420423
private fun setEventOriginTag(event: SentryEvent) {
421424
event.sdk?.let {
422425
when (it.name) {
423-
flutterSdk -> setEventEnvironmentTag(event, "flutter", "dart")
424-
androidSdk -> setEventEnvironmentTag(event, environment = "java")
425-
nativeSdk -> setEventEnvironmentTag(event, environment = "native")
426+
FLUTTER_SDK -> setEventEnvironmentTag(event, "flutter", "dart")
427+
ANDROID_SDK -> setEventEnvironmentTag(event, environment = "java")
428+
NATIVE_SDK -> setEventEnvironmentTag(event, environment = "native")
426429
else -> return
427430
}
428431
}
@@ -439,7 +442,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
439442

440443
private fun addPackages(event: SentryEvent, sdk: SdkVersion?) {
441444
event.sdk?.let {
442-
if (it.name == flutterSdk) {
445+
if (it.name == FLUTTER_SDK) {
443446
sdk?.packageSet?.forEach { sentryPackage ->
444447
it.addPackage(sentryPackage.name, sentryPackage.version)
445448
}
@@ -449,6 +452,13 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
449452
}
450453
}
451454
}
455+
456+
private fun crash() {
457+
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
458+
val mainThread = Looper.getMainLooper().thread
459+
mainThread.uncaughtExceptionHandler.uncaughtException(mainThread, exception)
460+
mainThread.join(NATIVE_CRASH_WAIT_TIME)
461+
}
452462
}
453463

454464
private fun loadContexts(result: Result) {

flutter/example/lib/main.dart

+12
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,12 @@ class AndroidExample extends StatelessWidget {
758758
},
759759
child: const Text('Platform exception'),
760760
),
761+
ElevatedButton(
762+
onPressed: () async {
763+
SentryFlutter.nativeCrash();
764+
},
765+
child: const Text('Sentry.nativeCrash'),
766+
),
761767
]);
762768
}
763769
}
@@ -870,6 +876,12 @@ class CocoaExample extends StatelessWidget {
870876
},
871877
child: const Text('Objective-C SEGFAULT'),
872878
),
879+
ElevatedButton(
880+
onPressed: () async {
881+
SentryFlutter.nativeCrash();
882+
},
883+
child: const Text('Sentry.nativeCrash'),
884+
),
873885
],
874886
);
875887
}

flutter/ios/Classes/SentryFlutterPluginApple.swift

+7
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
174174
case "resumeAppHangTracking":
175175
resumeAppHangTracking(result)
176176

177+
case "nativeCrash":
178+
crash()
179+
177180
default:
178181
result(FlutterMethodNotImplemented)
179182
}
@@ -729,6 +732,10 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin {
729732
SentrySDK.resumeAppHangTracking()
730733
result("")
731734
}
735+
736+
private func crash() {
737+
SentrySDK.crash()
738+
}
732739
}
733740

734741
// swiftlint:enable function_body_length

flutter/lib/src/native/sentry_native_binding.dart

+2
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@ abstract class SentryNativeBinding {
5757
Future<void> pauseAppHangTracking();
5858

5959
Future<void> resumeAppHangTracking();
60+
61+
Future<void> nativeCrash();
6062
}

flutter/lib/src/native/sentry_native_channel.dart

+3
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,7 @@ class SentryNativeChannel
194194
@override
195195
Future<void> resumeAppHangTracking() =>
196196
_channel.invokeMethod('resumeAppHangTracking');
197+
198+
@override
199+
Future<void> nativeCrash() => _channel.invokeMethod('nativeCrash');
197200
}

flutter/lib/src/sentry_flutter.dart

+21-10
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,7 @@ mixin SentryFlutter {
249249
/// Only for iOS and macOS.
250250
static Future<void> pauseAppHangTracking() {
251251
if (_native == null) {
252-
// ignore: invalid_use_of_internal_member
253-
Sentry.currentHub.options.logger(
254-
SentryLevel.debug,
255-
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the pauseAppHangTracking API.',
256-
);
252+
_logNativeIntegrationNotAvailable("pauseAppHangTracking");
257253
return Future<void>.value();
258254
}
259255
return _native!.pauseAppHangTracking();
@@ -263,11 +259,7 @@ mixin SentryFlutter {
263259
/// Only for iOS and macOS
264260
static Future<void> resumeAppHangTracking() {
265261
if (_native == null) {
266-
// ignore: invalid_use_of_internal_member
267-
Sentry.currentHub.options.logger(
268-
SentryLevel.debug,
269-
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the resumeAppHangTracking API.',
270-
);
262+
_logNativeIntegrationNotAvailable("resumeAppHangTracking");
271263
return Future<void>.value();
272264
}
273265
return _native!.resumeAppHangTracking();
@@ -280,4 +272,23 @@ mixin SentryFlutter {
280272
static set native(SentryNativeBinding? value) => _native = value;
281273

282274
static SentryNativeBinding? _native;
275+
276+
/// Use `nativeCrash()` to crash the native implementation and test/debug the crash reporting for native code.
277+
/// This should not be used in production code.
278+
/// Only for Android, iOS and macOS
279+
static Future<void> nativeCrash() {
280+
if (_native == null) {
281+
_logNativeIntegrationNotAvailable("nativeCrash");
282+
return Future<void>.value();
283+
}
284+
return _native!.nativeCrash();
285+
}
286+
287+
static void _logNativeIntegrationNotAvailable(String methodName) {
288+
// ignore: invalid_use_of_internal_member
289+
Sentry.currentHub.options.logger(
290+
SentryLevel.debug,
291+
'Native integration is not available. Make sure SentryFlutter is initialized before accessing the $methodName API.',
292+
);
293+
}
283294
}

flutter/test/sentry_native_channel_test.dart

+9
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,15 @@ void main() {
302302

303303
verify(channel.invokeMethod('resumeAppHangTracking'));
304304
});
305+
306+
test('nativeCrash', () async {
307+
when(channel.invokeMethod('nativeCrash'))
308+
.thenAnswer((_) => Future.value());
309+
310+
await sut.nativeCrash();
311+
312+
verify(channel.invokeMethod('nativeCrash'));
313+
});
305314
});
306315
}
307316
}

0 commit comments

Comments
 (0)