In my Flutter app, I have main() set up to catch runtime errors, like this:
void main() async
{
runZoned< Future<void> >(() async {
runApp ( MyApp() );
}, onError: ( error, stackTrace ) async {
print("Got an error");
// ...
});
}
This works fine for all Flutter-level errors, and also works fine for code that runs natively on Android, but the onError: handler is never called for iOS apps.
An example:
My Flutter app calls the following native method through a method channel:
void _onButtonClick() async {
final channel = const MethodChannel('my-custom-channel');
await channel.invokeMethod('testMethod');
}
Here's the Android implementation:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
MethodChannel channel = new MethodChannel(getFlutterView(), "my-custom-channel");
channel.setMethodCallHandler(new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
if ( call.method.equals("testMethod") ) {
throw new IllegalStateException("This is a native Android exception.");
} else {
result.notImplemented();
}
}
});
}
When this code executes, the IllegalStateException is "propagated" back up to the onError: handler in Flutter's main() function, and I get some info and a bit of a stack trace, as expected.
Here is the iOS implementation:
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"my-custom-channel"
binaryMessenger:controller];
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ( [call.method isEqualToString:@"testMethod"] )
{
@throw([NSException
exceptionWithName:@"iOS Exception"
reason:@"Testing native iOS exceptions"
userInfo:nil]);
result(FlutterMethodNotImplemented);
}
else
{
result(FlutterMethodNotImplemented);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
In this case, the onError: handler in Flutter's main() function is never called. The console output is the following:
Lost connection to device.
*** First throw call stack:
(
0 CoreFoundation 0x00000001065cf1bb __exceptionPreprocess + 331
1 libobjc.A.dylib 0x0000000105b6d735 objc_exception_throw + 48
2 Runner 0x0000000103cd0772 __57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke + 226
3 Flutter 0x0000000103d7b9a2 __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke + 115
4 Flutter 0x0000000103d98616 _ZNK5shell21PlatformMessageRouter21HandlePlatformMessageEN3fml6RefPtrIN5blink15PlatformMessageEEE + 166
5 Flutter 0x0000000103d9bfbe _ZN5shell15PlatformViewIOS21HandlePlatformMessageEN3fml6RefPtrIN5blink15PlatformMessageEEE + 38
6 Flutter 0x0000000103dec8e9 _ZNSt3__110__function6__funcIZN5shell5Shell29OnEngineHandle<…>
That looks like a bit of a stack trace output from iOS land, but nothing ever "propagates" back up to the Flutter onError: handler, like it does with Android.
It's worth mentioning that this only happens for native NSExceptions, and also happens if I force a crash with something like assert(false) (both in the simulator and on physical devices), but it does not happen if I use [FlutterError errorWithCode: ... ] instead of native NSExceptions on the iOS side.
So, is there a way to have native iOS exceptions and crashes propagate back up to the onError: handler in Flutter?